10a0c62823
We now track the views on a per-route basis. We have plans for an admin UI for this, global views, etc. which will come in a future commit. The local error JSON is now properly formed. Fixed an outdated line in topic.go which was using the old cache system. We now use fuzzy dates for relative times between three months ago and a year ago. Added the super admin middleware and the associated tests. Added the route column to the viewchunks table. Added more alt attributes to images. Added a few missing ARIA attributes. Began refactoring the route generator to use text/template instead of generating everything procedurally. Began work on per-topic view counts.
348 lines
9.0 KiB
Go
348 lines
9.0 KiB
Go
/* WIP Under Construction */
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"text/template"
|
|
)
|
|
|
|
var routeList []*RouteImpl
|
|
var routeGroups []*RouteGroup
|
|
|
|
type TmplVars struct {
|
|
RouteList []*RouteImpl
|
|
RouteGroups []*RouteGroup
|
|
AllRouteNames []string
|
|
AllRouteMap map[string]int
|
|
}
|
|
|
|
func main() {
|
|
log.Println("Generating the router...")
|
|
|
|
// Load all the routes...
|
|
routes()
|
|
|
|
var tmplVars = TmplVars{
|
|
RouteList: routeList,
|
|
RouteGroups: routeGroups,
|
|
}
|
|
var allRouteNames []string
|
|
var allRouteMap = make(map[string]int)
|
|
|
|
var out string
|
|
var mapIt = func(name string) {
|
|
allRouteNames = append(allRouteNames, name)
|
|
allRouteMap[name] = len(allRouteNames) - 1
|
|
}
|
|
var countToIndents = func(indent int) (indentor string) {
|
|
for i := 0; i < indent; i++ {
|
|
indentor += "\t"
|
|
}
|
|
return indentor
|
|
}
|
|
var runBefore = func(runnables []Runnable, indent int) (out string) {
|
|
var indentor = countToIndents(indent)
|
|
if len(runnables) > 0 {
|
|
for _, runnable := range runnables {
|
|
if runnable.Literal {
|
|
out += "\n\t" + indentor + runnable.Contents
|
|
} else {
|
|
out += "\n" + indentor + "err = common." + runnable.Contents + "(w,req,user)\n" +
|
|
indentor + "if err != nil {\n" +
|
|
indentor + "\trouter.handleError(err,w,req,user)\n" +
|
|
indentor + "\treturn\n" +
|
|
indentor + "}\n" + indentor
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
for _, route := range routeList {
|
|
mapIt(route.Name)
|
|
var end = len(route.Path) - 1
|
|
out += "\n\t\tcase \"" + route.Path[0:end] + "\":"
|
|
out += runBefore(route.RunBefore, 4)
|
|
out += "\n\t\t\tcommon.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[route.Name]) + ")"
|
|
out += "\n\t\t\terr = " + route.Name + "(w,req,user"
|
|
for _, item := range route.Vars {
|
|
out += "," + item
|
|
}
|
|
out += `)
|
|
if err != nil {
|
|
router.handleError(err,w,req,user)
|
|
}`
|
|
}
|
|
|
|
for _, group := range routeGroups {
|
|
var end = len(group.Path) - 1
|
|
out += "\n\t\tcase \"" + group.Path[0:end] + "\":"
|
|
out += runBefore(group.RunBefore, 3)
|
|
out += "\n\t\t\tswitch(req.URL.Path) {"
|
|
|
|
var defaultRoute = blankRoute()
|
|
for _, route := range group.RouteList {
|
|
if group.Path == route.Path {
|
|
defaultRoute = route
|
|
continue
|
|
}
|
|
mapIt(route.Name)
|
|
|
|
out += "\n\t\t\t\tcase \"" + route.Path + "\":"
|
|
if len(route.RunBefore) > 0 {
|
|
skipRunnable:
|
|
for _, runnable := range route.RunBefore {
|
|
for _, gRunnable := range group.RunBefore {
|
|
if gRunnable.Contents == runnable.Contents {
|
|
continue
|
|
}
|
|
// TODO: Stop hard-coding these
|
|
if gRunnable.Contents == "AdminOnly" && runnable.Contents == "MemberOnly" {
|
|
continue skipRunnable
|
|
}
|
|
if gRunnable.Contents == "AdminOnly" && runnable.Contents == "SuperModOnly" {
|
|
continue skipRunnable
|
|
}
|
|
if gRunnable.Contents == "SuperModOnly" && runnable.Contents == "MemberOnly" {
|
|
continue skipRunnable
|
|
}
|
|
}
|
|
|
|
if runnable.Literal {
|
|
out += "\n\t\t\t\t\t" + runnable.Contents
|
|
} else {
|
|
out += `
|
|
err = common.` + runnable.Contents + `(w,req,user)
|
|
if err != nil {
|
|
router.handleError(err,w,req,user)
|
|
return
|
|
}
|
|
`
|
|
}
|
|
}
|
|
}
|
|
out += "\n\t\t\t\t\tcommon.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[route.Name]) + ")"
|
|
out += "\n\t\t\t\t\terr = " + route.Name + "(w,req,user"
|
|
for _, item := range route.Vars {
|
|
out += "," + item
|
|
}
|
|
out += ")"
|
|
}
|
|
|
|
if defaultRoute.Name != "" {
|
|
mapIt(defaultRoute.Name)
|
|
out += "\n\t\t\t\tdefault:"
|
|
out += runBefore(defaultRoute.RunBefore, 4)
|
|
out += "\n\t\t\t\t\tcommon.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[defaultRoute.Name]) + ")"
|
|
out += "\n\t\t\t\t\terr = " + defaultRoute.Name + "(w,req,user"
|
|
for _, item := range defaultRoute.Vars {
|
|
out += ", " + item
|
|
}
|
|
out += ")"
|
|
}
|
|
out += `
|
|
}
|
|
if err != nil {
|
|
router.handleError(err,w,req,user)
|
|
}`
|
|
}
|
|
|
|
tmplVars.AllRouteNames = allRouteNames
|
|
tmplVars.AllRouteMap = allRouteMap
|
|
|
|
var fileData = `// Code generated by. DO NOT EDIT.
|
|
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
|
|
package main
|
|
|
|
import (
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"./common"
|
|
)
|
|
|
|
var ErrNoRoute = errors.New("That route doesn't exist.")
|
|
// TODO: What about the /uploads/ route? x.x
|
|
var RouteMap = map[string]interface{}{ {{range .AllRouteNames}}
|
|
"{{.}}": {{.}},{{end}}
|
|
}
|
|
|
|
// ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS
|
|
var routeMapEnum = map[string]int{ {{range $index, $element := .AllRouteNames}}
|
|
"{{$element}}": {{$index}},{{end}}
|
|
}
|
|
var reverseRouteMapEnum = map[int]string{ {{range $index, $element := .AllRouteNames}}
|
|
{{$index}}: "{{$element}}",{{end}}
|
|
}
|
|
|
|
// TODO: Stop spilling these into the package scope?
|
|
func init() {
|
|
common.SetRouteMapEnum(routeMapEnum)
|
|
common.SetReverseRouteMapEnum(reverseRouteMapEnum)
|
|
}
|
|
|
|
type GenRouter struct {
|
|
UploadHandler func(http.ResponseWriter, *http.Request)
|
|
extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError
|
|
|
|
sync.RWMutex
|
|
}
|
|
|
|
func NewGenRouter(uploads http.Handler) *GenRouter {
|
|
return &GenRouter{
|
|
UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP,
|
|
extraRoutes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError),
|
|
}
|
|
}
|
|
|
|
func (router *GenRouter) handleError(err common.RouteError, w http.ResponseWriter, r *http.Request, user common.User) {
|
|
if err.Handled() {
|
|
return
|
|
}
|
|
|
|
if err.Type() == "system" {
|
|
common.InternalErrorJSQ(err, w, r, err.JSON())
|
|
return
|
|
}
|
|
common.LocalErrorJSQ(err.Error(), w, r, user,err.JSON())
|
|
}
|
|
|
|
func (router *GenRouter) Handle(_ string, _ http.Handler) {
|
|
}
|
|
|
|
func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request, common.User) common.RouteError) {
|
|
router.Lock()
|
|
defer router.Unlock()
|
|
router.extraRoutes[pattern] = handle
|
|
}
|
|
|
|
func (router *GenRouter) RemoveFunc(pattern string) error {
|
|
router.Lock()
|
|
defer router.Unlock()
|
|
_, ok := router.extraRoutes[pattern]
|
|
if !ok {
|
|
return ErrNoRoute
|
|
}
|
|
delete(router.extraRoutes, pattern)
|
|
return nil
|
|
}
|
|
|
|
func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
|
|
w.WriteHeader(405)
|
|
w.Write([]byte(""))
|
|
return
|
|
}
|
|
|
|
var prefix, extraData string
|
|
prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1]
|
|
if req.URL.Path[len(req.URL.Path) - 1] != '/' {
|
|
extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
|
|
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
|
|
}
|
|
|
|
if common.Dev.SuperDebug {
|
|
log.Print("before routeStatic")
|
|
log.Print("prefix: ", prefix)
|
|
log.Print("req.URL.Path: ", req.URL.Path)
|
|
log.Print("extraData: ", extraData)
|
|
log.Print("req.Referer(): ", req.Referer())
|
|
}
|
|
|
|
if prefix == "/static" {
|
|
req.URL.Path += extraData
|
|
routeStatic(w, req)
|
|
return
|
|
}
|
|
if common.Dev.SuperDebug {
|
|
log.Print("before PreRoute")
|
|
}
|
|
|
|
// Increment the global view counter
|
|
common.GlobalViewCounter.Bump()
|
|
|
|
// Deal with the session stuff, etc.
|
|
user, ok := common.PreRoute(w, req)
|
|
if !ok {
|
|
return
|
|
}
|
|
if common.Dev.SuperDebug {
|
|
log.Print("after PreRoute")
|
|
log.Print("routeMapEnum: ", routeMapEnum)
|
|
}
|
|
|
|
var err common.RouteError
|
|
switch(prefix) {` + out + `
|
|
case "/uploads":
|
|
if extraData == "" {
|
|
common.NotFound(w,req)
|
|
return
|
|
}
|
|
req.URL.Path += extraData
|
|
// TODO: Find a way to propagate errors up from this?
|
|
router.UploadHandler(w,req) // TODO: Count these views
|
|
case "":
|
|
// Stop the favicons, robots.txt file, etc. resolving to the topics list
|
|
// TODO: Add support for favicons and robots.txt files
|
|
switch(extraData) {
|
|
case "robots.txt":
|
|
err = routeRobotsTxt(w,req) // TODO: Count these views
|
|
if err != nil {
|
|
router.handleError(err,w,req,user)
|
|
}
|
|
return
|
|
}
|
|
|
|
if extraData != "" {
|
|
common.NotFound(w,req)
|
|
return
|
|
}
|
|
common.Config.DefaultRoute(w,req,user) // TODO: Count these views
|
|
default:
|
|
// A fallback for the routes which haven't been converted to the new router yet or plugins
|
|
router.RLock()
|
|
handle, ok := router.extraRoutes[req.URL.Path]
|
|
router.RUnlock()
|
|
|
|
if ok {
|
|
req.URL.Path += extraData
|
|
err = handle(w,req,user) // TODO: Count these views
|
|
if err != nil {
|
|
router.handleError(err,w,req,user)
|
|
}
|
|
return
|
|
}
|
|
common.NotFound(w,req) // TODO: Collect all the error view counts so we can add a replacement for GlobalViewCounter by adding up the view counts of every route? Complex and may be inaccurate, collecting it globally and locally would at-least help find places we aren't capturing views
|
|
}
|
|
}
|
|
`
|
|
var tmpl = template.Must(template.New("router").Parse(fileData))
|
|
var b bytes.Buffer
|
|
err := tmpl.Execute(&b, tmplVars)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
writeFile("./gen_router.go", string(b.Bytes()))
|
|
log.Println("Successfully generated the router")
|
|
}
|
|
|
|
func writeFile(name string, content string) {
|
|
f, err := os.Create(name)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
_, err = f.WriteString(content)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
f.Sync()
|
|
f.Close()
|
|
}
|