From 20bb909c546da70372fac9858c2b4eba6b794e37 Mon Sep 17 00:00:00 2001 From: Azareal Date: Fri, 10 Nov 2017 03:33:11 +0000 Subject: [PATCH] Moving the files into subpackages 1/? Moved more routes into the router generator. Renamed a few of the routes. Refactored the router generator. Fixed PreRoute. Began work on letting plugins plug into the template transpiler. --- common/common.go | 6 + errors.go => common/errors.go | 25 +- extend.go => common/extend.go | 24 +- forum.go => common/forum.go | 2 +- forum_store.go => common/forum_store.go | 4 +- group.go => common/group.go | 2 +- common/pages.go | 286 +++++++++ permissions.go => common/permissions.go | 4 +- pluginlangs.go => common/pluginlangs.go | 2 +- .../profile_reply_store.go | 4 +- reply.go => common/reply.go | 2 +- reply_store.go => common/reply_store.go | 4 +- setting.go => common/setting.go | 16 +- site.go => common/site.go | 2 +- template_init.go => common/template_init.go | 22 +- themes.go => common/themes.go | 7 +- topic.go => common/topic.go | 2 +- topic_store.go => common/topic_store.go | 4 +- user.go => common/user.go | 7 +- user_store.go => common/user_store.go | 3 +- utils.go => common/utils.go | 2 +- extend/guilds/lib/guilds.go | 536 ++++++++++++++++ gen_router.go | 60 +- main.go | 58 -- member_routes.go | 32 +- mod_routes.go | 9 - pages.go | 279 +-------- plugin_guilds.go | 582 ++---------------- query_gen/main.go | 2 - router_gen/route_group.go | 15 +- router_gen/route_impl.go | 15 +- router_gen/route_subset.go | 19 +- router_gen/routes.go | 30 +- routes.go | 10 +- routes_common.go | 81 ++- 35 files changed, 1108 insertions(+), 1050 deletions(-) create mode 100644 common/common.go rename errors.go => common/errors.go (91%) rename extend.go => common/extend.go (95%) rename forum.go => common/forum.go (99%) rename forum_store.go => common/forum_store.go (99%) rename group.go => common/group.go (99%) create mode 100644 common/pages.go rename permissions.go => common/permissions.go (99%) rename pluginlangs.go => common/pluginlangs.go (99%) rename profile_reply_store.go => common/profile_reply_store.go (97%) rename reply.go => common/reply.go (99%) rename reply_store.go => common/reply_store.go (97%) rename setting.go => common/setting.go (84%) rename site.go => common/site.go (99%) rename template_init.go => common/template_init.go (94%) rename themes.go => common/themes.go (99%) rename topic.go => common/topic.go (99%) rename topic_store.go => common/topic_store.go (99%) rename user.go => common/user.go (98%) rename user_store.go => common/user_store.go (99%) rename utils.go => common/utils.go (99%) create mode 100644 extend/guilds/lib/guilds.go diff --git a/common/common.go b/common/common.go new file mode 100644 index 00000000..5fa8e013 --- /dev/null +++ b/common/common.go @@ -0,0 +1,6 @@ +package common + +import "database/sql" + +// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores +var ErrNoRows = sql.ErrNoRows diff --git a/errors.go b/common/errors.go similarity index 91% rename from errors.go rename to common/errors.go index 1c902db7..c29744a1 100644 --- a/errors.go +++ b/common/errors.go @@ -1,4 +1,4 @@ -package main +package common import "log" @@ -14,6 +14,9 @@ var errorBuffer []error //var notfoundCountPerSecond int //var nopermsCountPerSecond int +// A blank list to fill out that parameter in Page for routes which don't use it +var tList []interface{} + // WIP, a new system to propagate errors up from routes type RouteError interface { Type() string @@ -82,7 +85,7 @@ func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError // TODO: Centralise the user struct somewhere else user := User{0, "guest", "Guest", "", 0, false, false, false, false, false, false, GuestPerms, nil, "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0} - pi := Page{"Internal Server Error", user, getDefaultHeaderVar(), tList, "A problem has occurred in the system."} + pi := Page{"Internal Server Error", user, DefaultHeaderVar(), tList, "A problem has occurred in the system."} err = templates.ExecuteTemplate(w, "error.html", pi) if err != nil { log.Print(err) @@ -120,7 +123,7 @@ func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteErr // LoginRequired is an error shown to the end-user when they try to access an area which requires them to login func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(401) - pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You need to login to do that."} + pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."} if preRenderHooks["pre_render_error"] != nil { if runPreRenderHook("pre_render_error", w, r, &user, &pi) { return nil @@ -136,7 +139,7 @@ func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError { w.WriteHeader(500) user := User{ID: 0, Group: 6, Perms: GuestPerms} - pi := Page{"Error", user, getDefaultHeaderVar(), tList, errmsg} + pi := Page{"Error", user, DefaultHeaderVar(), tList, errmsg} if preRenderHooks["pre_render_error"] != nil { if runPreRenderHook("pre_render_error", w, r, &user, &pi) { return nil @@ -165,7 +168,7 @@ func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs boo // LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(500) - pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, errmsg} + pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg} if preRenderHooks["pre_render_error"] != nil { if runPreRenderHook("pre_render_error", w, r, &user, &pi) { return nil @@ -195,7 +198,7 @@ func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteEr // NoPermissions is an error shown to the end-user when they try to access an area which they aren't authorised to access func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(403) - pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You don't have permission to do that."} + pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You don't have permission to do that."} // TODO: What to do about this hook? if preRenderHooks["pre_render_error"] != nil { if runPreRenderHook("pre_render_error", w, r, &user, &pi) { @@ -225,7 +228,7 @@ func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) RouteErr // ? - Is this actually used? Should it be used? A ban in Gosora should be more of a permission revocation to stop them posting rather than something which spits up an error page, right? func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(403) - pi := Page{"Banned", user, getDefaultHeaderVar(), tList, "You have been banned from this site."} + pi := Page{"Banned", user, DefaultHeaderVar(), tList, "You have been banned from this site."} if preRenderHooks["pre_render_error"] != nil { if runPreRenderHook("pre_render_error", w, r, &user, &pi) { return nil @@ -257,7 +260,7 @@ func BannedJS(w http.ResponseWriter, r *http.Request, user User) RouteError { func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError { w.WriteHeader(401) if !isJs { - pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You need to login to do that."} + pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."} if preRenderHooks["pre_render_error"] != nil { if runPreRenderHook("pre_render_error", w, r, &user, &pi) { return nil @@ -277,7 +280,7 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bo // ? - Should we add JS and JSQ versions of this? func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(403) - pi := Page{"Security Error", user, getDefaultHeaderVar(), tList, "There was a security issue with your request."} + pi := Page{"Security Error", user, DefaultHeaderVar(), tList, "There was a security issue with your request."} if preRenderHooks["pre_render_security_error"] != nil { if runPreRenderHook("pre_render_security_error", w, r, &user, &pi) { return nil @@ -297,7 +300,7 @@ func NotFound(w http.ResponseWriter, r *http.Request) RouteError { w.WriteHeader(404) // TODO: Centralise the user struct somewhere else user := User{0, "guest", "Guest", "", 0, false, false, false, false, false, false, GuestPerms, nil, "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0} - pi := Page{"Not Found", user, getDefaultHeaderVar(), tList, "The requested page doesn't exist."} + pi := Page{"Not Found", user, DefaultHeaderVar(), tList, "The requested page doesn't exist."} err := templates.ExecuteTemplate(w, "error.html", pi) if err != nil { LogError(err) @@ -308,7 +311,7 @@ func NotFound(w http.ResponseWriter, r *http.Request) RouteError { // CustomError lets us make custom error types which aren't covered by the generic functions above func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) RouteError { w.WriteHeader(errcode) - pi := Page{errtitle, user, getDefaultHeaderVar(), tList, errmsg} + pi := Page{errtitle, user, DefaultHeaderVar(), tList, errmsg} if preRenderHooks["pre_render_error"] != nil { if runPreRenderHook("pre_render_error", w, r, &user, &pi) { return nil diff --git a/extend.go b/common/extend.go similarity index 95% rename from extend.go rename to common/extend.go index 269b44f7..3016631e 100644 --- a/extend.go +++ b/common/extend.go @@ -4,14 +4,18 @@ * Copyright Azareal 2016 - 2018 * */ -package main +package common import ( "log" "net/http" + + "../query_gen/lib" ) -var plugins = make(map[string]*Plugin) +type PluginList map[string]*Plugin + +var Plugins PluginList = make(map[string]*Plugin) // Hooks with a single argument. Is this redundant? Might be useful for inlining, as variadics aren't inlined? Are closures even inlined to begin with? var hooks = map[string][]func(interface{}) interface{}{ @@ -138,12 +142,16 @@ func initExtend() (err error) { if err != nil { return err } - return LoadPlugins() + return Plugins.Load() } // LoadPlugins polls the database to see which plugins have been activated and which have been installed -func LoadPlugins() error { - rows, err := stmts.getPlugins.Query() +func (plugins PluginList) Load() error { + getPlugins, err := qgen.Builder.SimpleSelect("plugins", "uname, active, installed", "", "", "") + if err != nil { + return err + } + rows, err := getPlugins.Query() if err != nil { return err } @@ -278,12 +286,12 @@ func (plugin *Plugin) RemoveHook(name string, handler interface{}) { var pluginsInited = false func initPlugins() { - for name, body := range plugins { + for name, body := range Plugins { log.Print("Added plugin " + name) if body.Active { log.Print("Initialised plugin " + name) - if plugins[name].Init != nil { - err := plugins[name].Init() + if Plugins[name].Init != nil { + err := Plugins[name].Init() if err != nil { log.Print(err) } diff --git a/forum.go b/common/forum.go similarity index 99% rename from forum.go rename to common/forum.go index 023365f5..1630ffd2 100644 --- a/forum.go +++ b/common/forum.go @@ -1,4 +1,4 @@ -package main +package common //import "fmt" import ( diff --git a/forum_store.go b/common/forum_store.go similarity index 99% rename from forum_store.go rename to common/forum_store.go index 59c09680..105376bf 100644 --- a/forum_store.go +++ b/common/forum_store.go @@ -4,7 +4,7 @@ * Copyright Azareal 2017 - 2018 * */ -package main +package common import ( "database/sql" @@ -14,7 +14,7 @@ import ( "sync" "sync/atomic" - "./query_gen/lib" + "../query_gen/lib" ) var forumUpdateMutex sync.Mutex diff --git a/group.go b/common/group.go similarity index 99% rename from group.go rename to common/group.go index 88712c82..b6c91366 100644 --- a/group.go +++ b/common/group.go @@ -1,4 +1,4 @@ -package main +package common var blankGroup = Group{ID: 0, Name: ""} diff --git a/common/pages.go b/common/pages.go new file mode 100644 index 00000000..cbac4a63 --- /dev/null +++ b/common/pages.go @@ -0,0 +1,286 @@ +package common + +import ( + "html/template" + "sync" + "time" +) + +type HeaderVars struct { + NoticeList []string + Scripts []string + Stylesheets []string + Widgets PageWidgets + Site *Site + Settings map[string]interface{} + Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed + ThemeName string + //TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over? + ExtData ExtData +} + +// TODO: Add this to routes which don't use templates. E.g. Json APIs. +type HeaderLite struct { + Site *Site + Settings SettingMap + ExtData ExtData +} + +type PageWidgets struct { + LeftSidebar template.HTML + RightSidebar template.HTML +} + +// TODO: Add a ExtDataHolder interface with methods for manipulating the contents? +// ? - Could we use a sync.Map instead? +type ExtData struct { + items map[string]interface{} // Key: pluginname + sync.RWMutex +} + +type Page struct { + Title string + CurrentUser User + Header *HeaderVars + ItemList []interface{} + Something interface{} +} + +type TopicPage struct { + Title string + CurrentUser User + Header *HeaderVars + ItemList []ReplyUser + Topic TopicUser + Page int + LastPage int +} + +type TopicsPage struct { + Title string + CurrentUser User + Header *HeaderVars + TopicList []*TopicsRow + ForumList []Forum + DefaultForum int +} + +type ForumPage struct { + Title string + CurrentUser User + Header *HeaderVars + ItemList []*TopicsRow + Forum *Forum + Page int + LastPage int +} + +type ForumsPage struct { + Title string + CurrentUser User + Header *HeaderVars + ItemList []Forum +} + +type ProfilePage struct { + Title string + CurrentUser User + Header *HeaderVars + ItemList []ReplyUser + ProfileOwner User +} + +type CreateTopicPage struct { + Title string + CurrentUser User + Header *HeaderVars + ItemList []Forum + FID int +} + +type IPSearchPage struct { + Title string + CurrentUser User + Header *HeaderVars + ItemList map[int]*User + IP string +} + +type PanelStats struct { + Users int + Groups int + Forums int + Settings int + WordFilters int + Themes int + Reports int +} + +type PanelPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + ItemList []interface{} + Something interface{} +} + +type GridElement struct { + ID string + Body string + Order int // For future use + Class string + Background string + TextColour string + Note string +} + +type PanelDashboardPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + GridItems []GridElement +} + +type PanelThemesPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + PrimaryThemes []Theme + VariantThemes []Theme +} + +type PanelUserPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + ItemList []User + PageList []int + Page int + LastPage int +} + +type PanelGroupPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + ItemList []GroupAdmin + PageList []int + Page int + LastPage int +} + +type PanelEditGroupPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + ID int + Name string + Tag string + Rank string + DisableRank bool +} + +type GroupForumPermPreset struct { + Group *Group + Preset string +} + +type PanelEditForumPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + ID int + Name string + Desc string + Active bool + Preset string + Groups []GroupForumPermPreset +} + +/*type NameLangPair struct { + Name string + LangStr string +}*/ + +type NameLangToggle struct { + Name string + LangStr string + Toggle bool +} + +type PanelEditGroupPermsPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + ID int + Name string + LocalPerms []NameLangToggle + GlobalPerms []NameLangToggle +} + +type backupItem struct { + SQLURL string + + // TODO: Add an easier to parse format here for Gosora to be able to more easily reimport portions of the dump and to strip unnecessary data (e.g. table defs and parsed post data) + + Timestamp time.Time +} + +type PanelBackupPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + Backups []backupItem +} + +type logItem struct { + Action template.HTML + IPAddress string + DoneAt string +} + +type PanelLogsPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + Logs []logItem + PageList []int + Page int + LastPage int +} + +type PanelDebugPage struct { + Title string + CurrentUser User + Header *HeaderVars + Stats PanelStats + Uptime string + OpenConns int + DBAdapter string +} + +type PageSimple struct { + Title string + Something interface{} +} + +type AreYouSure struct { + URL string + Message string +} + +// This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible +// TODO: Write a test for this +func DefaultHeaderVar() *HeaderVars { + return &HeaderVars{Site: site, ThemeName: fallbackTheme} +} diff --git a/permissions.go b/common/permissions.go similarity index 99% rename from permissions.go rename to common/permissions.go index b78fcb10..a2132439 100644 --- a/permissions.go +++ b/common/permissions.go @@ -1,12 +1,10 @@ -package main +package common import ( "database/sql" "encoding/json" "log" "sync" - - "./query_gen/lib" ) // TODO: Refactor the perms system diff --git a/pluginlangs.go b/common/pluginlangs.go similarity index 99% rename from pluginlangs.go rename to common/pluginlangs.go index 1b1eea12..58ef5422 100644 --- a/pluginlangs.go +++ b/common/pluginlangs.go @@ -1,4 +1,4 @@ -package main +package common import ( "encoding/json" diff --git a/profile_reply_store.go b/common/profile_reply_store.go similarity index 97% rename from profile_reply_store.go rename to common/profile_reply_store.go index cfb861af..382accc3 100644 --- a/profile_reply_store.go +++ b/common/profile_reply_store.go @@ -1,9 +1,7 @@ -package main +package common import ( "database/sql" - - "./query_gen/lib" ) var prstore ProfileReplyStore diff --git a/reply.go b/common/reply.go similarity index 99% rename from reply.go rename to common/reply.go index 744a726a..00b76e23 100644 --- a/reply.go +++ b/common/reply.go @@ -4,7 +4,7 @@ * Copyright Azareal 2016 - 2018 * */ -package main +package common import ( "errors" diff --git a/reply_store.go b/common/reply_store.go similarity index 97% rename from reply_store.go rename to common/reply_store.go index e054b0f2..bc3692c0 100644 --- a/reply_store.go +++ b/common/reply_store.go @@ -1,7 +1,7 @@ -package main +package common import "database/sql" -import "./query_gen/lib" +import "../query_gen/lib" var rstore ReplyStore diff --git a/setting.go b/common/setting.go similarity index 84% rename from setting.go rename to common/setting.go index d11f2848..8f061425 100644 --- a/setting.go +++ b/common/setting.go @@ -1,13 +1,13 @@ -package main +package common import "strconv" import "strings" import "sync/atomic" -// SettingBox is a map type specifically for holding the various settings admins set to toggle features on and off or to otherwise alter Gosora's behaviour from the Control Panel -type SettingBox map[string]interface{} +// SettingMap is a map type specifically for holding the various settings admins set to toggle features on and off or to otherwise alter Gosora's behaviour from the Control Panel +type SettingMap map[string]interface{} -var settingBox atomic.Value // An atomic value pointing to a SettingBox +var SettingBox atomic.Value // An atomic value pointing to a SettingBox type OptionLabel struct { Label string @@ -23,7 +23,7 @@ type Setting struct { } func init() { - settingBox.Store(SettingBox(make(map[string]interface{}))) + SettingBox.Store(SettingMap(make(map[string]interface{}))) } func LoadSettings() error { @@ -33,7 +33,7 @@ func LoadSettings() error { } defer rows.Close() - var sBox = SettingBox(make(map[string]interface{})) + var sBox = SettingMap(make(map[string]interface{})) var sname, scontent, stype, sconstraints string for rows.Next() { err = rows.Scan(&sname, &scontent, &stype, &sconstraints) @@ -50,12 +50,12 @@ func LoadSettings() error { return err } - settingBox.Store(sBox) + SettingBox.Store(sBox) return nil } // TODO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions. -func (sBox SettingBox) ParseSetting(sname string, scontent string, stype string, constraint string) string { +func (sBox SettingMap) ParseSetting(sname string, scontent string, stype string, constraint string) string { var err error var ssBox = map[string]interface{}(sBox) if stype == "bool" { diff --git a/site.go b/common/site.go similarity index 99% rename from site.go rename to common/site.go index 0c91be38..9d176011 100644 --- a/site.go +++ b/common/site.go @@ -1,4 +1,4 @@ -package main +package common import ( "errors" diff --git a/template_init.go b/common/template_init.go similarity index 94% rename from template_init.go rename to common/template_init.go index 7fa9cbe9..ea272d94 100644 --- a/template_init.go +++ b/common/template_init.go @@ -1,4 +1,4 @@ -package main +package common import ( "html/template" @@ -8,6 +8,15 @@ import ( ) var templates = template.New("") +var prebuildTmplList []func(*User, *HeaderVars) CTmpl + +type CTmpl struct { + Name string + Filename string + Path string + StructName string + Data interface{} +} // nolint func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) error { @@ -150,6 +159,17 @@ func compileTemplates() error { return err } + // Let plugins register their own templates + for _, tmplfunc := range prebuildTmplList { + tmpl := tmplfunc(user, headerVars) + varList = make(map[string]VarItem) + compiledTmpl, err := c.compileTemplate(tmpl.Filename, tmpl.Path, tmpl.StructName, tmpl.Data, varList) + if err != nil { + return err + } + go writeTemplate(tmpl.Name, compiledTmpl) + } + log.Print("Writing the templates") go writeTemplate("topic", topicIDTmpl) go writeTemplate("topic_alt", topicIDAltTmpl) diff --git a/themes.go b/common/themes.go similarity index 99% rename from themes.go rename to common/themes.go index 343ace3a..fbe7b744 100644 --- a/themes.go +++ b/common/themes.go @@ -1,5 +1,5 @@ /* Copyright Azareal 2016 - 2018 */ -package main +package common import ( //"fmt" @@ -19,11 +19,10 @@ import ( "text/template" ) -//var themes = make(map[string]Theme) type ThemeList map[string]Theme -var themes ThemeList = make(map[string]Theme) -var defaultThemeBox atomic.Value +//var themes ThemeList = make(map[string]Theme) +var DefaultThemeBox atomic.Value var changeDefaultThemeMutex sync.Mutex // TODO: Use this when the default theme doesn't exist diff --git a/topic.go b/common/topic.go similarity index 99% rename from topic.go rename to common/topic.go index 1bd2953f..b8df45d6 100644 --- a/topic.go +++ b/common/topic.go @@ -4,7 +4,7 @@ * Copyright Azareal 2017 - 2018 * */ -package main +package common //import "fmt" import ( diff --git a/topic_store.go b/common/topic_store.go similarity index 99% rename from topic_store.go rename to common/topic_store.go index 5022a8b2..fbb165c9 100644 --- a/topic_store.go +++ b/common/topic_store.go @@ -4,7 +4,7 @@ * Copyright Azareal 2017 - 2018 * */ -package main +package common import ( "database/sql" @@ -13,7 +13,7 @@ import ( "sync" "sync/atomic" - "./query_gen/lib" + "../query_gen/lib" ) // TODO: Add the watchdog goroutine diff --git a/user.go b/common/user.go similarity index 98% rename from user.go rename to common/user.go index 27ce19f4..01f0a4d8 100644 --- a/user.go +++ b/common/user.go @@ -4,7 +4,7 @@ * Copyright Azareal 2017 - 2018 * */ -package main +package common import ( //"log" @@ -15,14 +15,13 @@ import ( "strings" "time" - "./query_gen/lib" "golang.org/x/crypto/bcrypt" ) // TODO: Replace any literals with this -var banGroup = 4 +var BanGroup = 4 -var guestUser = User{ID: 0, Link: "#", Group: 6, Perms: GuestPerms} +var GuestUser = User{ID: 0, Link: "#", Group: 6, Perms: GuestPerms} //func(real_password string, password string, salt string) (err error) var CheckPassword = BcryptCheckPassword diff --git a/user_store.go b/common/user_store.go similarity index 99% rename from user_store.go rename to common/user_store.go index 9f65ae5f..1ab8f4f1 100644 --- a/user_store.go +++ b/common/user_store.go @@ -1,4 +1,4 @@ -package main +package common import ( "database/sql" @@ -8,7 +8,6 @@ import ( "sync" "sync/atomic" - "./query_gen/lib" "golang.org/x/crypto/bcrypt" ) diff --git a/utils.go b/common/utils.go similarity index 99% rename from utils.go rename to common/utils.go index ff74be1c..0b237c45 100644 --- a/utils.go +++ b/common/utils.go @@ -4,7 +4,7 @@ * Copyright Azareal 2017 - 2018 * */ -package main +package common import ( "crypto/rand" diff --git a/extend/guilds/lib/guilds.go b/extend/guilds/lib/guilds.go new file mode 100644 index 00000000..69c2a467 --- /dev/null +++ b/extend/guilds/lib/guilds.go @@ -0,0 +1,536 @@ +package guilds + +import ( + "bytes" + "context" + "database/sql" + "errors" + "html" + "html/template" + "net/http" + "strconv" + "strings" + + "../../../common" +) + +var ListStmt *sql.Stmt +var MemberListStmt *sql.Stmt +var MemberListJoinStmt *sql.Stmt +var GetMemberStmt *sql.Stmt +var GetGuildStmt *sql.Stmt +var CreateGuildStmt *sql.Stmt +var AttachForumStmt *sql.Stmt +var UnattachForumStmt *sql.Stmt +var AddMemberStmt *sql.Stmt + +// Guild is a struct representing a guild +type Guild struct { + ID int + Link string + Name string + Desc string + Active bool + Privacy int /* 0: Public, 1: Protected, 2: Private */ + + // Who should be able to accept applications and create invites? Mods+ or just admins? Mods is a good start, we can ponder over whether we should make this more flexible in the future. + Joinable int /* 0: Private, 1: Anyone can join, 2: Applications, 3: Invite-only */ + + MemberCount int + Owner int + Backdrop string + CreatedAt string + LastUpdateTime string + + MainForumID int + MainForum *common.Forum + Forums []*common.Forum + ExtData ExtData +} + +type Page struct { + Title string + CurrentUser User + Header *common.HeaderVars + ItemList []*TopicsRow + Forum *commmon.Forum + Guild *Guild + Page int + LastPage int +} + +// ListPage is a page struct for constructing a list of every guild +type ListPage struct { + Title string + CurrentUser User + Header *HeaderVars + GuildList []*Guild +} + +type MemberListPage struct { + Title string + CurrentUser User + Header *HeaderVars + ItemList []GuildMember + Guild *Guild + Page int + LastPage int +} + +// Member is a struct representing a specific member of a guild, not to be confused with the global User struct. +type Member struct { + Link string + Rank int /* 0: Member. 1: Mod. 2: Admin. */ + RankString string /* Member, Mod, Admin, Owner */ + PostCount int + JoinedAt string + Offline bool // TODO: Need to track the online states of members when WebSockets are enabled + + User User +} + +func PrebuildTmplList(user *User, headerVars *HeaderVars) CTmpl { + var guildList = []*Guild{ + &Guild{ + ID: 1, + Name: "lol", + Link: guildsBuildGuildURL(nameToSlug("lol"), 1), + Desc: "A group for people who like to laugh", + Active: true, + MemberCount: 1, + Owner: 1, + CreatedAt: "date", + LastUpdateTime: "date", + MainForumID: 1, + MainForum: fstore.DirtyGet(1), + Forums: []*Forum{fstore.DirtyGet(1)}, + }, + } + listPage := ListPage{"Guild List", user, headerVars, guildList} + return CTmpl{"guilds-guild-list", "guilds_guild_list", "templates/", "guilds.ListPage", listPage} +} + +// TODO: Do this properly via the widget system +func CommonAreaWidgets(headerVars *HeaderVars) { + // TODO: Hot Groups? Featured Groups? Official Groups? + var b bytes.Buffer + var menu = WidgetMenu{"Guilds", []WidgetMenuItem{ + WidgetMenuItem{"Create Guild", "/guild/create/", false}, + }} + + err := templates.ExecuteTemplate(&b, "widget_menu.html", menu) + if err != nil { + LogError(err) + return + } + + if themes[headerVars.ThemeName].Sidebars == "left" { + headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) + } else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" { + headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) + } +} + +// TODO: Do this properly via the widget system +// TODO: Make a better more customisable group widget system +func GuildWidgets(headerVars *HeaderVars, guildItem *Guild) (success bool) { + return false // Disabled until the next commit + + /*var b bytes.Buffer + var menu WidgetMenu = WidgetMenu{"Guild Options", []WidgetMenuItem{ + WidgetMenuItem{"Join", "/guild/join/" + strconv.Itoa(guildItem.ID), false}, + WidgetMenuItem{"Members", "/guild/members/" + strconv.Itoa(guildItem.ID), false}, + }} + + err := templates.ExecuteTemplate(&b, "widget_menu.html", menu) + if err != nil { + LogError(err) + return false + } + + if themes[headerVars.ThemeName].Sidebars == "left" { + headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) + } else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" { + headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) + } else { + return false + } + return true*/ +} + +/* + Custom Pages +*/ + +func routeGuildList(w http.ResponseWriter, r *http.Request, user User) RouteError { + headerVars, ferr := UserCheck(w, r, &user) + if ferr != nil { + return ferr + } + guildsCommonAreaWidgets(headerVars) + + rows, err := guildsListStmt.Query() + if err != nil && err != ErrNoRows { + return InternalError(err, w, r) + } + defer rows.Close() + + var guildList []*Guild + for rows.Next() { + guildItem := &Guild{ID: 0} + err := rows.Scan(&guildItem.ID, &guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.CreatedAt, &guildItem.LastUpdateTime) + if err != nil { + return InternalError(err, w, r) + } + guildItem.Link = guildsBuildGuildURL(nameToSlug(guildItem.Name), guildItem.ID) + guildList = append(guildList, guildItem) + } + err = rows.Err() + if err != nil { + return InternalError(err, w, r) + } + + pi := GuildListPage{"Guild List", user, headerVars, guildList} + err = RunThemeTemplate(headerVars.ThemeName, "guilds_guild_list", pi, w) + if err != nil { + return InternalError(err, w, r) + } + return nil +} + +func GetGuild(guildID int) (guildItem *Guild, err error) { + guildItem = &Guild{ID: guildID} + err = guildsGetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.MainForumID, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime) + return guildItem, err +} + +func middleViewGuild(w http.ResponseWriter, r *http.Request, user User) RouteError { + // SEO URLs... + halves := strings.Split(r.URL.Path[len("/guild/"):], ".") + if len(halves) < 2 { + halves = append(halves, halves[0]) + } + guildID, err := strconv.Atoi(halves[1]) + if err != nil { + return PreError("Not a valid guild ID", w, r) + } + + guildItem, err := guildsGetGuild(guildID) + if err != nil { + return LocalError("Bad guild", w, r, user) + } + if !guildItem.Active { + return NotFound(w, r) + } + + // Re-route the request to routeForums + var ctx = context.WithValue(r.Context(), "guilds_current_guild", guildItem) + return routeForum(w, r.WithContext(ctx), user, strconv.Itoa(guildItem.MainForumID)) +} + +func CreateGuild(w http.ResponseWriter, r *http.Request, user User) RouteError { + headerVars, ferr := UserCheck(w, r, &user) + if ferr != nil { + return ferr + } + // TODO: Add an approval queue mode for group creation + if !user.Loggedin || !user.PluginPerms["CreateGuild"] { + return NoPermissions(w, r, user) + } + guildsCommonAreaWidgets(headerVars) + + pi := Page{"Create Guild", user, headerVars, tList, nil} + err := templates.ExecuteTemplate(w, "guilds_create_guild.html", pi) + if err != nil { + return InternalError(err, w, r) + } + return nil +} + +func CreateGuildSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError { + // TODO: Add an approval queue mode for group creation + if !user.Loggedin || !user.PluginPerms["CreateGuild"] { + return NoPermissions(w, r, user) + } + + var guildActive = true + var guildName = html.EscapeString(r.PostFormValue("group_name")) + var guildDesc = html.EscapeString(r.PostFormValue("group_desc")) + var gprivacy = r.PostFormValue("group_privacy") + + var guildPrivacy int + switch gprivacy { + case "0": + guildPrivacy = 0 // Public + case "1": + guildPrivacy = 1 // Protected + case "2": + guildPrivacy = 2 // private + default: + guildPrivacy = 0 + } + + // Create the backing forum + fid, err := fstore.Create(guildName, "", true, "") + if err != nil { + return InternalError(err, w, r) + } + + res, err := guildsCreateGuildStmt.Exec(guildName, guildDesc, guildActive, guildPrivacy, user.ID, fid) + if err != nil { + return InternalError(err, w, r) + } + lastID, err := res.LastInsertId() + if err != nil { + return InternalError(err, w, r) + } + + // Add the main backing forum to the forum list + err = guildsAttachForum(int(lastID), fid) + if err != nil { + return InternalError(err, w, r) + } + + _, err = guildsAddMemberStmt.Exec(lastID, user.ID, 2) + if err != nil { + return InternalError(err, w, r) + } + + http.Redirect(w, r, guildsBuildGuildURL(nameToSlug(guildName), int(lastID)), http.StatusSeeOther) + return nil +} + +func MemberList(w http.ResponseWriter, r *http.Request, user User) RouteError { + headerVars, ferr := UserCheck(w, r, &user) + if ferr != nil { + return ferr + } + + // SEO URLs... + halves := strings.Split(r.URL.Path[len("/guild/members/"):], ".") + if len(halves) < 2 { + halves = append(halves, halves[0]) + } + guildID, err := strconv.Atoi(halves[1]) + if err != nil { + return PreError("Not a valid group ID", w, r) + } + + var guildItem = &Guild{ID: guildID} + var mainForum int // Unused + err = guildsGetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &mainForum, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime) + if err != nil { + return LocalError("Bad group", w, r, user) + } + guildItem.Link = guildsBuildGuildURL(nameToSlug(guildItem.Name), guildItem.ID) + + guildsGuildWidgets(headerVars, guildItem) + + rows, err := guildsMemberListJoinStmt.Query(guildID) + if err != nil && err != ErrNoRows { + return InternalError(err, w, r) + } + + var guildMembers []GuildMember + for rows.Next() { + guildMember := GuildMember{PostCount: 0} + err := rows.Scan(&guildMember.User.ID, &guildMember.Rank, &guildMember.PostCount, &guildMember.JoinedAt, &guildMember.User.Name, &guildMember.User.Avatar) + if err != nil { + return InternalError(err, w, r) + } + guildMember.Link = buildProfileURL(nameToSlug(guildMember.User.Name), guildMember.User.ID) + if guildMember.User.Avatar != "" { + if guildMember.User.Avatar[0] == '.' { + guildMember.User.Avatar = "/uploads/avatar_" + strconv.Itoa(guildMember.User.ID) + guildMember.User.Avatar + } + } else { + guildMember.User.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(guildMember.User.ID), 1) + } + guildMember.JoinedAt, _ = relativeTimeFromString(guildMember.JoinedAt) + if guildItem.Owner == guildMember.User.ID { + guildMember.RankString = "Owner" + } else { + switch guildMember.Rank { + case 0: + guildMember.RankString = "Member" + case 1: + guildMember.RankString = "Mod" + case 2: + guildMember.RankString = "Admin" + } + } + guildMembers = append(guildMembers, guildMember) + } + err = rows.Err() + if err != nil { + return InternalError(err, w, r) + } + rows.Close() + + pi := GuildMemberListPage{"Guild Member List", user, headerVars, guildMembers, guildItem, 0, 0} + // A plugin with plugins. Pluginception! + if preRenderHooks["pre_render_guilds_member_list"] != nil { + if runPreRenderHook("pre_render_guilds_member_list", w, r, &user, &pi) { + return nil + } + } + err = RunThemeTemplate(headerVars.ThemeName, "guilds_member_list", pi, w) + if err != nil { + return InternalError(err, w, r) + } + return nil +} + +func AttachForum(guildID int, fid int) error { + _, err := guildsAttachForumStmt.Exec(guildID, fid) + return err +} + +func UnattachForum(fid int) error { + _, err := guildsAttachForumStmt.Exec(fid) + return err +} + +func BuildGuildURL(slug string, id int) string { + if slug == "" { + return "/guild/" + slug + "." + strconv.Itoa(id) + } + return "/guild/" + strconv.Itoa(id) +} + +/* + Hooks +*/ + +// TODO: Prebuild this template +func PreRenderViewForum(w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) { + pi := data.(*ForumPage) + if pi.Header.ExtData.items != nil { + if guildData, ok := pi.Header.ExtData.items["guilds_current_group"]; ok { + guildItem := guildData.(*Guild) + + guildpi := GuildPage{pi.Title, pi.CurrentUser, pi.Header, pi.ItemList, pi.Forum, guildItem, pi.Page, pi.LastPage} + err := templates.ExecuteTemplate(w, "guilds_view_guild.html", guildpi) + if err != nil { + LogError(err) + return false + } + return true + } + } + return false +} + +func TrowAssign(args ...interface{}) interface{} { + var forum = args[1].(*Forum) + if forum.ParentType == "guild" { + var topicItem = args[0].(*TopicsRow) + topicItem.ForumLink = "/guild/" + strings.TrimPrefix(topicItem.ForumLink, getForumURLPrefix()) + } + return nil +} + +// TODO: It would be nice, if you could select one of the boards in the group from that drop-down rather than just the one you got linked from +func TopicCreatePreLoop(args ...interface{}) interface{} { + var fid = args[2].(int) + if fstore.DirtyGet(fid).ParentType == "guild" { + var strictmode = args[5].(*bool) + *strictmode = true + } + return nil +} + +// TODO: Add privacy options +// TODO: Add support for multiple boards and add per-board simplified permissions +// TODO: Take isJs into account for routes which expect JSON responses +func ForumCheck(args ...interface{}) (skip bool, rerr RouteError) { + var r = args[1].(*http.Request) + var fid = args[3].(*int) + var forum = fstore.DirtyGet(*fid) + + if forum.ParentType == "guild" { + var err error + var w = args[0].(http.ResponseWriter) + guildItem, ok := r.Context().Value("guilds_current_group").(*Guild) + if !ok { + guildItem, err = guildsGetGuild(forum.ParentID) + if err != nil { + return true, InternalError(errors.New("Unable to find the parent group for a forum"), w, r) + } + if !guildItem.Active { + return true, NotFound(w, r) + } + r = r.WithContext(context.WithValue(r.Context(), "guilds_current_group", guildItem)) + } + + var user = args[2].(*User) + var rank int + var posts int + var joinedAt string + + // TODO: Group privacy settings. For now, groups are all globally visible + + // Clear the default group permissions + // TODO: Do this more efficiently, doing it quick and dirty for now to get this out quickly + overrideForumPerms(&user.Perms, false) + user.Perms.ViewTopic = true + + err = guildsGetMemberStmt.QueryRow(guildItem.ID, user.ID).Scan(&rank, &posts, &joinedAt) + if err != nil && err != ErrNoRows { + return true, InternalError(err, w, r) + } else if err != nil { + // TODO: Should we let admins / guests into public groups? + return true, LocalError("You're not part of this group!", w, r, *user) + } + + // TODO: Implement bans properly by adding the Local Ban API in the next commit + // TODO: How does this even work? Refactor it along with the rest of this plugin! + if rank < 0 { + return true, LocalError("You've been banned from this group!", w, r, *user) + } + + // Basic permissions for members, more complicated permissions coming in the next commit! + if guildItem.Owner == user.ID { + overrideForumPerms(&user.Perms, true) + } else if rank == 0 { + user.Perms.LikeItem = true + user.Perms.CreateTopic = true + user.Perms.CreateReply = true + } else { + overrideForumPerms(&user.Perms, true) + } + return true, nil + } + + return false, nil +} + +// TODO: Override redirects? I don't think this is needed quite yet + +func Widgets(args ...interface{}) interface{} { + var zone = args[0].(string) + var headerVars = args[2].(*HeaderVars) + var request = args[3].(*http.Request) + + if zone != "view_forum" { + return false + } + + var forum = args[1].(*Forum) + if forum.ParentType == "guild" { + // This is why I hate using contexts, all the daisy chains and interface casts x.x + guildItem, ok := request.Context().Value("guilds_current_group").(*Guild) + if !ok { + LogError(errors.New("Unable to find a parent group in the context data")) + return false + } + + if headerVars.ExtData.items == nil { + headerVars.ExtData.items = make(map[string]interface{}) + } + headerVars.ExtData.items["guilds_current_group"] = guildItem + + return guildsGuildWidgets(headerVars, guildItem) + } + return false +} diff --git a/gen_router.go b/gen_router.go index 1e0de7be..4cafcdd0 100644 --- a/gen_router.go +++ b/gen_router.go @@ -266,15 +266,21 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeAccountOwnEditCritical(w,req,user) + err = routeAccountEditCritical(w,req,user) case "/user/edit/critical/submit/": + err = NoSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + err = MemberOnly(w,req,user) if err != nil { router.handleError(err,w,req,user) return } - err = routeAccountOwnEditCriticalSubmit(w,req,user) + err = routeAccountEditCriticalSubmit(w,req,user) case "/user/edit/avatar/": err = MemberOnly(w,req,user) if err != nil { @@ -282,7 +288,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeAccountOwnEditAvatar(w,req,user) + err = routeAccountEditAvatar(w,req,user) case "/user/edit/avatar/submit/": err = MemberOnly(w,req,user) if err != nil { @@ -290,7 +296,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeAccountOwnEditAvatarSubmit(w,req,user) + err = routeAccountEditAvatarSubmit(w,req,user) case "/user/edit/username/": err = MemberOnly(w,req,user) if err != nil { @@ -298,7 +304,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeAccountOwnEditUsername(w,req,user) + err = routeAccountEditUsername(w,req,user) case "/user/edit/username/submit/": err = MemberOnly(w,req,user) if err != nil { @@ -306,7 +312,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeAccountOwnEditUsernameSubmit(w,req,user) + err = routeAccountEditUsernameSubmit(w,req,user) case "/user/edit/email/": err = MemberOnly(w,req,user) if err != nil { @@ -314,7 +320,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeAccountOwnEditEmail(w,req,user) + err = routeAccountEditEmail(w,req,user) case "/user/edit/token/": err = MemberOnly(w,req,user) if err != nil { @@ -322,7 +328,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err = routeAccountOwnEditEmailTokenSubmit(w,req,user,extra_data) + err = routeAccountEditEmailTokenSubmit(w,req,user,extra_data) default: req.URL.Path += extra_data err = routeProfile(w,req,user) @@ -330,6 +336,44 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if err != nil { router.handleError(err,w,req,user) } + case "/users": + err = MemberOnly(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + switch(req.URL.Path) { + case "/users/ban/submit/": + err = NoSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + err = routeBanSubmit(w,req,user) + case "/users/unban/": + err = NoSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + err = routeUnban(w,req,user) + case "/users/activate/": + err = NoSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + + err = routeActivate(w,req,user) + case "/users/ips/": + err = routeIps(w,req,user) + } + if err != nil { + router.handleError(err,w,req,user) + } case "/uploads": if extra_data == "" { NotFound(w,req) diff --git a/main.go b/main.go index 4453a2ec..627869d4 100644 --- a/main.go +++ b/main.go @@ -228,12 +228,6 @@ func main() { log.Print("Initialising the router") router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) - ////router.HandleFunc("/static/", routeStatic) - ////router.HandleFunc("/overview/", routeOverview) - ////router.HandleFunc("/topics/create/", routeTopicCreate) - ////router.HandleFunc("/topics/", routeTopics) - ////router.HandleFunc("/forums/", routeForums) - ////router.HandleFunc("/forum/", routeForum) router.HandleFunc("/topic/create/submit/", routeTopicCreateSubmit) router.HandleFunc("/topic/", routeTopicID) router.HandleFunc("/reply/create/", routeCreateReply) @@ -242,7 +236,6 @@ func main() { router.HandleFunc("/reply/edit/submit/", routeReplyEditSubmit) router.HandleFunc("/reply/delete/submit/", routeReplyDeleteSubmit) router.HandleFunc("/reply/like/submit/", routeReplyLikeSubmit) - ///router.HandleFunc("/report/submit/", route_report_submit) router.HandleFunc("/topic/edit/submit/", routeEditTopic) router.HandleFunc("/topic/delete/submit/", routeDeleteTopic) router.HandleFunc("/topic/stick/submit/", routeStickTopic) @@ -260,65 +253,14 @@ func main() { router.HandleFunc("/accounts/logout/", routeLogout) router.HandleFunc("/accounts/login/submit/", routeLoginSubmit) router.HandleFunc("/accounts/create/submit/", routeRegisterSubmit) - //router.HandleFunc("/accounts/list/", routeLogin) // Redirect /accounts/ and /user/ to here.. // Get a list of all of the accounts on the forum - //router.HandleFunc("/accounts/create/full/", routeLogout) // Advanced account creator for admins? - //router.HandleFunc("/user/edit/", routeLogout) - ////router.HandleFunc("/user/edit/critical/", routeAccountOwnEditCritical) // Password & Email - ////router.HandleFunc("/user/edit/critical/submit/", routeAccountOwnEditCriticalSubmit) - ////router.HandleFunc("/user/edit/avatar/", routeAccountOwnEditAvatar) - ////router.HandleFunc("/user/edit/avatar/submit/", routeAccountOwnEditAvatarSubmit) - ////router.HandleFunc("/user/edit/username/", routeAccountOwnEditUsername) - ////router.HandleFunc("/user/edit/username/submit/", routeAccountOwnEditUsernameSubmit) - ////router.HandleFunc("/user/edit/email/", routeAccountOwnEditEmail) - ////router.HandleFunc("/user/edit/token/", routeAccountOwnEditEmailTokenSubmit) - ////router.HandleFunc("/user/", routeProfile) // TODO: Move these into /user/? router.HandleFunc("/profile/reply/create/", routeProfileReplyCreate) router.HandleFunc("/profile/reply/edit/submit/", routeProfileReplyEditSubmit) router.HandleFunc("/profile/reply/delete/submit/", routeProfileReplyDeleteSubmit) - //router.HandleFunc("/user/edit/submit/", routeLogout) // routeLogout? what on earth? o.o - //router.HandleFunc("/users/ban/", routeBan) - router.HandleFunc("/users/ban/submit/", routeBanSubmit) - router.HandleFunc("/users/unban/", routeUnban) - router.HandleFunc("/users/activate/", routeActivate) - router.HandleFunc("/users/ips/", routeIps) - - // The Control Panel - // TODO: Rename the commented route handlers to the new camelCase format :'( - ////router.HandleFunc("/panel/", routePanel) - ////router.HandleFunc("/panel/forums/", routePanelForums) - ////router.HandleFunc("/panel/forums/create/", routePanelForumsCreateSubmit) - ////router.HandleFunc("/panel/forums/delete/", routePanelForumsDelete) - ////router.HandleFunc("/panel/forums/delete/submit/", routePanelForumsDeleteSubmit) - ////router.HandleFunc("/panel/forums/edit/", routePanelForumsEdit) - ////router.HandleFunc("/panel/forums/edit/submit/", routePanelForumsEditSubmit) - ////router.HandleFunc("/panel/forums/edit/perms/submit/", routePanelForumsEditPermsSubmit) - ////router.HandleFunc("/panel/settings/", routePanelSettings) - ////router.HandleFunc("/panel/settings/edit/", routePanelSetting) - ////router.HandleFunc("/panel/settings/edit/submit/", routePanelSettingEdit) - ///router.HandleFunc("/panel/themes/", route_panel_themes) - ///router.HandleFunc("/panel/themes/default/", route_panel_themes_default) - ///router.HandleFunc("/panel/plugins/", route_panel_plugins) - ///router.HandleFunc("/panel/plugins/activate/", route_panel_plugins_activate) - ///router.HandleFunc("/panel/plugins/deactivate/", route_panel_plugins_deactivate) - ///router.HandleFunc("/panel/users/", route_panel_users) - ///router.HandleFunc("/panel/users/edit/", route_panel_users_edit) - ///router.HandleFunc("/panel/users/edit/submit/", route_panel_users_edit_submit) - ///router.HandleFunc("/panel/groups/", route_panel_groups) - ///router.HandleFunc("/panel/groups/edit/", route_panel_groups_edit) - ///router.HandleFunc("/panel/groups/edit/perms/", route_panel_groups_edit_perms) - ///router.HandleFunc("/panel/groups/edit/submit/", route_panel_groups_edit_submit) - ///router.HandleFunc("/panel/groups/edit/perms/submit/", route_panel_groups_edit_perms_submit) - ///router.HandleFunc("/panel/groups/create/", route_panel_groups_create_submit) - ///router.HandleFunc("/panel/logs/mod/", route_panel_logs_mod) - ///router.HandleFunc("/panel/debug/", route_panel_debug) - - ////router.HandleFunc("/api/", routeAPI) //router.HandleFunc("/exit/", routeExit) - ////router.HandleFunc("/", config.DefaultRoute) router.HandleFunc("/ws/", routeWebsockets) log.Print("Initialising the plugins") diff --git a/member_routes.go b/member_routes.go index d42c26be..b241d504 100644 --- a/member_routes.go +++ b/member_routes.go @@ -96,7 +96,7 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid st } } - err = template_create_topic_handle(ctpage, w) + err = RunThemeTemplate(headerVars.ThemeName, "create-topic", ctpage, w) if err != nil { return InternalError(err, w, r) } @@ -630,7 +630,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI return nil } -func routeAccountOwnEditCritical(w http.ResponseWriter, r *http.Request, user User) RouteError { +func routeAccountEditCritical(w http.ResponseWriter, r *http.Request, user User) RouteError { headerVars, ferr := UserCheck(w, r, &user) if ferr != nil { return ferr @@ -649,23 +649,18 @@ func routeAccountOwnEditCritical(w http.ResponseWriter, r *http.Request, user Us return nil } -func routeAccountOwnEditCriticalSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError { +func routeAccountEditCriticalSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError { headerVars, ferr := UserCheck(w, r, &user) if ferr != nil { return ferr } - err := r.ParseForm() - if err != nil { - return LocalError("Bad Form", w, r, user) - } - var realPassword, salt string currentPassword := r.PostFormValue("account-current-password") newPassword := r.PostFormValue("account-new-password") confirmPassword := r.PostFormValue("account-confirm-password") - err = stmts.getPassword.QueryRow(user.ID).Scan(&realPassword, &salt) + err := stmts.getPassword.QueryRow(user.ID).Scan(&realPassword, &salt) if err == ErrNoRows { return LocalError("Your account no longer exists.", w, r, user) } else if err != nil { @@ -700,7 +695,7 @@ func routeAccountOwnEditCriticalSubmit(w http.ResponseWriter, r *http.Request, u return nil } -func routeAccountOwnEditAvatar(w http.ResponseWriter, r *http.Request, user User) RouteError { +func routeAccountEditAvatar(w http.ResponseWriter, r *http.Request, user User) RouteError { headerVars, ferr := UserCheck(w, r, &user) if ferr != nil { return ferr @@ -719,7 +714,7 @@ func routeAccountOwnEditAvatar(w http.ResponseWriter, r *http.Request, user User return nil } -func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError { +func routeAccountEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError { if r.ContentLength > int64(config.MaxRequestSize) { size, unit := convertByteUnit(float64(config.MaxRequestSize)) return CustomError("Your avatar's too big. Avatars must be smaller than "+strconv.Itoa(int(size))+unit, http.StatusExpectationFailed, "Error", w, r, user) @@ -805,7 +800,7 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use return nil } -func routeAccountOwnEditUsername(w http.ResponseWriter, r *http.Request, user User) RouteError { +func routeAccountEditUsername(w http.ResponseWriter, r *http.Request, user User) RouteError { headerVars, ferr := UserCheck(w, r, &user) if ferr != nil { return ferr @@ -824,18 +819,14 @@ func routeAccountOwnEditUsername(w http.ResponseWriter, r *http.Request, user Us return nil } -func routeAccountOwnEditUsernameSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError { +func routeAccountEditUsernameSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError { headerVars, ferr := UserCheck(w, r, &user) if ferr != nil { return ferr } - err := r.ParseForm() - if err != nil { - return LocalError("Bad Form", w, r, user) - } newUsername := html.EscapeString(r.PostFormValue("account-new-username")) - err = user.ChangeName(newUsername) + err := user.ChangeName(newUsername) if err != nil { return LocalError("Unable to change the username. Does someone else already have this name?", w, r, user) } @@ -855,7 +846,7 @@ func routeAccountOwnEditUsernameSubmit(w http.ResponseWriter, r *http.Request, u return nil } -func routeAccountOwnEditEmail(w http.ResponseWriter, r *http.Request, user User) RouteError { +func routeAccountEditEmail(w http.ResponseWriter, r *http.Request, user User) RouteError { headerVars, ferr := UserCheck(w, r, &user) if ferr != nil { return ferr @@ -910,7 +901,8 @@ func routeAccountOwnEditEmail(w http.ResponseWriter, r *http.Request, user User) return nil } -func routeAccountOwnEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user User, token string) RouteError { +// TODO: Do a session check on this? +func routeAccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user User, token string) RouteError { headerVars, ferr := UserCheck(w, r, &user) if ferr != nil { return ferr diff --git a/mod_routes.go b/mod_routes.go index b3949467..a65ad49b 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -603,9 +603,6 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user User) RouteErro if !user.Perms.BanUsers { return NoPermissions(w, r, user) } - if r.FormValue("session") != user.Session { - return SecurityError(w, r, user) - } uid, err := strconv.Atoi(r.URL.Path[len("/users/ban/submit/"):]) if err != nil { @@ -679,9 +676,6 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user User) RouteError { if !user.Perms.BanUsers { return NoPermissions(w, r, user) } - if r.FormValue("session") != user.Session { - return SecurityError(w, r, user) - } uid, err := strconv.Atoi(r.URL.Path[len("/users/unban/"):]) if err != nil { @@ -721,9 +715,6 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user User) RouteError if !user.Perms.ActivateUsers { return NoPermissions(w, r, user) } - if r.FormValue("session") != user.Session { - return SecurityError(w, r, user) - } uid, err := strconv.Atoi(r.URL.Path[len("/users/activate/"):]) if err != nil { diff --git a/pages.go b/pages.go index 7498103e..481db7f4 100644 --- a/pages.go +++ b/pages.go @@ -3,287 +3,12 @@ package main import ( //"fmt" "bytes" - "html/template" "net/url" "regexp" "strconv" "strings" - "sync" - "time" ) -type HeaderVars struct { - NoticeList []string - Scripts []string - Stylesheets []string - Widgets PageWidgets - Site *Site - Settings map[string]interface{} - Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed - ThemeName string - ExtData ExtData -} - -// TODO: Add this to routes which don't use templates. E.g. Json APIs. -type HeaderLite struct { - Site *Site - Settings SettingBox - ExtData ExtData -} - -type PageWidgets struct { - LeftSidebar template.HTML - RightSidebar template.HTML -} - -// TODO: Add a ExtDataHolder interface with methods for manipulating the contents? -// ? - Could we use a sync.Map instead? -type ExtData struct { - items map[string]interface{} // Key: pluginname - sync.RWMutex -} - -type Page struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList []interface{} - Something interface{} -} - -type TopicPage struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList []ReplyUser - Topic TopicUser - Page int - LastPage int -} - -type TopicsPage struct { - Title string - CurrentUser User - Header *HeaderVars - TopicList []*TopicsRow - ForumList []Forum - DefaultForum int -} - -type ForumPage struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList []*TopicsRow - Forum *Forum - Page int - LastPage int -} - -type ForumsPage struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList []Forum -} - -type ProfilePage struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList []ReplyUser - ProfileOwner User -} - -type CreateTopicPage struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList []Forum - FID int -} - -type IPSearchPage struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList map[int]*User - IP string -} - -type PanelStats struct { - Users int - Groups int - Forums int - Settings int - WordFilters int - Themes int - Reports int -} - -type PanelPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - ItemList []interface{} - Something interface{} -} - -type GridElement struct { - ID string - Body string - Order int // For future use - Class string - Background string - TextColour string - Note string -} - -type PanelDashboardPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - GridItems []GridElement -} - -type PanelThemesPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - PrimaryThemes []Theme - VariantThemes []Theme -} - -type PanelUserPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - ItemList []User - PageList []int - Page int - LastPage int -} - -type PanelGroupPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - ItemList []GroupAdmin - PageList []int - Page int - LastPage int -} - -type PanelEditGroupPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - ID int - Name string - Tag string - Rank string - DisableRank bool -} - -type GroupForumPermPreset struct { - Group *Group - Preset string -} - -type PanelEditForumPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - ID int - Name string - Desc string - Active bool - Preset string - Groups []GroupForumPermPreset -} - -/*type NameLangPair struct { - Name string - LangStr string -}*/ - -type NameLangToggle struct { - Name string - LangStr string - Toggle bool -} - -type PanelEditGroupPermsPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - ID int - Name string - LocalPerms []NameLangToggle - GlobalPerms []NameLangToggle -} - -type backupItem struct { - SQLURL string - - // TODO: Add an easier to parse format here for Gosora to be able to more easily reimport portions of the dump and to strip unnecessary data (e.g. table defs and parsed post data) - - Timestamp time.Time -} - -type PanelBackupPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - Backups []backupItem -} - -type logItem struct { - Action template.HTML - IPAddress string - DoneAt string -} - -type PanelLogsPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - Logs []logItem - PageList []int - Page int - LastPage int -} - -type PanelDebugPage struct { - Title string - CurrentUser User - Header *HeaderVars - Stats PanelStats - Uptime string - OpenConns int - DBAdapter string -} - -type PageSimple struct { - Title string - Something interface{} -} - -type AreYouSure struct { - URL string - Message string -} - var spaceGap = []byte(" ") var httpProtBytes = []byte("http://") var invalidURL = []byte("[Invalid URL]") @@ -300,11 +25,11 @@ var urlClose = []byte("") var imageOpen = []byte("") -var urlpattern = `(?s)([ {1}])((http|https|ftp|mailto)*)(:{??)\/\/([\.a-zA-Z\/]+)([ {1}])` +var urlPattern = `(?s)([ {1}])((http|https|ftp|mailto)*)(:{??)\/\/([\.a-zA-Z\/]+)([ {1}])` var urlReg *regexp.Regexp func init() { - urlReg = regexp.MustCompile(urlpattern) + urlReg = regexp.MustCompile(urlPattern) } // TODO: Write a test for this diff --git a/plugin_guilds.go b/plugin_guilds.go index 83d244f7..d8ec5502 100644 --- a/plugin_guilds.go +++ b/plugin_guilds.go @@ -2,149 +2,69 @@ package main import ( //"fmt" - "bytes" - "context" - "database/sql" - "errors" - "html" - "html/template" - "net/http" - "strconv" - "strings" + "./extend/guilds/lib" "./query_gen/lib" ) -var guildsListStmt *sql.Stmt -var guildsMemberListStmt *sql.Stmt -var guildsMemberListJoinStmt *sql.Stmt -var guildsGetMemberStmt *sql.Stmt -var guildsGetGuildStmt *sql.Stmt -var guildsCreateGuildStmt *sql.Stmt -var guildsAttachForumStmt *sql.Stmt -var guildsUnattachForumStmt *sql.Stmt -var guildsAddMemberStmt *sql.Stmt - // TODO: Add a better way of splitting up giant plugins like this -// Guild is a struct representing a guild -type Guild struct { - ID int - Link string - Name string - Desc string - Active bool - Privacy int /* 0: Public, 1: Protected, 2: Private */ - - // Who should be able to accept applications and create invites? Mods+ or just admins? Mods is a good start, we can ponder over whether we should make this more flexible in the future. - Joinable int /* 0: Private, 1: Anyone can join, 2: Applications, 3: Invite-only */ - - MemberCount int - Owner int - Backdrop string - CreatedAt string - LastUpdateTime string - - MainForumID int - MainForum *Forum - Forums []*Forum - ExtData ExtData -} - -type GuildPage struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList []*TopicsRow - Forum *Forum - Guild *Guild - Page int - LastPage int -} - -// GuildListPage is a page struct for constructing a list of every guild -type GuildListPage struct { - Title string - CurrentUser User - Header *HeaderVars - GuildList []*Guild -} - -type GuildMemberListPage struct { - Title string - CurrentUser User - Header *HeaderVars - ItemList []GuildMember - Guild *Guild - Page int - LastPage int -} - -// GuildMember is a struct representing a specific member of a guild, not to be confused with the global User struct. -type GuildMember struct { - Link string - Rank int /* 0: Member. 1: Mod. 2: Admin. */ - RankString string /* Member, Mod, Admin, Owner */ - PostCount int - JoinedAt string - Offline bool // TODO: Need to track the online states of members when WebSockets are enabled - - User User -} - // TODO: Add a plugin interface instead of having a bunch of argument to AddPlugin? func init() { plugins["guilds"] = NewPlugin("guilds", "Guilds", "Azareal", "http://github.com/Azareal", "", "", "", initGuilds, nil, deactivateGuilds, installGuilds, nil) + + // TODO: Is it possible to avoid doing this when the plugin isn't activated? + prebuildTmplList = append(prebuildTmplList, guilds.PrebuildTmplList) } func initGuilds() (err error) { - plugins["guilds"].AddHook("intercept_build_widgets", guildsWidgets) - plugins["guilds"].AddHook("trow_assign", guildsTrowAssign) - plugins["guilds"].AddHook("topic_create_pre_loop", guildsTopicCreatePreLoop) - plugins["guilds"].AddHook("pre_render_view_forum", guildsPreRenderViewForum) - plugins["guilds"].AddHook("simple_forum_check_pre_perms", guildsForumCheck) - plugins["guilds"].AddHook("forum_check_pre_perms", guildsForumCheck) + plugins["guilds"].AddHook("intercept_build_widgets", guilds.Widgets) + plugins["guilds"].AddHook("trow_assign", guilds.TrowAssign) + plugins["guilds"].AddHook("topic_create_pre_loop", guilds.TopicCreatePreLoop) + plugins["guilds"].AddHook("pre_render_view_forum", guilds.PreRenderViewForum) + plugins["guilds"].AddHook("simple_forum_check_pre_perms", guilds.ForumCheck) + plugins["guilds"].AddHook("forum_check_pre_perms", guilds.ForumCheck) // TODO: Auto-grant this perm to admins upon installation? registerPluginPerm("CreateGuild") - router.HandleFunc("/guilds/", guildsGuildList) - router.HandleFunc("/guild/", guildsViewGuild) - router.HandleFunc("/guild/create/", guildsCreateGuild) - router.HandleFunc("/guild/create/submit/", guildsCreateGuildSubmit) - router.HandleFunc("/guild/members/", guildsMemberList) + router.HandleFunc("/guilds/", guilds.GuildList) + router.HandleFunc("/guild/", guilds.ViewGuild) + router.HandleFunc("/guild/create/", guilds.CreateGuild) + router.HandleFunc("/guild/create/submit/", guilds.CreateGuildSubmit) + router.HandleFunc("/guild/members/", guilds.MemberList) - guildsListStmt, err = qgen.Builder.SimpleSelect("guilds", "guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime", "", "", "") + guilds.ListStmt, err = qgen.Builder.SimpleSelect("guilds", "guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime", "", "", "") if err != nil { return err } - guildsGetGuildStmt, err = qgen.Builder.SimpleSelect("guilds", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "guildID = ?", "", "") + guilds.GetGuildStmt, err = qgen.Builder.SimpleSelect("guilds", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "guildID = ?", "", "") if err != nil { return err } - guildsMemberListStmt, err = qgen.Builder.SimpleSelect("guilds_members", "guildID, uid, rank, posts, joinedAt", "", "", "") + guilds.MemberListStmt, err = qgen.Builder.SimpleSelect("guilds_members", "guildID, uid, rank, posts, joinedAt", "", "", "") if err != nil { return err } - guildsMemberListJoinStmt, err = qgen.Builder.SimpleLeftJoin("guilds_members", "users", "users.uid, guilds_members.rank, guilds_members.posts, guilds_members.joinedAt, users.name, users.avatar", "guilds_members.uid = users.uid", "guilds_members.guildID = ?", "guilds_members.rank DESC, guilds_members.joinedat ASC", "") + guilds.MemberListJoinStmt, err = qgen.Builder.SimpleLeftJoin("guilds_members", "users", "users.uid, guilds_members.rank, guilds_members.posts, guilds_members.joinedAt, users.name, users.avatar", "guilds_members.uid = users.uid", "guilds_members.guildID = ?", "guilds_members.rank DESC, guilds_members.joinedat ASC", "") if err != nil { return err } - guildsGetMemberStmt, err = qgen.Builder.SimpleSelect("guilds_members", "rank, posts, joinedAt", "guildID = ? AND uid = ?", "", "") + guilds.GetMemberStmt, err = qgen.Builder.SimpleSelect("guilds_members", "rank, posts, joinedAt", "guildID = ? AND uid = ?", "", "") if err != nil { return err } - guildsCreateGuildStmt, err = qgen.Builder.SimpleInsert("guilds", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()") + guilds.CreateGuildStmt, err = qgen.Builder.SimpleInsert("guilds", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()") if err != nil { return err } - guildsAttachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = ?, parentType = 'guild'", "fid = ?") + guilds.AttachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = ?, parentType = 'guild'", "fid = ?") if err != nil { return err } - guildsUnattachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = 0, parentType = ''", "fid = ?") + guilds.UnattachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = 0, parentType = ''", "fid = ?") if err != nil { return err } - guildsAddMemberStmt, err = qgen.Builder.SimpleInsert("guilds_members", "guildID, uid, rank, posts, joinedAt", "?,?,?,0,UTC_TIMESTAMP()") + guilds.AddMemberStmt, err = qgen.Builder.SimpleInsert("guilds_members", "guildID, uid, rank, posts, joinedAt", "?,?,?,0,UTC_TIMESTAMP()") if err != nil { return err } @@ -153,26 +73,26 @@ func initGuilds() (err error) { } func deactivateGuilds() { - plugins["guilds"].RemoveHook("intercept_build_widgets", guildsWidgets) - plugins["guilds"].RemoveHook("trow_assign", guildsTrowAssign) - plugins["guilds"].RemoveHook("topic_create_pre_loop", guildsTopicCreatePreLoop) - plugins["guilds"].RemoveHook("pre_render_view_forum", guildsPreRenderViewForum) - plugins["guilds"].RemoveHook("simple_forum_check_pre_perms", guildsForumCheck) - plugins["guilds"].RemoveHook("forum_check_pre_perms", guildsForumCheck) + plugins["guilds"].RemoveHook("intercept_build_widgets", guilds.Widgets) + plugins["guilds"].RemoveHook("trow_assign", guilds.TrowAssign) + plugins["guilds"].RemoveHook("topic_create_pre_loop", guilds.TopicCreatePreLoop) + plugins["guilds"].RemoveHook("pre_render_view_forum", guilds.PreRenderViewForum) + plugins["guilds"].RemoveHook("simple_forum_check_pre_perms", guilds.ForumCheck) + plugins["guilds"].RemoveHook("forum_check_pre_perms", guilds.ForumCheck) deregisterPluginPerm("CreateGuild") _ = router.RemoveFunc("/guilds/") _ = router.RemoveFunc("/guild/") _ = router.RemoveFunc("/guild/create/") _ = router.RemoveFunc("/guild/create/submit/") - _ = guildsListStmt.Close() - _ = guildsMemberListStmt.Close() - _ = guildsMemberListJoinStmt.Close() - _ = guildsGetMemberStmt.Close() - _ = guildsGetGuildStmt.Close() - _ = guildsCreateGuildStmt.Close() - _ = guildsAttachForumStmt.Close() - _ = guildsUnattachForumStmt.Close() - _ = guildsAddMemberStmt.Close() + _ = guilds.ListStmt.Close() + _ = guilds.MemberListStmt.Close() + _ = guilds.MemberListJoinStmt.Close() + _ = guilds.GetMemberStmt.Close() + _ = guilds.GetGuildStmt.Close() + _ = guilds.CreateGuildStmt.Close() + _ = guilds.AttachForumStmt.Close() + _ = guilds.UnattachForumStmt.Close() + _ = guilds.AddMemberStmt.Close() } // TODO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process @@ -228,427 +148,3 @@ func installGuilds() error { func uninstallGuilds() error { return nil } - -// TODO: Do this properly via the widget system -func guildsCommonAreaWidgets(headerVars *HeaderVars) { - // TODO: Hot Groups? Featured Groups? Official Groups? - var b bytes.Buffer - var menu = WidgetMenu{"Guilds", []WidgetMenuItem{ - WidgetMenuItem{"Create Guild", "/guild/create/", false}, - }} - - err := templates.ExecuteTemplate(&b, "widget_menu.html", menu) - if err != nil { - LogError(err) - return - } - - if themes[headerVars.ThemeName].Sidebars == "left" { - headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) - } else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" { - headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) - } -} - -// TODO: Do this properly via the widget system -// TODO: Make a better more customisable group widget system -func guildsGuildWidgets(headerVars *HeaderVars, guildItem *Guild) (success bool) { - return false // Disabled until the next commit - - /*var b bytes.Buffer - var menu WidgetMenu = WidgetMenu{"Guild Options", []WidgetMenuItem{ - WidgetMenuItem{"Join", "/guild/join/" + strconv.Itoa(guildItem.ID), false}, - WidgetMenuItem{"Members", "/guild/members/" + strconv.Itoa(guildItem.ID), false}, - }} - - err := templates.ExecuteTemplate(&b, "widget_menu.html", menu) - if err != nil { - LogError(err) - return false - } - - if themes[headerVars.ThemeName].Sidebars == "left" { - headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) - } else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" { - headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) - } else { - return false - } - return true*/ -} - -/* - Custom Pages -*/ - -func guildsGuildList(w http.ResponseWriter, r *http.Request, user User) RouteError { - headerVars, ferr := UserCheck(w, r, &user) - if ferr != nil { - return ferr - } - guildsCommonAreaWidgets(headerVars) - - rows, err := guildsListStmt.Query() - if err != nil && err != ErrNoRows { - return InternalError(err, w, r) - } - - var guildList []*Guild - for rows.Next() { - guildItem := &Guild{ID: 0} - err := rows.Scan(&guildItem.ID, &guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.CreatedAt, &guildItem.LastUpdateTime) - if err != nil { - return InternalError(err, w, r) - } - guildItem.Link = guildsBuildGuildURL(nameToSlug(guildItem.Name), guildItem.ID) - guildList = append(guildList, guildItem) - } - err = rows.Err() - if err != nil { - return InternalError(err, w, r) - } - rows.Close() - - pi := GuildListPage{"Guild List", user, headerVars, guildList} - err = templates.ExecuteTemplate(w, "guilds_guild_list.html", pi) - if err != nil { - return InternalError(err, w, r) - } - return nil -} - -func guildsGetGuild(guildID int) (guildItem *Guild, err error) { - guildItem = &Guild{ID: guildID} - err = guildsGetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.MainForumID, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime) - return guildItem, err -} - -func guildsViewGuild(w http.ResponseWriter, r *http.Request, user User) RouteError { - // SEO URLs... - halves := strings.Split(r.URL.Path[len("/guild/"):], ".") - if len(halves) < 2 { - halves = append(halves, halves[0]) - } - guildID, err := strconv.Atoi(halves[1]) - if err != nil { - return PreError("Not a valid guild ID", w, r) - } - - guildItem, err := guildsGetGuild(guildID) - if err != nil { - return LocalError("Bad guild", w, r, user) - } - if !guildItem.Active { - return NotFound(w, r) - } - - // Re-route the request to routeForums - var ctx = context.WithValue(r.Context(), "guilds_current_guild", guildItem) - return routeForum(w, r.WithContext(ctx), user, strconv.Itoa(guildItem.MainForumID)) -} - -func guildsCreateGuild(w http.ResponseWriter, r *http.Request, user User) RouteError { - headerVars, ferr := UserCheck(w, r, &user) - if ferr != nil { - return ferr - } - // TODO: Add an approval queue mode for group creation - if !user.Loggedin || !user.PluginPerms["CreateGuild"] { - return NoPermissions(w, r, user) - } - guildsCommonAreaWidgets(headerVars) - - pi := Page{"Create Guild", user, headerVars, tList, nil} - err := templates.ExecuteTemplate(w, "guilds_create_guild.html", pi) - if err != nil { - return InternalError(err, w, r) - } - return nil -} - -func guildsCreateGuildSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError { - // TODO: Add an approval queue mode for group creation - if !user.Loggedin || !user.PluginPerms["CreateGuild"] { - return NoPermissions(w, r, user) - } - - var guildActive = true - var guildName = html.EscapeString(r.PostFormValue("group_name")) - var guildDesc = html.EscapeString(r.PostFormValue("group_desc")) - var gprivacy = r.PostFormValue("group_privacy") - - var guildPrivacy int - switch gprivacy { - case "0": - guildPrivacy = 0 // Public - case "1": - guildPrivacy = 1 // Protected - case "2": - guildPrivacy = 2 // private - default: - guildPrivacy = 0 - } - - // Create the backing forum - fid, err := fstore.Create(guildName, "", true, "") - if err != nil { - return InternalError(err, w, r) - } - - res, err := guildsCreateGuildStmt.Exec(guildName, guildDesc, guildActive, guildPrivacy, user.ID, fid) - if err != nil { - return InternalError(err, w, r) - } - lastID, err := res.LastInsertId() - if err != nil { - return InternalError(err, w, r) - } - - // Add the main backing forum to the forum list - err = guildsAttachForum(int(lastID), fid) - if err != nil { - return InternalError(err, w, r) - } - - _, err = guildsAddMemberStmt.Exec(lastID, user.ID, 2) - if err != nil { - return InternalError(err, w, r) - } - - http.Redirect(w, r, guildsBuildGuildURL(nameToSlug(guildName), int(lastID)), http.StatusSeeOther) - return nil -} - -func guildsMemberList(w http.ResponseWriter, r *http.Request, user User) RouteError { - headerVars, ferr := UserCheck(w, r, &user) - if ferr != nil { - return ferr - } - - // SEO URLs... - halves := strings.Split(r.URL.Path[len("/guild/members/"):], ".") - if len(halves) < 2 { - halves = append(halves, halves[0]) - } - guildID, err := strconv.Atoi(halves[1]) - if err != nil { - return PreError("Not a valid group ID", w, r) - } - - var guildItem = &Guild{ID: guildID} - var mainForum int // Unused - err = guildsGetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &mainForum, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime) - if err != nil { - return LocalError("Bad group", w, r, user) - } - guildItem.Link = guildsBuildGuildURL(nameToSlug(guildItem.Name), guildItem.ID) - - guildsGuildWidgets(headerVars, guildItem) - - rows, err := guildsMemberListJoinStmt.Query(guildID) - if err != nil && err != ErrNoRows { - return InternalError(err, w, r) - } - - var guildMembers []GuildMember - for rows.Next() { - guildMember := GuildMember{PostCount: 0} - err := rows.Scan(&guildMember.User.ID, &guildMember.Rank, &guildMember.PostCount, &guildMember.JoinedAt, &guildMember.User.Name, &guildMember.User.Avatar) - if err != nil { - return InternalError(err, w, r) - } - guildMember.Link = buildProfileURL(nameToSlug(guildMember.User.Name), guildMember.User.ID) - if guildMember.User.Avatar != "" { - if guildMember.User.Avatar[0] == '.' { - guildMember.User.Avatar = "/uploads/avatar_" + strconv.Itoa(guildMember.User.ID) + guildMember.User.Avatar - } - } else { - guildMember.User.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(guildMember.User.ID), 1) - } - guildMember.JoinedAt, _ = relativeTimeFromString(guildMember.JoinedAt) - if guildItem.Owner == guildMember.User.ID { - guildMember.RankString = "Owner" - } else { - switch guildMember.Rank { - case 0: - guildMember.RankString = "Member" - case 1: - guildMember.RankString = "Mod" - case 2: - guildMember.RankString = "Admin" - } - } - guildMembers = append(guildMembers, guildMember) - } - err = rows.Err() - if err != nil { - return InternalError(err, w, r) - } - rows.Close() - - pi := GuildMemberListPage{"Guild Member List", user, headerVars, guildMembers, guildItem, 0, 0} - // A plugin with plugins. Pluginception! - if preRenderHooks["pre_render_guilds_member_list"] != nil { - if runPreRenderHook("pre_render_guilds_member_list", w, r, &user, &pi) { - return nil - } - } - err = templates.ExecuteTemplate(w, "guilds_member_list.html", pi) - if err != nil { - return InternalError(err, w, r) - } - return nil -} - -func guildsAttachForum(guildID int, fid int) error { - _, err := guildsAttachForumStmt.Exec(guildID, fid) - return err -} - -func guildsUnattachForum(fid int) error { - _, err := guildsAttachForumStmt.Exec(fid) - return err -} - -func guildsBuildGuildURL(slug string, id int) string { - if slug == "" { - return "/guild/" + slug + "." + strconv.Itoa(id) - } - return "/guild/" + strconv.Itoa(id) -} - -/* - Hooks -*/ - -func guildsPreRenderViewForum(w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) { - pi := data.(*ForumPage) - if pi.Header.ExtData.items != nil { - if guildData, ok := pi.Header.ExtData.items["guilds_current_group"]; ok { - guildItem := guildData.(*Guild) - - guildpi := GuildPage{pi.Title, pi.CurrentUser, pi.Header, pi.ItemList, pi.Forum, guildItem, pi.Page, pi.LastPage} - err := templates.ExecuteTemplate(w, "guilds_view_guild.html", guildpi) - if err != nil { - LogError(err) - return false - } - return true - } - } - return false -} - -func guildsTrowAssign(args ...interface{}) interface{} { - var forum = args[1].(*Forum) - if forum.ParentType == "guild" { - var topicItem = args[0].(*TopicsRow) - topicItem.ForumLink = "/guild/" + strings.TrimPrefix(topicItem.ForumLink, getForumURLPrefix()) - } - return nil -} - -// TODO: It would be nice, if you could select one of the boards in the group from that drop-down rather than just the one you got linked from -func guildsTopicCreatePreLoop(args ...interface{}) interface{} { - var fid = args[2].(int) - if fstore.DirtyGet(fid).ParentType == "guild" { - var strictmode = args[5].(*bool) - *strictmode = true - } - return nil -} - -// TODO: Add privacy options -// TODO: Add support for multiple boards and add per-board simplified permissions -// TODO: Take isJs into account for routes which expect JSON responses -func guildsForumCheck(args ...interface{}) (skip bool, rerr RouteError) { - var r = args[1].(*http.Request) - var fid = args[3].(*int) - var forum = fstore.DirtyGet(*fid) - - if forum.ParentType == "guild" { - var err error - var w = args[0].(http.ResponseWriter) - guildItem, ok := r.Context().Value("guilds_current_group").(*Guild) - if !ok { - guildItem, err = guildsGetGuild(forum.ParentID) - if err != nil { - return true, InternalError(errors.New("Unable to find the parent group for a forum"), w, r) - } - if !guildItem.Active { - return true, NotFound(w, r) - } - r = r.WithContext(context.WithValue(r.Context(), "guilds_current_group", guildItem)) - } - - var user = args[2].(*User) - var rank int - var posts int - var joinedAt string - - // TODO: Group privacy settings. For now, groups are all globally visible - - // Clear the default group permissions - // TODO: Do this more efficiently, doing it quick and dirty for now to get this out quickly - overrideForumPerms(&user.Perms, false) - user.Perms.ViewTopic = true - - err = guildsGetMemberStmt.QueryRow(guildItem.ID, user.ID).Scan(&rank, &posts, &joinedAt) - if err != nil && err != ErrNoRows { - return true, InternalError(err, w, r) - } else if err != nil { - // TODO: Should we let admins / guests into public groups? - return true, LocalError("You're not part of this group!", w, r, *user) - } - - // TODO: Implement bans properly by adding the Local Ban API in the next commit - // TODO: How does this even work? Refactor it along with the rest of this plugin! - if rank < 0 { - return true, LocalError("You've been banned from this group!", w, r, *user) - } - - // Basic permissions for members, more complicated permissions coming in the next commit! - if guildItem.Owner == user.ID { - overrideForumPerms(&user.Perms, true) - } else if rank == 0 { - user.Perms.LikeItem = true - user.Perms.CreateTopic = true - user.Perms.CreateReply = true - } else { - overrideForumPerms(&user.Perms, true) - } - return true, nil - } - - return false, nil -} - -// TODO: Override redirects? I don't think this is needed quite yet - -func guildsWidgets(args ...interface{}) interface{} { - var zone = args[0].(string) - var headerVars = args[2].(*HeaderVars) - var request = args[3].(*http.Request) - - if zone != "view_forum" { - return false - } - - var forum = args[1].(*Forum) - if forum.ParentType == "guild" { - // This is why I hate using contexts, all the daisy chains and interface casts x.x - guildItem, ok := request.Context().Value("guilds_current_group").(*Guild) - if !ok { - LogError(errors.New("Unable to find a parent group in the context data")) - return false - } - - if headerVars.ExtData.items == nil { - headerVars.ExtData.items = make(map[string]interface{}) - } - headerVars.ExtData.items["guilds_current_group"] = guildItem - - return guildsGuildWidgets(headerVars, guildItem) - } - return false -} diff --git a/query_gen/main.go b/query_gen/main.go index 9ab5d943..04bd116b 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -232,8 +232,6 @@ func writeSelects(adapter qgen.DB_Adapter) error { adapter.SimpleSelect("getFullSettings", "settings", "name, content, type, constraints", "", "", "") - adapter.SimpleSelect("getPlugins", "plugins", "uname, active, installed", "", "", "") - adapter.SimpleSelect("getThemes", "themes", "uname, default", "", "", "") adapter.SimpleSelect("getWidgets", "widgets", "position, side, type, active, location, data", "", "position ASC", "") diff --git a/router_gen/route_group.go b/router_gen/route_group.go index 330ded1f..ff1843e4 100644 --- a/router_gen/route_group.go +++ b/router_gen/route_group.go @@ -30,12 +30,17 @@ func inStringList(needle string, list []string) bool { return false } -func (group *RouteGroup) Before(line string, literal ...bool) *RouteGroup { - var litItem bool - if len(literal) > 0 { - litItem = literal[0] +func (group *RouteGroup) Before(lines ...string) *RouteGroup { + for _, line := range lines { + group.RunBefore = append(group.RunBefore, Runnable{line, false}) + } + return group +} + +func (group *RouteGroup) LitBefore(lines ...string) *RouteGroup { + for _, line := range lines { + group.RunBefore = append(group.RunBefore, Runnable{line, true}) } - group.RunBefore = append(group.RunBefore, Runnable{line, litItem}) return group } diff --git a/router_gen/route_impl.go b/router_gen/route_impl.go index 96b4fd02..a541d509 100644 --- a/router_gen/route_impl.go +++ b/router_gen/route_impl.go @@ -16,12 +16,17 @@ func addRoute(route *RouteImpl) { routeList = append(routeList, route) } -func (route *RouteImpl) Before(item string, literal ...bool) *RouteImpl { - var litItem bool - if len(literal) > 0 { - litItem = literal[0] +func (route *RouteImpl) Before(items ...string) *RouteImpl { + for _, item := range items { + route.RunBefore = append(route.RunBefore, Runnable{item, false}) + } + return route +} + +func (route *RouteImpl) LitBefore(items ...string) *RouteImpl { + for _, item := range items { + route.RunBefore = append(route.RunBefore, Runnable{item, true}) } - route.RunBefore = append(route.RunBefore, Runnable{item, litItem}) return route } diff --git a/router_gen/route_subset.go b/router_gen/route_subset.go index 918bcc6c..4f3959a9 100644 --- a/router_gen/route_subset.go +++ b/router_gen/route_subset.go @@ -4,13 +4,20 @@ type RouteSubset struct { RouteList []*RouteImpl } -func (set *RouteSubset) Before(line string, literal ...bool) *RouteSubset { - var litItem bool - if len(literal) > 0 { - litItem = literal[0] +func (set *RouteSubset) Before(lines ...string) *RouteSubset { + for _, line := range lines { + for _, route := range set.RouteList { + route.RunBefore = append(route.RunBefore, Runnable{line, false}) + } } - for _, route := range set.RouteList { - route.RunBefore = append(route.RunBefore, Runnable{line, litItem}) + return set +} + +func (set *RouteSubset) LitBefore(lines ...string) *RouteSubset { + for _, line := range lines { + for _, route := range set.RouteList { + route.RunBefore = append(route.RunBefore, Runnable{line, true}) + } } return set } diff --git a/router_gen/routes.go b/router_gen/routes.go index 366d1ddb..aa731b21 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -17,7 +17,7 @@ func routes() { // TODO: Reduce the number of Befores. With a new method, perhaps? reportGroup := newRouteGroup("/report/", Route("routeReportSubmit", "/report/submit/", "extra_data"), - ).Before("MemberOnly").Before("NoBanned").Before("NoSessionMismatch") + ).Before("MemberOnly", "NoBanned", "NoSessionMismatch") addRouteGroup(reportGroup) topicGroup := newRouteGroup("/topics/", @@ -34,17 +34,27 @@ func routes() { func buildUserRoutes() { userGroup := newRouteGroup("/user/") userGroup.Routes( - Route("routeProfile", "/user/").Before("req.URL.Path += extra_data", true), - Route("routeAccountOwnEditCritical", "/user/edit/critical/"), - Route("routeAccountOwnEditCriticalSubmit", "/user/edit/critical/submit/"), - Route("routeAccountOwnEditAvatar", "/user/edit/avatar/"), - Route("routeAccountOwnEditAvatarSubmit", "/user/edit/avatar/submit/"), - Route("routeAccountOwnEditUsername", "/user/edit/username/"), - Route("routeAccountOwnEditUsernameSubmit", "/user/edit/username/submit/"), - Route("routeAccountOwnEditEmail", "/user/edit/email/"), - Route("routeAccountOwnEditEmailTokenSubmit", "/user/edit/token/", "extra_data"), + Route("routeProfile", "/user/").LitBefore("req.URL.Path += extra_data"), + Route("routeAccountEditCritical", "/user/edit/critical/"), + Route("routeAccountEditCriticalSubmit", "/user/edit/critical/submit/").Before("NoSessionMismatch"), // TODO: Full test this + Route("routeAccountEditAvatar", "/user/edit/avatar/"), + Route("routeAccountEditAvatarSubmit", "/user/edit/avatar/submit/"), + Route("routeAccountEditUsername", "/user/edit/username/"), + Route("routeAccountEditUsernameSubmit", "/user/edit/username/submit/"), // TODO: Full test this + Route("routeAccountEditEmail", "/user/edit/email/"), + Route("routeAccountEditEmailTokenSubmit", "/user/edit/token/", "extra_data"), ).Not("/user/").Before("MemberOnly") addRouteGroup(userGroup) + + // TODO: Auto test and manual test these routes + userGroup = newRouteGroup("/users/").Before("MemberOnly") + userGroup.Routes( + Route("routeBanSubmit", "/users/ban/submit/"), + Route("routeUnban", "/users/unban/"), + Route("routeActivate", "/users/activate/"), + Route("routeIps", "/users/ips/"), + ).Not("/users/ips/").Before("NoSessionMismatch") + addRouteGroup(userGroup) } func buildPanelRoutes() { diff --git a/routes.go b/routes.go index fa5e8f6e..fc30b3ad 100644 --- a/routes.go +++ b/routes.go @@ -17,6 +17,7 @@ import ( "strings" "time" + "./common" "./query_gen/lib" ) @@ -102,7 +103,7 @@ func routeOverview(w http.ResponseWriter, r *http.Request, user User) RouteError } BuildWidgets("overview", nil, headerVars, r) - pi := Page{"Overview", user, headerVars, tList, nil} + pi := common.Page{"Overview", user, headerVars, tList, nil} if preRenderHooks["pre_render_overview"] != nil { if runPreRenderHook("pre_render_overview", w, r, &user, &pi) { return nil @@ -128,7 +129,7 @@ func routeCustomPage(w http.ResponseWriter, r *http.Request, user User) RouteErr } BuildWidgets("custom_page", name, headerVars, r) - pi := Page{"Page", user, headerVars, tList, nil} + pi := common.Page{"Page", user, headerVars, tList, nil} if preRenderHooks["pre_render_custom_page"] != nil { if runPreRenderHook("pre_render_custom_page", w, r, &user, &pi) { return nil @@ -252,10 +253,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) RouteError { topicItem.ForumName = forum.Name topicItem.ForumLink = forum.Link - /*topicItem.CreatedAt, err = relativeTimeFromString(topicItem.CreatedAt) - if err != nil { - replyItem.CreatedAt = "" - }*/ + //topicItem.CreatedAt = relativeTime(topicItem.CreatedAt) topicItem.RelativeLastReplyAt = relativeTime(topicItem.LastReplyAt) if vhooks["topics_topic_row_assign"] != nil { diff --git a/routes_common.go b/routes_common.go index 9d5dad53..16ee30c1 100644 --- a/routes_common.go +++ b/routes_common.go @@ -7,6 +7,8 @@ import ( "net" "net/http" "strings" + + "./common" ) // nolint @@ -14,23 +16,17 @@ var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = preRoute // TODO: Come up with a better middleware solution // nolint We need these types so people can tell what they are without scrolling to the bottom of the file -var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, RouteError) = panelUserCheck -var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, RouteError) = simplePanelUserCheck -var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, err RouteError) = simpleForumUserCheck -var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, err RouteError) = forumUserCheck -var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, err RouteError) = memberCheck -var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, err RouteError) = simpleUserCheck -var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, err RouteError) = userCheck - -// This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible -// TODO: Write a test for this -func getDefaultHeaderVar() *HeaderVars { - return &HeaderVars{Site: site, ThemeName: fallbackTheme} -} +var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*common.HeaderVars, common.PanelStats, RouteError) = panelUserCheck +var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*common.HeaderLite, RouteError) = simplePanelUserCheck +var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *common.HeaderLite, err RouteError) = simpleForumUserCheck +var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *common.HeaderVars, err RouteError) = forumUserCheck +var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, err RouteError) = memberCheck +var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, err RouteError) = userCheck +var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, err RouteError) = userCheck // TODO: Support for left sidebars and sidebars on both sides // http.Request is for context.Context middleware. Mostly for plugin_guilds right now -func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) { +func BuildWidgets(zone string, data interface{}, headerVars *common.HeaderVars, r *http.Request) { if vhooks["intercept_build_widgets"] != nil { if runVhook("intercept_build_widgets", zone, data, headerVars, r).(bool) { return @@ -53,7 +49,7 @@ func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http } } -func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, rerr RouteError) { +func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *common.HeaderLite, rerr RouteError) { if !fstore.Exists(fid) { return nil, PreError("The target forum doesn't exist.", w, r) } @@ -77,7 +73,7 @@ func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fi return headerLite, nil } -func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, rerr RouteError) { +func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *common.HeaderVars, rerr RouteError) { headerVars, rerr = UserCheck(w, r, user) if rerr != nil { return headerVars, rerr @@ -130,7 +126,7 @@ func cascadeForumPerms(fperms ForumPerms, user *User) { // Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with // TODO: Do a panel specific theme? -func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) { +func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, stats common.PanelStats, rerr RouteError) { var themeName = defaultThemeBox.Load().(string) cookie, err := r.Cookie("current_theme") @@ -192,7 +188,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV return headerVars, stats, nil } -func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) { +func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *common.HeaderLite, rerr RouteError) { return &HeaderLite{ Site: site, Settings: settingBox.Load().(SettingBox), @@ -200,7 +196,7 @@ func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (h } // TODO: Add this to the member routes -func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) { +func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, rerr RouteError) { headerVars, rerr = UserCheck(w, r, user) if !user.Loggedin { return headerVars, NoPermissions(w, r, *user) @@ -209,7 +205,7 @@ func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars } // SimpleUserCheck is back from the grave, yay :D -func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) { +func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *common.HeaderLite, rerr RouteError) { headerLite = &HeaderLite{ Site: site, Settings: settingBox.Load().(SettingBox), @@ -218,7 +214,7 @@ func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header } // TODO: Add the ability for admins to restrict certain themes to certain groups? -func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) { +func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, rerr RouteError) { var themeName = defaultThemeBox.Load().(string) cookie, err := r.Cookie("current_theme") @@ -270,40 +266,37 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars * } func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) { - user, ok := func(w http.ResponseWriter, r *http.Request) (User, bool) { - user, halt := auth.SessionCheck(w, r) - if halt { - return *user, false - } - if user == &guestUser { - return *user, true - } - - h := w.Header() - h.Set("X-Frame-Options", "deny") - //h.Set("X-XSS-Protection", "1") - // TODO: Set the content policy header - return *user, true - }(w, r) - if !ok { - return user, false + user, halt := auth.SessionCheck(w, r) + if halt { + return *user, false } + if user == &guestUser { + return *user, true + } + + var usercpy = *user // TODO: WIP. Refactor this to eliminate the unnecessary query host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { PreError("Bad IP", w, r) - return user, false + return usercpy, false } - if host != user.LastIP { - _, err = stmts.updateLastIP.Exec(host, user.ID) + if host != usercpy.LastIP { + _, err = stmts.updateLastIP.Exec(host, usercpy.ID) if err != nil { InternalError(err, w, r) - return user, false + return usercpy, false } - user.LastIP = host + usercpy.LastIP = host } - return user, ok + + h := w.Header() + h.Set("X-Frame-Options", "deny") + //h.Set("X-XSS-Protection", "1") + // TODO: Set the content policy header + + return usercpy, true } // SuperModeOnly makes sure that only super mods or higher can access the panel routes