From 10a0c62823d881236646040151512dd46f050568 Mon Sep 17 00:00:00 2001 From: Azareal Date: Tue, 19 Dec 2017 03:53:13 +0000 Subject: [PATCH] The Cosora Theme is almost complete and is being rolled out on the site to demo it. 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. --- common/common.go | 12 + common/counters.go | 219 ++++++++++++++- common/email.go | 5 +- common/errors.go | 2 +- common/routes_common.go | 8 + common/site.go | 1 + common/topic.go | 2 +- common/utils.go | 9 +- gen_router.go | 284 +++++++++++++++---- main.go | 6 +- misc_test.go | 22 ++ panel_routes.go | 3 - query_gen/tables.go | 13 + router_gen/main.go | 107 ++++--- router_gen/routes.go | 58 ++-- schema/mssql/query_viewchunks.sql | 3 +- schema/mysql/query_viewchunks.sql | 3 +- schema/pgsql/query_viewchunks.sql | 3 +- template_forum.go | 56 ++-- template_list.go | 262 +++++++++-------- template_topic_alt.go | 174 +++++++----- template_topics.go | 72 ++--- templates/forum.html | 6 +- templates/ip-search.html | 9 +- templates/panel-adminlogs.html | 8 +- templates/panel-modlogs.html | 8 +- templates/panel-themes.html | 4 +- templates/panel-users.html | 2 +- templates/topic_alt.html | 32 ++- templates/topics.html | 6 +- themes/cosora/public/main.css | 370 +++++++++++++++++++++++-- themes/cosora/public/misc.js | 80 +++++- themes/shadow/public/main.css | 6 +- themes/shadow/public/panel.css | 4 - themes/tempra-conflux/public/main.css | 32 ++- themes/tempra-conflux/public/panel.css | 4 - themes/tempra-cursive/public/main.css | 7 +- themes/tempra-simple/public/main.css | 7 +- themes/tempra-simple/public/panel.css | 4 - 39 files changed, 1440 insertions(+), 473 deletions(-) diff --git a/common/common.go b/common/common.go index f920dca2..106730d3 100644 --- a/common/common.go +++ b/common/common.go @@ -107,3 +107,15 @@ func debugLogf(str string, args ...interface{}) { log.Printf(str, args...) } } + +// TODO: Make a neater API for this +var routeMapEnum map[string]int +var reverseRouteMapEnum map[int]string + +func SetRouteMapEnum(rme map[string]int) { + routeMapEnum = rme +} + +func SetReverseRouteMapEnum(rrme map[int]string) { + reverseRouteMapEnum = rrme +} diff --git a/common/counters.go b/common/counters.go index 2e8f1196..1bece345 100644 --- a/common/counters.go +++ b/common/counters.go @@ -2,32 +2,35 @@ package common import ( "database/sql" + "log" + "sync" "sync/atomic" "../query_gen/lib" ) -var GlobalViewCounter *BufferedViewCounter +var GlobalViewCounter *ChunkedViewCounter +var RouteViewCounter *RouteViewCounterImpl -type BufferedViewCounter struct { +type ChunkedViewCounter struct { buckets [2]int64 currentBucket int64 insert *sql.Stmt } -func NewGlobalViewCounter() (*BufferedViewCounter, error) { +func NewChunkedViewCounter() (*ChunkedViewCounter, error) { acc := qgen.Builder.Accumulator() - counter := &BufferedViewCounter{ + counter := &ChunkedViewCounter{ currentBucket: 0, - insert: acc.SimpleInsert("viewchunks", "count, createdAt", "?,UTC_TIMESTAMP()"), + insert: acc.Insert("viewchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(), } - //AddScheduledFifteenMinuteTask(counter.Tick) - AddScheduledSecondTask(counter.Tick) + AddScheduledFifteenMinuteTask(counter.Tick) // This is run once every fifteen minutes to match the frequency of the RouteViewCounter + //AddScheduledSecondTask(counter.Tick) return counter, acc.FirstError() } -func (counter *BufferedViewCounter) Tick() (err error) { +func (counter *ChunkedViewCounter) Tick() (err error) { var oldBucket = counter.currentBucket var nextBucket int64 if counter.currentBucket == 1 { @@ -38,22 +41,17 @@ func (counter *BufferedViewCounter) Tick() (err error) { atomic.AddInt64(&counter.buckets[oldBucket], counter.buckets[nextBucket]) atomic.StoreInt64(&counter.buckets[nextBucket], 0) atomic.StoreInt64(&counter.currentBucket, nextBucket) - /*debugLog("counter.buckets[nextBucket]: ", counter.buckets[nextBucket]) - debugLog("counter.buckets[oldBucket]: ", counter.buckets[oldBucket]) - debugLog("counter.currentBucket:", counter.currentBucket) - debugLog("oldBucket:", oldBucket) - debugLog("nextBucket:", nextBucket)*/ var previousViewChunk = counter.buckets[oldBucket] atomic.AddInt64(&counter.buckets[oldBucket], -previousViewChunk) return counter.insertChunk(previousViewChunk) } -func (counter *BufferedViewCounter) Bump() { +func (counter *ChunkedViewCounter) Bump() { atomic.AddInt64(&counter.buckets[counter.currentBucket], 1) } -func (counter *BufferedViewCounter) insertChunk(count int64) error { +func (counter *ChunkedViewCounter) insertChunk(count int64) error { if count == 0 { return nil } @@ -61,3 +59,194 @@ func (counter *BufferedViewCounter) insertChunk(count int64) error { _, err := counter.insert.Exec(count) return err } + +type RWMutexCounterBucket struct { + counter int + sync.RWMutex +} + +// The name of the struct clashes with the name of the variable, so we're adding Impl to the end +type RouteViewCounterImpl struct { + routeBuckets []*RWMutexCounterBucket //[RouteID]count + insert *sql.Stmt +} + +func NewRouteViewCounter() (*RouteViewCounterImpl, error) { + acc := qgen.Builder.Accumulator() + var routeBuckets = make([]*RWMutexCounterBucket, len(routeMapEnum)) + for bucketID, _ := range routeBuckets { + routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} + } + counter := &RouteViewCounterImpl{ + routeBuckets: routeBuckets, + insert: acc.Insert("viewchunks").Columns("count, createdAt, route").Fields("?,UTC_TIMESTAMP(),?").Prepare(), + } + AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second + //AddScheduledSecondTask(counter.Tick) + return counter, acc.FirstError() +} + +func (counter *RouteViewCounterImpl) Tick() error { + for routeID, routeBucket := range counter.routeBuckets { + var count int + routeBucket.RLock() + count = routeBucket.counter + routeBucket.counter = 0 + routeBucket.RUnlock() + + err := counter.insertChunk(count, routeID) // TODO: Bulk insert for speed? + if err != nil { + return err + } + } + return nil +} + +func (counter *RouteViewCounterImpl) insertChunk(count int, route int) error { + if count == 0 { + return nil + } + var routeName = reverseRouteMapEnum[route] + debugLogf("Inserting a viewchunk with a count of %d for route %s (%d)", count, routeName, route) + _, err := counter.insert.Exec(count, routeName) + return err +} + +func (counter *RouteViewCounterImpl) Bump(route int) { + // TODO: Test this check + log.Print("counter.routeBuckets[route]: ", counter.routeBuckets[route]) + if len(counter.routeBuckets) <= route { + return + } + counter.routeBuckets[route].Lock() + counter.routeBuckets[route].counter++ + counter.routeBuckets[route].Unlock() +} + +// TODO: The ForumViewCounter and TopicViewCounter + +// TODO: Unload forum counters without any views over the past 15 minutes, if the admin has configured the forumstore with a cap and it's been hit? +// Forums can be reloaded from the database at any time, so we want to keep the counters separate from them +type ForumViewCounter struct { + buckets [2]int64 + currentBucket int64 +} + +/*func (counter *ForumViewCounter) insertChunk(count int, forum int) error { + if count == 0 { + return nil + } + debugLogf("Inserting a viewchunk with a count of %d for forum %d", count, forum) + _, err := counter.insert.Exec(count, forum) + return err +}*/ + +// TODO: Use two odd-even maps for now, and move to something more concurrent later, maybe a sharded map? +type TopicViewCounter struct { + oddTopics map[int]*RWMutexCounterBucket // map[tid]struct{counter,sync.RWMutex} + evenTopics map[int]*RWMutexCounterBucket + oddLock sync.RWMutex + evenLock sync.RWMutex + + update *sql.Stmt +} + +func NewTopicViewCounter() (*TopicViewCounter, error) { + acc := qgen.Builder.Accumulator() + counter := &TopicViewCounter{ + oddTopics: make(map[int]*RWMutexCounterBucket), + evenTopics: make(map[int]*RWMutexCounterBucket), + update: acc.Update("topics").Set("views = ?").Where("tid = ?").Prepare(), // TODO: Add the views column to the topics table + } + AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second + //AddScheduledSecondTask(counter.Tick) + return counter, acc.FirstError() +} + +func (counter *TopicViewCounter) Tick() error { + counter.oddLock.RLock() + for topicID, topic := range counter.oddTopics { + var count int + topic.RLock() + count = topic.counter + topic.RUnlock() + err := counter.insertChunk(count, topicID) + if err != nil { + return err + } + } + counter.oddLock.RUnlock() + + counter.evenLock.RLock() + for topicID, topic := range counter.evenTopics { + var count int + topic.RLock() + count = topic.counter + topic.RUnlock() + err := counter.insertChunk(count, topicID) + if err != nil { + return err + } + } + counter.evenLock.RUnlock() + + return nil +} + +func (counter *TopicViewCounter) insertChunk(count int, topicID int) error { + if count == 0 { + return nil + } + debugLogf("Inserting %d views into topic %d", count, topicID) + _, err := counter.update.Exec(count, topicID) + return err +} + +func (counter *TopicViewCounter) Bump(topicID int) { + // Is the ID even? + if topicID%2 == 0 { + counter.evenLock.Lock() + topic, ok := counter.evenTopics[topicID] + counter.evenLock.Unlock() + if ok { + topic.Lock() + topic.counter++ + topic.Unlock() + } else { + counter.evenLock.Lock() + counter.evenTopics[topicID] = &RWMutexCounterBucket{counter: 1} + counter.evenLock.Unlock() + } + return + } + + counter.oddLock.Lock() + topic, ok := counter.oddTopics[topicID] + counter.oddLock.Unlock() + if ok { + topic.Lock() + topic.counter++ + topic.Unlock() + } else { + counter.oddLock.Lock() + counter.oddTopics[topicID] = &RWMutexCounterBucket{counter: 1} + counter.oddLock.Unlock() + } +} + +type TreeCounterNode struct { + Value int64 + Zero *TreeCounterNode + One *TreeCounterNode + Parent *TreeCounterNode +} + +// MEGA EXPERIMENTAL. Start from the right-most bits in the integer and move leftwards +type TreeTopicViewCounter struct { + zero *TreeCounterNode + one *TreeCounterNode +} + +func (counter *TreeTopicViewCounter) Bump(topicID int64) { + +} diff --git a/common/email.go b/common/email.go index 38ba2207..a6bf7de0 100644 --- a/common/email.go +++ b/common/email.go @@ -26,6 +26,7 @@ func SendValidationEmail(username string, email string, token string) bool { } // TODO: Refactor this +// TODO: Add support for TLS func SendEmail(email string, subject string, msg string) bool { // This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server? if Vhooks["email_send_intercept"] != nil { @@ -68,6 +69,6 @@ func SendEmail(email string, subject string, msg string) bool { if err != nil { return false } - err = con.Quit() - return err == nil + + return con.Quit() == nil } diff --git a/common/errors.go b/common/errors.go index cb17260c..cc1ad568 100644 --- a/common/errors.go +++ b/common/errors.go @@ -136,7 +136,7 @@ func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user U func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteError { w.WriteHeader(500) - _, _ = w.Write([]byte(`{'errmsg': '` + errmsg + `'}`)) + _, _ = w.Write([]byte(`{"errmsg": "` + errmsg + `"}`)) return HandledRouteError() } diff --git a/common/routes_common.go b/common/routes_common.go index 9f814c8e..313266bf 100644 --- a/common/routes_common.go +++ b/common/routes_common.go @@ -284,6 +284,14 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) { return *usercpy, true } +// SuperAdminOnly makes sure that only super admin can access certain critical panel routes +func SuperAdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError { + if !user.IsSuperAdmin { + return NoPermissions(w, r, user) + } + return nil +} + // AdminOnly makes sure that only admins can access certain panel routes func AdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError { if !user.IsAdmin { diff --git a/common/site.go b/common/site.go index cb2e0813..f10d2d93 100644 --- a/common/site.go +++ b/common/site.go @@ -59,6 +59,7 @@ type config struct { SMTPUsername string SMTPPassword string SMTPPort string + //SMTPEnableTLS bool DefaultRoute func(http.ResponseWriter, *http.Request, User) RouteError DefaultGroup int diff --git a/common/topic.go b/common/topic.go index 10eaf02e..c28e3183 100644 --- a/common/topic.go +++ b/common/topic.go @@ -149,7 +149,7 @@ func init() { // Flush the topic out of the cache // ? - We do a CacheRemove() here instead of mutating the pointer to avoid creating a race condition func (topic *Topic) cacheRemove() { - tcache := Topics.(TopicCache) + tcache := Topics.GetCache() if tcache != nil { tcache.Remove(topic.ID) } diff --git a/common/utils.go b/common/utils.go index d375b0be..55e644e7 100644 --- a/common/utils.go +++ b/common/utils.go @@ -73,9 +73,12 @@ func RelativeTime(t time.Time) string { weeks := int(hours / 24 / 7) months := int(hours / 24 / 31) switch { - case months > 11: - //return t.Format("Mon Jan 2 2006") - return t.Format("Jan 2 2006") + case months > 3: + if t.Year() != time.Now().Year() { + //return t.Format("Mon Jan 2 2006") + return t.Format("Jan 2 2006") + } + return t.Format("Jan 2") case months > 1: return fmt.Sprintf("%d months ago", months) case months == 1: diff --git a/gen_router.go b/gen_router.go index 549c945c..8f9304c2 100644 --- a/gen_router.go +++ b/gen_router.go @@ -13,7 +13,8 @@ import ( ) var ErrNoRoute = errors.New("That route doesn't exist.") -var RouteMap = map[string]interface{}{ +// TODO: What about the /uploads/ route? x.x +var RouteMap = map[string]interface{}{ "routeAPI": routeAPI, "routeOverview": routeOverview, "routeForums": routeForums, @@ -72,9 +73,133 @@ var RouteMap = map[string]interface{}{ "routeIps": routeIps, } +// ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS +var routeMapEnum = map[string]int{ + "routeAPI": 0, + "routeOverview": 1, + "routeForums": 2, + "routeForum": 3, + "routeChangeTheme": 4, + "routeShowAttachment": 5, + "routeReportSubmit": 6, + "routeTopicCreate": 7, + "routeTopics": 8, + "routePanelForums": 9, + "routePanelForumsCreateSubmit": 10, + "routePanelForumsDelete": 11, + "routePanelForumsDeleteSubmit": 12, + "routePanelForumsEdit": 13, + "routePanelForumsEditSubmit": 14, + "routePanelForumsEditPermsSubmit": 15, + "routePanelSettings": 16, + "routePanelSettingEdit": 17, + "routePanelSettingEditSubmit": 18, + "routePanelWordFilters": 19, + "routePanelWordFiltersCreate": 20, + "routePanelWordFiltersEdit": 21, + "routePanelWordFiltersEditSubmit": 22, + "routePanelWordFiltersDeleteSubmit": 23, + "routePanelThemes": 24, + "routePanelThemesSetDefault": 25, + "routePanelPlugins": 26, + "routePanelPluginsActivate": 27, + "routePanelPluginsDeactivate": 28, + "routePanelPluginsInstall": 29, + "routePanelUsers": 30, + "routePanelUsersEdit": 31, + "routePanelUsersEditSubmit": 32, + "routePanelGroups": 33, + "routePanelGroupsEdit": 34, + "routePanelGroupsEditPerms": 35, + "routePanelGroupsEditSubmit": 36, + "routePanelGroupsEditPermsSubmit": 37, + "routePanelGroupsCreateSubmit": 38, + "routePanelBackups": 39, + "routePanelLogsMod": 40, + "routePanelDebug": 41, + "routePanel": 42, + "routeAccountEditCritical": 43, + "routeAccountEditCriticalSubmit": 44, + "routeAccountEditAvatar": 45, + "routeAccountEditAvatarSubmit": 46, + "routeAccountEditUsername": 47, + "routeAccountEditUsernameSubmit": 48, + "routeAccountEditEmail": 49, + "routeAccountEditEmailTokenSubmit": 50, + "routeProfile": 51, + "routeBanSubmit": 52, + "routeUnban": 53, + "routeActivate": 54, + "routeIps": 55, +} +var reverseRouteMapEnum = map[int]string{ + 0: "routeAPI", + 1: "routeOverview", + 2: "routeForums", + 3: "routeForum", + 4: "routeChangeTheme", + 5: "routeShowAttachment", + 6: "routeReportSubmit", + 7: "routeTopicCreate", + 8: "routeTopics", + 9: "routePanelForums", + 10: "routePanelForumsCreateSubmit", + 11: "routePanelForumsDelete", + 12: "routePanelForumsDeleteSubmit", + 13: "routePanelForumsEdit", + 14: "routePanelForumsEditSubmit", + 15: "routePanelForumsEditPermsSubmit", + 16: "routePanelSettings", + 17: "routePanelSettingEdit", + 18: "routePanelSettingEditSubmit", + 19: "routePanelWordFilters", + 20: "routePanelWordFiltersCreate", + 21: "routePanelWordFiltersEdit", + 22: "routePanelWordFiltersEditSubmit", + 23: "routePanelWordFiltersDeleteSubmit", + 24: "routePanelThemes", + 25: "routePanelThemesSetDefault", + 26: "routePanelPlugins", + 27: "routePanelPluginsActivate", + 28: "routePanelPluginsDeactivate", + 29: "routePanelPluginsInstall", + 30: "routePanelUsers", + 31: "routePanelUsersEdit", + 32: "routePanelUsersEditSubmit", + 33: "routePanelGroups", + 34: "routePanelGroupsEdit", + 35: "routePanelGroupsEditPerms", + 36: "routePanelGroupsEditSubmit", + 37: "routePanelGroupsEditPermsSubmit", + 38: "routePanelGroupsCreateSubmit", + 39: "routePanelBackups", + 40: "routePanelLogsMod", + 41: "routePanelDebug", + 42: "routePanel", + 43: "routeAccountEditCritical", + 44: "routeAccountEditCriticalSubmit", + 45: "routeAccountEditAvatar", + 46: "routeAccountEditAvatarSubmit", + 47: "routeAccountEditUsername", + 48: "routeAccountEditUsernameSubmit", + 49: "routeAccountEditEmail", + 50: "routeAccountEditEmailTokenSubmit", + 51: "routeProfile", + 52: "routeBanSubmit", + 53: "routeUnban", + 54: "routeActivate", + 55: "routeIps", +} + +// 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) - extra_routes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError + extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError sync.RWMutex } @@ -82,7 +207,7 @@ type GenRouter struct { func NewGenRouter(uploads http.Handler) *GenRouter { return &GenRouter{ UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP, - extra_routes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError), + extraRoutes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError), } } @@ -104,17 +229,17 @@ 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.extra_routes[pattern] = handle + router.extraRoutes[pattern] = handle } func (router *GenRouter) RemoveFunc(pattern string) error { router.Lock() defer router.Unlock() - _, ok := router.extra_routes[pattern] + _, ok := router.extraRoutes[pattern] if !ok { return ErrNoRoute } - delete(router.extra_routes, pattern) + delete(router.extraRoutes, pattern) return nil } @@ -125,10 +250,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - var prefix, extra_data string + 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] != '/' { - extra_data = req.URL.Path[strings.LastIndexByte(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] } @@ -136,12 +261,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("before routeStatic") log.Print("prefix: ", prefix) log.Print("req.URL.Path: ", req.URL.Path) - log.Print("extra_data: ", extra_data) + log.Print("extraData: ", extraData) log.Print("req.Referer(): ", req.Referer()) } if prefix == "/static" { - req.URL.Path += extra_data + req.URL.Path += extraData routeStatic(w, req) return } @@ -159,27 +284,32 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } if common.Dev.SuperDebug { log.Print("after PreRoute") + log.Print("routeMapEnum: ", routeMapEnum) } var err common.RouteError switch(prefix) { case "/api": + common.RouteViewCounter.Bump(0) err = routeAPI(w,req,user) if err != nil { router.handleError(err,w,req,user) } case "/overview": + common.RouteViewCounter.Bump(1) err = routeOverview(w,req,user) if err != nil { router.handleError(err,w,req,user) } case "/forums": + common.RouteViewCounter.Bump(2) err = routeForums(w,req,user) if err != nil { router.handleError(err,w,req,user) } case "/forum": - err = routeForum(w,req,user,extra_data) + common.RouteViewCounter.Bump(3) + err = routeForum(w,req,user,extraData) if err != nil { router.handleError(err,w,req,user) } @@ -190,6 +320,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(4) err = routeChangeTheme(w,req,user) if err != nil { router.handleError(err,w,req,user) @@ -201,7 +332,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeShowAttachment(w,req,user,extra_data) + common.RouteViewCounter.Bump(5) + err = routeShowAttachment(w,req,user,extraData) if err != nil { router.handleError(err,w,req,user) } @@ -226,7 +358,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeReportSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(6) + err = routeReportSubmit(w,req,user,extraData) } if err != nil { router.handleError(err,w,req,user) @@ -240,8 +373,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeTopicCreate(w,req,user,extra_data) + common.RouteViewCounter.Bump(7) + err = routeTopicCreate(w,req,user,extraData) default: + common.RouteViewCounter.Bump(8) err = routeTopics(w,req,user) } if err != nil { @@ -256,6 +391,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch(req.URL.Path) { case "/panel/forums/": + common.RouteViewCounter.Bump(9) err = routePanelForums(w,req,user) case "/panel/forums/create/": err = common.NoSessionMismatch(w,req,user) @@ -264,6 +400,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(10) err = routePanelForumsCreateSubmit(w,req,user) case "/panel/forums/delete/": err = common.NoSessionMismatch(w,req,user) @@ -272,7 +409,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelForumsDelete(w,req,user,extra_data) + common.RouteViewCounter.Bump(11) + err = routePanelForumsDelete(w,req,user,extraData) case "/panel/forums/delete/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -280,9 +418,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelForumsDeleteSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(12) + err = routePanelForumsDeleteSubmit(w,req,user,extraData) case "/panel/forums/edit/": - err = routePanelForumsEdit(w,req,user,extra_data) + common.RouteViewCounter.Bump(13) + err = routePanelForumsEdit(w,req,user,extraData) case "/panel/forums/edit/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -290,7 +430,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelForumsEditSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(14) + err = routePanelForumsEditSubmit(w,req,user,extraData) case "/panel/forums/edit/perms/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -298,11 +439,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelForumsEditPermsSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(15) + err = routePanelForumsEditPermsSubmit(w,req,user,extraData) case "/panel/settings/": + common.RouteViewCounter.Bump(16) err = routePanelSettings(w,req,user) case "/panel/settings/edit/": - err = routePanelSettingEdit(w,req,user,extra_data) + common.RouteViewCounter.Bump(17) + err = routePanelSettingEdit(w,req,user,extraData) case "/panel/settings/edit/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -310,8 +454,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelSettingEditSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(18) + err = routePanelSettingEditSubmit(w,req,user,extraData) case "/panel/settings/word-filters/": + common.RouteViewCounter.Bump(19) err = routePanelWordFilters(w,req,user) case "/panel/settings/word-filters/create/": err = common.NoSessionMismatch(w,req,user) @@ -320,9 +466,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(20) err = routePanelWordFiltersCreate(w,req,user) case "/panel/settings/word-filters/edit/": - err = routePanelWordFiltersEdit(w,req,user,extra_data) + common.RouteViewCounter.Bump(21) + err = routePanelWordFiltersEdit(w,req,user,extraData) case "/panel/settings/word-filters/edit/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -330,7 +478,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelWordFiltersEditSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(22) + err = routePanelWordFiltersEditSubmit(w,req,user,extraData) case "/panel/settings/word-filters/delete/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -338,8 +487,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelWordFiltersDeleteSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(23) + err = routePanelWordFiltersDeleteSubmit(w,req,user,extraData) case "/panel/themes/": + common.RouteViewCounter.Bump(24) err = routePanelThemes(w,req,user) case "/panel/themes/default/": err = common.NoSessionMismatch(w,req,user) @@ -348,8 +499,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelThemesSetDefault(w,req,user,extra_data) + common.RouteViewCounter.Bump(25) + err = routePanelThemesSetDefault(w,req,user,extraData) case "/panel/plugins/": + common.RouteViewCounter.Bump(26) err = routePanelPlugins(w,req,user) case "/panel/plugins/activate/": err = common.NoSessionMismatch(w,req,user) @@ -358,7 +511,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelPluginsActivate(w,req,user,extra_data) + common.RouteViewCounter.Bump(27) + err = routePanelPluginsActivate(w,req,user,extraData) case "/panel/plugins/deactivate/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -366,7 +520,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelPluginsDeactivate(w,req,user,extra_data) + common.RouteViewCounter.Bump(28) + err = routePanelPluginsDeactivate(w,req,user,extraData) case "/panel/plugins/install/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -374,11 +529,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelPluginsInstall(w,req,user,extra_data) + common.RouteViewCounter.Bump(29) + err = routePanelPluginsInstall(w,req,user,extraData) case "/panel/users/": + common.RouteViewCounter.Bump(30) err = routePanelUsers(w,req,user) case "/panel/users/edit/": - err = routePanelUsersEdit(w,req,user,extra_data) + common.RouteViewCounter.Bump(31) + err = routePanelUsersEdit(w,req,user,extraData) case "/panel/users/edit/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -386,13 +544,17 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelUsersEditSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(32) + err = routePanelUsersEditSubmit(w,req,user,extraData) case "/panel/groups/": + common.RouteViewCounter.Bump(33) err = routePanelGroups(w,req,user) case "/panel/groups/edit/": - err = routePanelGroupsEdit(w,req,user,extra_data) + common.RouteViewCounter.Bump(34) + err = routePanelGroupsEdit(w,req,user,extraData) case "/panel/groups/edit/perms/": - err = routePanelGroupsEditPerms(w,req,user,extra_data) + common.RouteViewCounter.Bump(35) + err = routePanelGroupsEditPerms(w,req,user,extraData) case "/panel/groups/edit/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -400,7 +562,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelGroupsEditSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(36) + err = routePanelGroupsEditSubmit(w,req,user,extraData) case "/panel/groups/edit/perms/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -408,7 +571,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routePanelGroupsEditPermsSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(37) + err = routePanelGroupsEditPermsSubmit(w,req,user,extraData) case "/panel/groups/create/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -416,10 +580,19 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(38) err = routePanelGroupsCreateSubmit(w,req,user) case "/panel/backups/": - err = routePanelBackups(w,req,user,extra_data) + err = common.SuperAdminOnly(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + common.RouteViewCounter.Bump(39) + err = routePanelBackups(w,req,user,extraData) case "/panel/logs/mod/": + common.RouteViewCounter.Bump(40) err = routePanelLogsMod(w,req,user) case "/panel/debug/": err = common.AdminOnly(w,req,user) @@ -428,8 +601,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(41) err = routePanelDebug(w,req,user) default: + common.RouteViewCounter.Bump(42) err = routePanel(w,req,user) } if err != nil { @@ -444,6 +619,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(43) err = routeAccountEditCritical(w,req,user) case "/user/edit/critical/submit/": err = common.NoSessionMismatch(w,req,user) @@ -458,6 +634,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(44) err = routeAccountEditCriticalSubmit(w,req,user) case "/user/edit/avatar/": err = common.MemberOnly(w,req,user) @@ -466,6 +643,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(45) err = routeAccountEditAvatar(w,req,user) case "/user/edit/avatar/submit/": err = common.MemberOnly(w,req,user) @@ -474,6 +652,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(46) err = routeAccountEditAvatarSubmit(w,req,user) case "/user/edit/username/": err = common.MemberOnly(w,req,user) @@ -482,6 +661,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(47) err = routeAccountEditUsername(w,req,user) case "/user/edit/username/submit/": err = common.NoSessionMismatch(w,req,user) @@ -496,6 +676,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(48) err = routeAccountEditUsernameSubmit(w,req,user) case "/user/edit/email/": err = common.MemberOnly(w,req,user) @@ -504,6 +685,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(49) err = routeAccountEditEmail(w,req,user) case "/user/edit/token/": err = common.NoSessionMismatch(w,req,user) @@ -518,9 +700,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeAccountEditEmailTokenSubmit(w,req,user,extra_data) + common.RouteViewCounter.Bump(50) + err = routeAccountEditEmailTokenSubmit(w,req,user,extraData) default: - req.URL.Path += extra_data + req.URL.Path += extraData + common.RouteViewCounter.Bump(51) err = routeProfile(w,req,user) } if err != nil { @@ -541,6 +725,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(52) err = routeBanSubmit(w,req,user) case "/users/unban/": err = common.NoSessionMismatch(w,req,user) @@ -555,6 +740,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(53) err = routeUnban(w,req,user) case "/users/activate/": err = common.NoSessionMismatch(w,req,user) @@ -569,6 +755,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(54) err = routeActivate(w,req,user) case "/users/ips/": err = common.MemberOnly(w,req,user) @@ -577,50 +764,51 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + common.RouteViewCounter.Bump(55) err = routeIps(w,req,user) } if err != nil { router.handleError(err,w,req,user) } case "/uploads": - if extra_data == "" { + if extraData == "" { common.NotFound(w,req) return } - req.URL.Path += extra_data + req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? - router.UploadHandler(w,req) + 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(extra_data) { + switch(extraData) { case "robots.txt": - err = routeRobotsTxt(w,req) + err = routeRobotsTxt(w,req) // TODO: Count these views if err != nil { router.handleError(err,w,req,user) } return } - if extra_data != "" { + if extraData != "" { common.NotFound(w,req) return } - common.Config.DefaultRoute(w,req,user) + 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.extra_routes[req.URL.Path] + handle, ok := router.extraRoutes[req.URL.Path] router.RUnlock() if ok { - req.URL.Path += extra_data - err = handle(w,req,user) + 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) + 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 } } diff --git a/main.go b/main.go index 30465373..e697270c 100644 --- a/main.go +++ b/main.go @@ -80,7 +80,11 @@ func afterDBInit() (err error) { if err != nil { return err } - common.GlobalViewCounter, err = common.NewGlobalViewCounter() + common.GlobalViewCounter, err = common.NewChunkedViewCounter() + if err != nil { + return err + } + common.RouteViewCounter, err = common.NewRouteViewCounter() if err != nil { return err } diff --git a/misc_test.go b/misc_test.go index e1bcfa91..4f158b6a 100644 --- a/misc_test.go +++ b/misc_test.go @@ -478,6 +478,28 @@ func TestPermsMiddleware(t *testing.T) { expect(t, ferr == nil, "Logged in users should be able to access member areas") // TODO: Loop over the /user/ routes and make sure only members can access the ones other than /user/username + + // TODO: Write tests for AdminOnly() + + user = common.BlankUser() + + ferr = common.SuperAdminOnly(dummyResponseRecorder, dummyRequest, *user) + expect(t, ferr != nil, "Blank users shouldn't be considered super admins") + + user.IsSuperAdmin = false + ferr = common.SuperAdminOnly(dummyResponseRecorder, dummyRequest, *user) + expect(t, ferr != nil, "Non-super admins shouldn't be allowed through the super admin gate") + + user.IsSuperAdmin = true + ferr = common.SuperAdminOnly(dummyResponseRecorder, dummyRequest, *user) + expect(t, ferr == nil, "Super admins should be allowed through super admin gates") + + // TODO: Make sure only super admins can access the backups route + + //dummyResponseRecorder = httptest.NewRecorder() + //bytesBuffer = bytes.NewBuffer([]byte("")) + //dummyRequest = httptest.NewRequest("", "/panel/backups/", bytesBuffer) + } func TestTopicStore(t *testing.T) { diff --git a/panel_routes.go b/panel_routes.go index b2730270..b1107681 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -1569,9 +1569,6 @@ func routePanelBackups(w http.ResponseWriter, r *http.Request, user common.User, if ferr != nil { return ferr } - if !user.IsSuperAdmin { - return common.NoPermissions(w, r, user) - } if backupURL != "" { // We don't want them trying to break out of this directory, it shouldn't hurt since it's a super admin, but it's always good to practice good security hygiene, especially if this is one of many instances on a managed server not controlled by the superadmin/s diff --git a/query_gen/tables.go b/query_gen/tables.go index 555a7237..4393129a 100644 --- a/query_gen/tables.go +++ b/query_gen/tables.go @@ -159,6 +159,7 @@ func createTables(adapter qgen.Adapter) error { qgen.DBTableColumn{"postCount", "int", 0, false, false, "1"}, qgen.DBTableColumn{"likeCount", "int", 0, false, false, "0"}, qgen.DBTableColumn{"words", "int", 0, false, false, "0"}, + //qgen.DBTableColumn{"views", "int", 0, false, false, "0"}, qgen.DBTableColumn{"css_class", "varchar", 100, false, false, "''"}, qgen.DBTableColumn{"data", "varchar", 200, false, false, "''"}, }, @@ -358,10 +359,22 @@ func createTables(adapter qgen.Adapter) error { []qgen.DBTableColumn{ qgen.DBTableColumn{"count", "int", 0, false, false, "0"}, qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""}, + qgen.DBTableColumn{"route", "varchar", 200, false, false, ""}, }, []qgen.DBTableKey{}, ) + /* + qgen.Install.CreateTable("viewchunks_forums", "", "", + []qgen.DBTableColumn{ + qgen.DBTableColumn{"count", "int", 0, false, false, "0"}, + qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""}, + qgen.DBTableColumn{"forum", "int", 0, false, false, ""}, + }, + []qgen.DBTableKey{}, + ) + */ + qgen.Install.CreateTable("sync", "", "", []qgen.DBTableColumn{ qgen.DBTableColumn{"last_update", "datetime", 0, false, false, ""}, diff --git a/router_gen/main.go b/router_gen/main.go index fb0d644d..7c732ce5 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -2,25 +2,40 @@ package main import ( + "bytes" "log" "os" + "strconv" + "text/template" ) -//import "strings" - 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 out, routeMap string + var tmplVars = TmplVars{ + RouteList: routeList, + RouteGroups: routeGroups, + } + var allRouteNames []string + var allRouteMap = make(map[string]int) + + var out string var mapIt = func(name string) { - routeMap += "\t\"" + name + "\": " + name + ",\n" - //reverseRouteMap += "\t" + name + ": \"" + name + "\",\n" + allRouteNames = append(allRouteNames, name) + allRouteMap[name] = len(allRouteNames) - 1 } var countToIndents = func(indent int) (indentor string) { for i := 0; i < indent; i++ { @@ -47,9 +62,11 @@ func main() { } 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 @@ -58,7 +75,6 @@ func main() { if err != nil { router.handleError(err,w,req,user) }` - mapIt(route.Name) } for _, group := range routeGroups { @@ -73,6 +89,7 @@ func main() { defaultRoute = route continue } + mapIt(route.Name) out += "\n\t\t\t\tcase \"" + route.Path + "\":" if len(route.RunBefore) > 0 { @@ -107,23 +124,24 @@ func main() { } } } + 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 += ")" - mapIt(route.Name) } 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 += ")" - mapIt(defaultRoute.Name) } out += ` } @@ -132,6 +150,9 @@ func main() { }` } + 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 @@ -147,12 +168,28 @@ import ( ) var ErrNoRoute = errors.New("That route doesn't exist.") -var RouteMap = map[string]interface{}{ -` + routeMap + `} +// 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) - extra_routes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError + extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError sync.RWMutex } @@ -160,7 +197,7 @@ type GenRouter struct { func NewGenRouter(uploads http.Handler) *GenRouter { return &GenRouter{ UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP, - extra_routes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError), + extraRoutes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError), } } @@ -182,17 +219,17 @@ 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.extra_routes[pattern] = handle + router.extraRoutes[pattern] = handle } func (router *GenRouter) RemoveFunc(pattern string) error { router.Lock() defer router.Unlock() - _, ok := router.extra_routes[pattern] + _, ok := router.extraRoutes[pattern] if !ok { return ErrNoRoute } - delete(router.extra_routes, pattern) + delete(router.extraRoutes, pattern) return nil } @@ -203,10 +240,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - var prefix, extra_data string + 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] != '/' { - extra_data = req.URL.Path[strings.LastIndexByte(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] } @@ -214,12 +251,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("before routeStatic") log.Print("prefix: ", prefix) log.Print("req.URL.Path: ", req.URL.Path) - log.Print("extra_data: ", extra_data) + log.Print("extraData: ", extraData) log.Print("req.Referer(): ", req.Referer()) } if prefix == "/static" { - req.URL.Path += extra_data + req.URL.Path += extraData routeStatic(w, req) return } @@ -237,54 +274,62 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } if common.Dev.SuperDebug { log.Print("after PreRoute") + log.Print("routeMapEnum: ", routeMapEnum) } var err common.RouteError switch(prefix) {` + out + ` case "/uploads": - if extra_data == "" { + if extraData == "" { common.NotFound(w,req) return } - req.URL.Path += extra_data + req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? - router.UploadHandler(w,req) + 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(extra_data) { + switch(extraData) { case "robots.txt": - err = routeRobotsTxt(w,req) + err = routeRobotsTxt(w,req) // TODO: Count these views if err != nil { router.handleError(err,w,req,user) } return } - if extra_data != "" { + if extraData != "" { common.NotFound(w,req) return } - common.Config.DefaultRoute(w,req,user) + 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.extra_routes[req.URL.Path] + handle, ok := router.extraRoutes[req.URL.Path] router.RUnlock() if ok { - req.URL.Path += extra_data - err = handle(w,req,user) + 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) + 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 } } ` - writeFile("./gen_router.go", fileData) + 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") } diff --git a/router_gen/routes.go b/router_gen/routes.go index 5a93ebbc..e29d3b4e 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -4,23 +4,23 @@ package main func routes() { addRoute(View("routeAPI", "/api/")) addRoute(View("routeOverview", "/overview/")) - //addRoute("routeCustomPage","/pages/",""/*,"&extra_data"*/) + //addRoute("routeCustomPage","/pages/",""/*,"&extraData"*/) addRoute(View("routeForums", "/forums/" /*,"&forums"*/)) - addRoute(View("routeForum", "/forum/", "extra_data")) + addRoute(View("routeForum", "/forum/", "extraData")) addRoute(AnonAction("routeChangeTheme", "/theme/")) addRoute( - View("routeShowAttachment", "/attachs/", "extra_data").Before("ParseForm"), + View("routeShowAttachment", "/attachs/", "extraData").Before("ParseForm"), ) // TODO: Reduce the number of Befores. With a new method, perhaps? reportGroup := newRouteGroup("/report/", - Action("routeReportSubmit", "/report/submit/", "extra_data"), + Action("routeReportSubmit", "/report/submit/", "extraData"), ).Before("NoBanned") addRouteGroup(reportGroup) topicGroup := newRouteGroup("/topics/", View("routeTopics", "/topics/"), - MemberView("routeTopicCreate", "/topics/create/", "extra_data"), + MemberView("routeTopicCreate", "/topics/create/", "extraData"), ) addRouteGroup(topicGroup) @@ -32,7 +32,7 @@ func routes() { func buildUserRoutes() { userGroup := newRouteGroup("/user/") userGroup.Routes( - View("routeProfile", "/user/").LitBefore("req.URL.Path += extra_data"), + View("routeProfile", "/user/").LitBefore("req.URL.Path += extraData"), MemberView("routeAccountEditCritical", "/user/edit/critical/"), Action("routeAccountEditCriticalSubmit", "/user/edit/critical/submit/"), // TODO: Full test this MemberView("routeAccountEditAvatar", "/user/edit/avatar/"), @@ -40,7 +40,7 @@ func buildUserRoutes() { MemberView("routeAccountEditUsername", "/user/edit/username/"), Action("routeAccountEditUsernameSubmit", "/user/edit/username/submit/"), // TODO: Full test this MemberView("routeAccountEditEmail", "/user/edit/email/"), - Action("routeAccountEditEmailTokenSubmit", "/user/edit/token/", "extra_data"), + Action("routeAccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"), ) addRouteGroup(userGroup) @@ -50,7 +50,7 @@ func buildUserRoutes() { Action("routeBanSubmit", "/users/ban/submit/"), Action("routeUnban", "/users/unban/"), Action("routeActivate", "/users/activate/"), - MemberView("routeIps", "/users/ips/"), + MemberView("routeIps", "/users/ips/"), // TODO: .Perms("ViewIPs")? ) addRouteGroup(userGroup) } @@ -61,42 +61,42 @@ func buildPanelRoutes() { View("routePanel", "/panel/"), View("routePanelForums", "/panel/forums/"), Action("routePanelForumsCreateSubmit", "/panel/forums/create/"), - Action("routePanelForumsDelete", "/panel/forums/delete/", "extra_data"), - Action("routePanelForumsDeleteSubmit", "/panel/forums/delete/submit/", "extra_data"), - View("routePanelForumsEdit", "/panel/forums/edit/", "extra_data"), - Action("routePanelForumsEditSubmit", "/panel/forums/edit/submit/", "extra_data"), - Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extra_data"), + Action("routePanelForumsDelete", "/panel/forums/delete/", "extraData"), + Action("routePanelForumsDeleteSubmit", "/panel/forums/delete/submit/", "extraData"), + View("routePanelForumsEdit", "/panel/forums/edit/", "extraData"), + Action("routePanelForumsEditSubmit", "/panel/forums/edit/submit/", "extraData"), + Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"), View("routePanelSettings", "/panel/settings/"), - View("routePanelSettingEdit", "/panel/settings/edit/", "extra_data"), - Action("routePanelSettingEditSubmit", "/panel/settings/edit/submit/", "extra_data"), + View("routePanelSettingEdit", "/panel/settings/edit/", "extraData"), + Action("routePanelSettingEditSubmit", "/panel/settings/edit/submit/", "extraData"), View("routePanelWordFilters", "/panel/settings/word-filters/"), Action("routePanelWordFiltersCreate", "/panel/settings/word-filters/create/"), - View("routePanelWordFiltersEdit", "/panel/settings/word-filters/edit/", "extra_data"), - Action("routePanelWordFiltersEditSubmit", "/panel/settings/word-filters/edit/submit/", "extra_data"), - Action("routePanelWordFiltersDeleteSubmit", "/panel/settings/word-filters/delete/submit/", "extra_data"), + View("routePanelWordFiltersEdit", "/panel/settings/word-filters/edit/", "extraData"), + Action("routePanelWordFiltersEditSubmit", "/panel/settings/word-filters/edit/submit/", "extraData"), + Action("routePanelWordFiltersDeleteSubmit", "/panel/settings/word-filters/delete/submit/", "extraData"), View("routePanelThemes", "/panel/themes/"), - Action("routePanelThemesSetDefault", "/panel/themes/default/", "extra_data"), + Action("routePanelThemesSetDefault", "/panel/themes/default/", "extraData"), View("routePanelPlugins", "/panel/plugins/"), - Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extra_data"), - Action("routePanelPluginsDeactivate", "/panel/plugins/deactivate/", "extra_data"), - Action("routePanelPluginsInstall", "/panel/plugins/install/", "extra_data"), + Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extraData"), + Action("routePanelPluginsDeactivate", "/panel/plugins/deactivate/", "extraData"), + Action("routePanelPluginsInstall", "/panel/plugins/install/", "extraData"), View("routePanelUsers", "/panel/users/"), - View("routePanelUsersEdit", "/panel/users/edit/", "extra_data"), - Action("routePanelUsersEditSubmit", "/panel/users/edit/submit/", "extra_data"), + View("routePanelUsersEdit", "/panel/users/edit/", "extraData"), + Action("routePanelUsersEditSubmit", "/panel/users/edit/submit/", "extraData"), View("routePanelGroups", "/panel/groups/"), - View("routePanelGroupsEdit", "/panel/groups/edit/", "extra_data"), - View("routePanelGroupsEditPerms", "/panel/groups/edit/perms/", "extra_data"), - Action("routePanelGroupsEditSubmit", "/panel/groups/edit/submit/", "extra_data"), - Action("routePanelGroupsEditPermsSubmit", "/panel/groups/edit/perms/submit/", "extra_data"), + View("routePanelGroupsEdit", "/panel/groups/edit/", "extraData"), + View("routePanelGroupsEditPerms", "/panel/groups/edit/perms/", "extraData"), + Action("routePanelGroupsEditSubmit", "/panel/groups/edit/submit/", "extraData"), + Action("routePanelGroupsEditPermsSubmit", "/panel/groups/edit/perms/submit/", "extraData"), Action("routePanelGroupsCreateSubmit", "/panel/groups/create/"), - View("routePanelBackups", "/panel/backups/", "extra_data"), + View("routePanelBackups", "/panel/backups/", "extraData").Before("SuperAdminOnly"), // TODO: Test View("routePanelLogsMod", "/panel/logs/mod/"), View("routePanelDebug", "/panel/debug/").Before("AdminOnly"), ) diff --git a/schema/mssql/query_viewchunks.sql b/schema/mssql/query_viewchunks.sql index efff665a..c3ba8519 100644 --- a/schema/mssql/query_viewchunks.sql +++ b/schema/mssql/query_viewchunks.sql @@ -1,4 +1,5 @@ CREATE TABLE [viewchunks] ( [count] int DEFAULT 0 not null, - [createdAt] datetime not null + [createdAt] datetime not null, + [route] nvarchar (200) not null ); \ No newline at end of file diff --git a/schema/mysql/query_viewchunks.sql b/schema/mysql/query_viewchunks.sql index 803c612e..a0ebf7d7 100644 --- a/schema/mysql/query_viewchunks.sql +++ b/schema/mysql/query_viewchunks.sql @@ -1,4 +1,5 @@ CREATE TABLE `viewchunks` ( `count` int DEFAULT 0 not null, - `createdAt` datetime not null + `createdAt` datetime not null, + `route` varchar(200) not null ); \ No newline at end of file diff --git a/schema/pgsql/query_viewchunks.sql b/schema/pgsql/query_viewchunks.sql index 4065bcbc..5177bdc8 100644 --- a/schema/pgsql/query_viewchunks.sql +++ b/schema/pgsql/query_viewchunks.sql @@ -1,4 +1,5 @@ CREATE TABLE `viewchunks` ( `count` int DEFAULT 0 not null, - `createdAt` timestamp not null + `createdAt` timestamp not null, + `route` varchar (200) not null ); \ No newline at end of file diff --git a/template_forum.go b/template_forum.go index 3b784f23..b0551377 100644 --- a/template_forum.go +++ b/template_forum.go @@ -3,9 +3,9 @@ // Code generated by Gosora. More below: /* 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 "strconv" import "net/http" import "./common" +import "strconv" // nolint func init() { @@ -143,54 +143,58 @@ w.Write([]byte(item.Creator.Link)) w.Write(forum_30) w.Write([]byte(item.Creator.Avatar)) w.Write(forum_31) -w.Write([]byte(item.Link)) +w.Write([]byte(item.Creator.Avatar)) w.Write(forum_32) -w.Write([]byte(item.Title)) +w.Write([]byte(item.Link)) w.Write(forum_33) -w.Write([]byte(item.Creator.Link)) +w.Write([]byte(item.Title)) w.Write(forum_34) -w.Write([]byte(item.Creator.Name)) +w.Write([]byte(item.Creator.Link)) w.Write(forum_35) -if item.IsClosed { +w.Write([]byte(item.Creator.Name)) w.Write(forum_36) -} -if item.Sticky { +if item.IsClosed { w.Write(forum_37) } -w.Write(forum_38) -w.Write([]byte(strconv.Itoa(item.PostCount))) -w.Write(forum_39) -w.Write([]byte(strconv.Itoa(item.LikeCount))) -w.Write(forum_40) if item.Sticky { +w.Write(forum_38) +} +w.Write(forum_39) +w.Write([]byte(strconv.Itoa(item.PostCount))) +w.Write(forum_40) +w.Write([]byte(strconv.Itoa(item.LikeCount))) w.Write(forum_41) +if item.Sticky { +w.Write(forum_42) } else { if item.IsClosed { -w.Write(forum_42) -} -} w.Write(forum_43) -w.Write([]byte(item.LastUser.Link)) +} +} w.Write(forum_44) -w.Write([]byte(item.LastUser.Avatar)) -w.Write(forum_45) w.Write([]byte(item.LastUser.Link)) +w.Write(forum_45) +w.Write([]byte(item.LastUser.Avatar)) w.Write(forum_46) w.Write([]byte(item.LastUser.Name)) w.Write(forum_47) -w.Write([]byte(item.RelativeLastReplyAt)) +w.Write([]byte(item.LastUser.Link)) w.Write(forum_48) +w.Write([]byte(item.LastUser.Name)) +w.Write(forum_49) +w.Write([]byte(item.RelativeLastReplyAt)) +w.Write(forum_50) } } else { -w.Write(forum_49) -if tmpl_forum_vars.CurrentUser.Perms.CreateTopic { -w.Write(forum_50) -w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) w.Write(forum_51) -} +if tmpl_forum_vars.CurrentUser.Perms.CreateTopic { w.Write(forum_52) -} +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) w.Write(forum_53) +} +w.Write(forum_54) +} +w.Write(forum_55) w.Write(footer_0) w.Write([]byte(common.BuildWidget("footer",tmpl_forum_vars.Header))) w.Write(footer_1) diff --git a/template_list.go b/template_list.go index 5f29baee..7599f2fe 100644 --- a/template_list.go +++ b/template_list.go @@ -352,127 +352,139 @@ var topic_alt_29 = []byte(`
`) var topic_alt_30 = []byte(``) +var topic_alt_31 = []byte(`" class="action_button like_item add_like" aria-label="Like this post" data-action="like">`) var topic_alt_32 = []byte(`Edit`) +var topic_alt_33 = []byte(`" class="action_button open_edit" aria-label="Edit this post" data-action="edit">`) var topic_alt_34 = []byte(`Delete`) +var topic_alt_35 = []byte(`" class="action_button delete_item" aria-label="Delete this post" data-action="delete">`) var topic_alt_36 = []byte(`Unlock`) +var topic_alt_37 = []byte(`' class="action_button unlock_item" data-action="unlock">`) var topic_alt_38 = []byte(`Lock`) +var topic_alt_39 = []byte(`' class="action_button lock_item" data-action="lock">`) var topic_alt_40 = []byte(`Unpin`) +var topic_alt_41 = []byte(`' class="action_button unpin_item" data-action="unpin">`) var topic_alt_42 = []byte(`Pin`) -var topic_alt_44 = []byte(` +var topic_alt_43 = []byte(`' class="action_button pin_item" data-action="pin">`) +var topic_alt_44 = []byte(``) +var topic_alt_46 = []byte(` Report +var topic_alt_47 = []byte(`?session=`) +var topic_alt_48 = []byte(`&type=topic" class="action_button report_item" aria-label="Report this post" data-action="report"> + `) -var topic_alt_47 = []byte(` -
+var topic_alt_49 = []byte(` +
`) -var topic_alt_48 = []byte(``) -var topic_alt_50 = []byte(` - `) -var topic_alt_51 = []byte(` - `) -var topic_alt_52 = []byte(``) +var topic_alt_52 = []byte(``) var topic_alt_54 = []byte(` + `) +var topic_alt_55 = []byte(` + `) +var topic_alt_56 = []byte(``) +var topic_alt_58 = []byte(``) +var topic_alt_59 = []byte(`
`) -var topic_alt_55 = []byte(` +var topic_alt_60 = []byte(`
+var topic_alt_61 = []byte(`action_item`) +var topic_alt_62 = []byte(`">
 
+var topic_alt_63 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;"> 
+var topic_alt_64 = []byte(`" class="the_name" rel="author">`) +var topic_alt_65 = []byte(` `) -var topic_alt_61 = []byte(`
`) -var topic_alt_63 = []byte(`
`) -var topic_alt_65 = []byte(` +var topic_alt_66 = []byte(`
`) +var topic_alt_68 = []byte(`
`) +var topic_alt_70 = []byte(`
+var topic_alt_71 = []byte(`style="margin-left: 0px;"`) +var topic_alt_72 = []byte(`> `) -var topic_alt_68 = []byte(` +var topic_alt_73 = []byte(` `) -var topic_alt_69 = []byte(` +var topic_alt_74 = []byte(` `) -var topic_alt_70 = []byte(` +var topic_alt_75 = []byte(` `) -var topic_alt_71 = []byte(` +var topic_alt_76 = []byte(`
`) -var topic_alt_72 = []byte(`
+var topic_alt_77 = []byte(`
`) -var topic_alt_73 = []byte(``) -var topic_alt_75 = []byte(`Edit`) -var topic_alt_77 = []byte(`Delete`) -var topic_alt_79 = []byte(` +var topic_alt_78 = []byte(``) +var topic_alt_80 = []byte(``) +var topic_alt_82 = []byte(``) +var topic_alt_84 = []byte(``) +var topic_alt_86 = []byte(` Report +var topic_alt_87 = []byte(`?session=`) +var topic_alt_88 = []byte(`&type=reply" class="action_button report_item" aria-label="Report this post" data-action="report"> + `) -var topic_alt_82 = []byte(` -
- `) -var topic_alt_83 = []byte(``) -var topic_alt_85 = []byte(` - `) -var topic_alt_86 = []byte(` - `) -var topic_alt_87 = []byte(``) -var topic_alt_88 = []byte(``) var topic_alt_89 = []byte(` +
+ `) +var topic_alt_92 = []byte(``) +var topic_alt_94 = []byte(` + `) +var topic_alt_95 = []byte(` + `) +var topic_alt_96 = []byte(``) +var topic_alt_98 = []byte(``) +var topic_alt_99 = []byte(`
`) -var topic_alt_90 = []byte(` +var topic_alt_100 = []byte(`
`) -var topic_alt_91 = []byte(` +var topic_alt_101 = []byte(` `) -var topic_alt_92 = []byte(` +var topic_alt_102 = []byte(`
 
+var topic_alt_103 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;"> 
+var topic_alt_104 = []byte(`" class="the_name" rel="author">`) +var topic_alt_105 = []byte(` `) -var topic_alt_96 = []byte(`
`) -var topic_alt_98 = []byte(`
`) -var topic_alt_100 = []byte(` +var topic_alt_106 = []byte(`
`) +var topic_alt_108 = []byte(`
`) +var topic_alt_110 = []byte(`
+var topic_alt_111 = []byte(`' type="hidden" />
@@ -482,17 +494,17 @@ var topic_alt_101 = []byte(`' type="hidden" />
`) -var topic_alt_102 = []byte(` +var topic_alt_112 = []byte(`
`) -var topic_alt_103 = []byte(` +var topic_alt_113 = []byte(`
`) -var topic_alt_104 = []byte(` +var topic_alt_114 = []byte(` @@ -762,7 +774,7 @@ var topics_9 = []byte(` `) diff --git a/template_topic_alt.go b/template_topic_alt.go index a541eb14..2334f38d 100644 --- a/template_topic_alt.go +++ b/template_topic_alt.go @@ -3,9 +3,9 @@ // Code generated by Gosora. More below: /* 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 "net/http" import "./common" import "strconv" -import "net/http" // nolint func init() { @@ -176,132 +176,154 @@ w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) w.Write(topic_alt_43) } } -w.Write(topic_alt_44) -w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) -w.Write(topic_alt_45) -w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) -w.Write(topic_alt_46) -} -w.Write(topic_alt_47) -if tmpl_topic_alt_vars.Topic.LikeCount > 0 { -w.Write(topic_alt_48) -w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount))) -w.Write(topic_alt_49) -} -w.Write(topic_alt_50) -w.Write([]byte(tmpl_topic_alt_vars.Topic.RelativeCreatedAt)) -w.Write(topic_alt_51) if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { -w.Write(topic_alt_52) +w.Write(topic_alt_44) w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress)) +w.Write(topic_alt_45) +} +w.Write(topic_alt_46) +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) +w.Write(topic_alt_47) +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) +w.Write(topic_alt_48) +} +w.Write(topic_alt_49) +if tmpl_topic_alt_vars.Topic.LikeCount > 0 { +w.Write(topic_alt_50) +} +w.Write(topic_alt_51) +if tmpl_topic_alt_vars.Topic.LikeCount > 0 { +w.Write(topic_alt_52) +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount))) w.Write(topic_alt_53) } w.Write(topic_alt_54) +w.Write([]byte(tmpl_topic_alt_vars.Topic.RelativeCreatedAt)) +w.Write(topic_alt_55) +if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { +w.Write(topic_alt_56) +w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress)) +w.Write(topic_alt_57) +w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress)) +w.Write(topic_alt_58) +} +w.Write(topic_alt_59) if len(tmpl_topic_alt_vars.ItemList) != 0 { for _, item := range tmpl_topic_alt_vars.ItemList { -w.Write(topic_alt_55) -if item.ActionType != "" { -w.Write(topic_alt_56) -} -w.Write(topic_alt_57) -w.Write([]byte(item.Avatar)) -w.Write(topic_alt_58) -w.Write([]byte(item.UserLink)) -w.Write(topic_alt_59) -w.Write([]byte(item.CreatedByName)) w.Write(topic_alt_60) -if item.Tag != "" { +if item.ActionType != "" { w.Write(topic_alt_61) -w.Write([]byte(item.Tag)) +} w.Write(topic_alt_62) -} else { +w.Write([]byte(item.Avatar)) w.Write(topic_alt_63) -w.Write([]byte(strconv.Itoa(item.Level))) +w.Write([]byte(item.UserLink)) w.Write(topic_alt_64) -} +w.Write([]byte(item.CreatedByName)) w.Write(topic_alt_65) -if item.ActionType != "" { +if item.Tag != "" { w.Write(topic_alt_66) -} +w.Write([]byte(item.Tag)) w.Write(topic_alt_67) -if item.ActionType != "" { -w.Write(topic_alt_68) -w.Write([]byte(item.ActionIcon)) -w.Write(topic_alt_69) -w.Write([]byte(item.ActionType)) -w.Write(topic_alt_70) } else { +w.Write(topic_alt_68) +w.Write([]byte(strconv.Itoa(item.Level))) +w.Write(topic_alt_69) +} +w.Write(topic_alt_70) +if item.ActionType != "" { w.Write(topic_alt_71) -w.Write([]byte(item.ContentHtml)) +} w.Write(topic_alt_72) +if item.ActionType != "" { +w.Write(topic_alt_73) +w.Write([]byte(item.ActionIcon)) +w.Write(topic_alt_74) +w.Write([]byte(item.ActionType)) +w.Write(topic_alt_75) +} else { +w.Write(topic_alt_76) +w.Write([]byte(item.ContentHtml)) +w.Write(topic_alt_77) if tmpl_topic_alt_vars.CurrentUser.Loggedin { if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem { -w.Write(topic_alt_73) +w.Write(topic_alt_78) w.Write([]byte(strconv.Itoa(item.ID))) -w.Write(topic_alt_74) +w.Write(topic_alt_79) } if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply { -w.Write(topic_alt_75) -w.Write([]byte(strconv.Itoa(item.ID))) -w.Write(topic_alt_76) -} -if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply { -w.Write(topic_alt_77) -w.Write([]byte(strconv.Itoa(item.ID))) -w.Write(topic_alt_78) -} -w.Write(topic_alt_79) -w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_80) -w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) +w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_81) } +if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply { w.Write(topic_alt_82) -if item.LikeCount > 0 { +w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_83) -w.Write([]byte(strconv.Itoa(item.LikeCount))) -w.Write(topic_alt_84) } -w.Write(topic_alt_85) -w.Write([]byte(item.RelativeCreatedAt)) -w.Write(topic_alt_86) if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { -w.Write(topic_alt_87) +w.Write(topic_alt_84) w.Write([]byte(item.IPAddress)) +w.Write(topic_alt_85) +} +w.Write(topic_alt_86) +w.Write([]byte(strconv.Itoa(item.ID))) +w.Write(topic_alt_87) +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) w.Write(topic_alt_88) } w.Write(topic_alt_89) -} +if item.LikeCount > 0 { w.Write(topic_alt_90) } -} w.Write(topic_alt_91) -if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply { +if item.LikeCount > 0 { w.Write(topic_alt_92) -w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Avatar)) +w.Write([]byte(strconv.Itoa(item.LikeCount))) w.Write(topic_alt_93) -w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link)) +} w.Write(topic_alt_94) -w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Name)) +w.Write([]byte(item.RelativeCreatedAt)) w.Write(topic_alt_95) -if tmpl_topic_alt_vars.CurrentUser.Tag != "" { +if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { w.Write(topic_alt_96) -w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Tag)) +w.Write([]byte(item.IPAddress)) w.Write(topic_alt_97) -} else { +w.Write([]byte(item.IPAddress)) w.Write(topic_alt_98) -w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.CurrentUser.Level))) +} w.Write(topic_alt_99) } w.Write(topic_alt_100) -w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) +} +} w.Write(topic_alt_101) -if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles { +if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply { w.Write(topic_alt_102) -} +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Avatar)) w.Write(topic_alt_103) -} +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link)) w.Write(topic_alt_104) +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Name)) +w.Write(topic_alt_105) +if tmpl_topic_alt_vars.CurrentUser.Tag != "" { +w.Write(topic_alt_106) +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Tag)) +w.Write(topic_alt_107) +} else { +w.Write(topic_alt_108) +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.CurrentUser.Level))) +w.Write(topic_alt_109) +} +w.Write(topic_alt_110) +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) +w.Write(topic_alt_111) +if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles { +w.Write(topic_alt_112) +} +w.Write(topic_alt_113) +} +w.Write(topic_alt_114) w.Write(footer_0) w.Write([]byte(common.BuildWidget("footer",tmpl_topic_alt_vars.Header))) w.Write(footer_1) diff --git a/template_topics.go b/template_topics.go index 2f092150..78a5e736 100644 --- a/template_topics.go +++ b/template_topics.go @@ -133,60 +133,64 @@ w.Write([]byte(item.Creator.Link)) w.Write(topics_25) w.Write([]byte(item.Creator.Avatar)) w.Write(topics_26) -w.Write([]byte(item.Link)) -w.Write(topics_27) -w.Write([]byte(item.Title)) -w.Write(topics_28) -if item.ForumName != "" { -w.Write(topics_29) -w.Write([]byte(item.ForumLink)) -w.Write(topics_30) -w.Write([]byte(item.ForumName)) -w.Write(topics_31) -} -w.Write(topics_32) -w.Write([]byte(item.Creator.Link)) -w.Write(topics_33) w.Write([]byte(item.Creator.Name)) -w.Write(topics_34) -if item.IsClosed { -w.Write(topics_35) +w.Write(topics_27) +w.Write([]byte(item.Link)) +w.Write(topics_28) +w.Write([]byte(item.Title)) +w.Write(topics_29) +if item.ForumName != "" { +w.Write(topics_30) +w.Write([]byte(item.ForumLink)) +w.Write(topics_31) +w.Write([]byte(item.ForumName)) +w.Write(topics_32) } -if item.Sticky { +w.Write(topics_33) +w.Write([]byte(item.Creator.Link)) +w.Write(topics_34) +w.Write([]byte(item.Creator.Name)) +w.Write(topics_35) +if item.IsClosed { w.Write(topics_36) } -w.Write(topics_37) -w.Write([]byte(strconv.Itoa(item.PostCount))) -w.Write(topics_38) -w.Write([]byte(strconv.Itoa(item.LikeCount))) -w.Write(topics_39) if item.Sticky { +w.Write(topics_37) +} +w.Write(topics_38) +w.Write([]byte(strconv.Itoa(item.PostCount))) +w.Write(topics_39) +w.Write([]byte(strconv.Itoa(item.LikeCount))) w.Write(topics_40) +if item.Sticky { +w.Write(topics_41) } else { if item.IsClosed { -w.Write(topics_41) -} -} w.Write(topics_42) -w.Write([]byte(item.LastUser.Link)) +} +} w.Write(topics_43) -w.Write([]byte(item.LastUser.Avatar)) -w.Write(topics_44) w.Write([]byte(item.LastUser.Link)) +w.Write(topics_44) +w.Write([]byte(item.LastUser.Avatar)) w.Write(topics_45) w.Write([]byte(item.LastUser.Name)) w.Write(topics_46) -w.Write([]byte(item.RelativeLastReplyAt)) +w.Write([]byte(item.LastUser.Link)) w.Write(topics_47) -} -} else { +w.Write([]byte(item.LastUser.Name)) w.Write(topics_48) -if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { +w.Write([]byte(item.RelativeLastReplyAt)) w.Write(topics_49) } +} else { w.Write(topics_50) -} +if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { w.Write(topics_51) +} +w.Write(topics_52) +} +w.Write(topics_53) w.Write(footer_0) w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header))) w.Write(footer_1) diff --git a/templates/forum.html b/templates/forum.html index 9c67eb49..1bc671ec 100644 --- a/templates/forum.html +++ b/templates/forum.html @@ -39,7 +39,7 @@ {{if .CurrentUser.Perms.CreateTopic}}