From 11c60b3cbebd81c7b09c69305b1dc5331acc1446 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sat, 23 Sep 2017 20:57:13 +0100 Subject: [PATCH] You can now search for whatever IP you want in the IP Searcher. Removed the Uncategorised Forum. Added the Backup Page for super admins. Not quite functional yet. Forums are now sorted properly again. Fixed a bug in DirtyGet() where invalid IDs would trigger a panic. Fixed a bug where alternate themes wouldn't work without setting them as default first and restarting Gosora. --- .gitignore | 1 + backups/filler.txt | 1 + config.go | 2 +- forum.go | 14 ++++++++ forum_store.go | 21 +++++++---- gen_router.go | 3 ++ install/install.go | 2 +- main.go | 17 ++++----- member_routes.go | 3 ++ mod_routes.go | 2 +- pages.go | 30 ++++++++++++++-- panel_routes.go | 62 ++++++++++++++++++++++++++++++-- router_gen/routes.go | 1 + routes.go | 10 +++--- site.go | 39 +++++++++++++++----- template_list.go | 16 ++++----- templates/ip-search-results.html | 24 +++++++++---- templates/panel-backups.html | 20 +++++++++++ templates/panel-forums.html | 6 ++-- templates/panel-inner-menu.html | 3 ++ templates/topic.html | 8 ++--- themes.go | 45 +++++++++++++---------- themes/shadow/public/main.css | 46 +++++++++++++++++++----- utils.go | 22 +++++++++++- 24 files changed, 307 insertions(+), 91 deletions(-) create mode 100644 backups/filler.txt create mode 100644 templates/panel-backups.html diff --git a/.gitignore b/.gitignore index 705c6af2..13a268d5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ brun.bat uploads/avatar_* uploads/socialgroup_* +backups/*.sql bin/* out/* *.exe diff --git a/backups/filler.txt b/backups/filler.txt new file mode 100644 index 00000000..20e14b1e --- /dev/null +++ b/backups/filler.txt @@ -0,0 +1 @@ +This file is here so that Git will include this folder in the repository. \ No newline at end of file diff --git a/config.go b/config.go index d08afbaf..c254459d 100644 --- a/config.go +++ b/config.go @@ -39,7 +39,7 @@ func init() { config.DefaultGroup = 3 // Should be a setting in the database config.ActivationGroup = 5 // Should be a setting in the database config.StaffCSS = "staff_post" - config.UncategorisedForumVisible = true + config.DefaultForum = 2 config.MinifyTemplates = false config.MultiServer = false // Experimental: Enable Cross-Server Synchronisation and several other features diff --git a/forum.go b/forum.go index 5635f0e0..f697a3b0 100644 --- a/forum.go +++ b/forum.go @@ -39,6 +39,20 @@ type ForumSimple struct { Preset string } +// TODO: Replace this sorting mechanism with something a lot more efficient +// ? - Use sort.Slice instead? +type SortForum []*Forum + +func (sf SortForum) Len() int { + return len(sf) +} +func (sf SortForum) Swap(i, j int) { + sf[i], sf[j] = sf[j], sf[i] +} +func (sf SortForum) Less(i, j int) bool { + return sf[i].ID < sf[j].ID +} + func buildForumURL(slug string, fid int) string { if slug == "" { return "/forum/" + strconv.Itoa(fid) diff --git a/forum_store.go b/forum_store.go index 47dae81f..6a36bcd5 100644 --- a/forum_store.go +++ b/forum_store.go @@ -9,6 +9,7 @@ package main import ( "database/sql" "log" + "sort" "sync" "sync/atomic" @@ -104,8 +105,6 @@ func (mfs *MemoryForumStore) LoadForums() error { } } - addForum(&Forum{0, buildForumURL(nameToSlug("Uncategorised"), 0), "Uncategorised", "", config.UncategorisedForumVisible, "all", 0, "", 0, "", "", 0, "", 0, ""}) - rows, err := getForumsStmt.Query() if err != nil { return err @@ -148,16 +147,16 @@ func (mfs *MemoryForumStore) rebuildView() { } return true }) + sort.Sort(SortForum(forumView)) mfs.forumView.Store(forumView) } func (mfs *MemoryForumStore) DirtyGet(id int) *Forum { fint, ok := mfs.forums.Load(id) - forum := fint.(*Forum) - if !ok || forum.Name == "" { + if !ok || fint.(*Forum).Name == "" { return &Forum{ID: -1, Name: ""} } - return forum + return fint.(*Forum) } func (mfs *MemoryForumStore) CacheGet(id int) (*Forum, error) { @@ -225,24 +224,29 @@ func (mfs *MemoryForumStore) CacheSet(forum *Forum) error { return nil } +// ! Has a randomised order func (mfs *MemoryForumStore) GetAll() (forumView []*Forum, err error) { mfs.forums.Range(func(_ interface{}, value interface{}) bool { forumView = append(forumView, value.(*Forum)) return true }) + sort.Sort(SortForum(forumView)) return forumView, nil } +// ? - Can we optimise the sorting? func (mfs *MemoryForumStore) GetAllIDs() (ids []int, err error) { mfs.forums.Range(func(_ interface{}, value interface{}) bool { ids = append(ids, value.(*Forum).ID) return true }) + sort.Ints(ids) return ids, nil } -func (mfs *MemoryForumStore) GetAllVisible() ([]*Forum, error) { - return mfs.forumView.Load().([]*Forum), nil +func (mfs *MemoryForumStore) GetAllVisible() (forumView []*Forum, err error) { + forumView = mfs.forumView.Load().([]*Forum) + return forumView, nil } func (mfs *MemoryForumStore) GetAllVisibleIDs() ([]int, error) { @@ -285,6 +289,7 @@ func (mfs *MemoryForumStore) Delete(id int) error { return nil } +// ! Is this racey? func (mfs *MemoryForumStore) IncrementTopicCount(id int) error { forum, err := mfs.Get(id) if err != nil { @@ -298,6 +303,7 @@ func (mfs *MemoryForumStore) IncrementTopicCount(id int) error { return nil } +// ! Is this racey? func (mfs *MemoryForumStore) DecrementTopicCount(id int) error { forum, err := mfs.Get(id) if err != nil { @@ -312,6 +318,7 @@ func (mfs *MemoryForumStore) DecrementTopicCount(id int) error { } // TODO: Have a pointer to the last topic rather than storing it on the forum itself +// ! Is this racey? func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error { forum, err := mfs.Get(fid) if err != nil { diff --git a/gen_router.go b/gen_router.go index e58350e2..8fe0464e 100644 --- a/gen_router.go +++ b/gen_router.go @@ -214,6 +214,9 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { case "/panel/groups/create/": routePanelGroupsCreateSubmit(w,req,user) return + case "/panel/backups/": + routePanelBackups(w,req,user,extra_data) + return case "/panel/logs/mod/": routePanelLogsMod(w,req,user) return diff --git a/install/install.go b/install/install.go index 6d072080..bb59b72a 100644 --- a/install/install.go +++ b/install/install.go @@ -183,7 +183,7 @@ config.DefaultRoute = routeTopics config.DefaultGroup = 3 // Should be a setting in the database config.ActivationGroup = 5 // Should be a setting in the database config.StaffCSS = "staff_post" -config.UncategorisedForumVisible = true +config.DefaultForum = 2 config.MinifyTemplates = true config.MultiServer = false // Experimental: Enable Cross-Server Synchronisation and several other features diff --git a/main.go b/main.go index 44f9e11c..8d439100 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,6 @@ import ( "log" "net/http" "os" - "strings" "time" //"runtime/pprof" ) @@ -41,16 +40,6 @@ var externalSites = map[string]string{ var staticFiles = make(map[string]SFile) var logWriter = io.MultiWriter(os.Stderr) -func processConfig() { - config.Noavatar = strings.Replace(config.Noavatar, "{site_url}", site.URL, -1) - if site.Port != "80" && site.Port != "443" { - site.URL = strings.TrimSuffix(site.URL, "/") - site.URL = strings.TrimSuffix(site.URL, "\\") - site.URL = strings.TrimSuffix(site.URL, ":") - site.URL = site.URL + ":" + site.Port - } -} - func main() { // TODO: Have a file for each run with the time/date the server started as the file name? // TODO: Log panics with recover() @@ -121,6 +110,11 @@ func main() { log.Fatal(err) } + err = verifyConfig() + if err != nil { + log.Fatal(err) + } + // Run this goroutine once a second secondTicker := time.NewTicker(1 * time.Second) fifteenMinuteTicker := time.NewTicker(15 * time.Minute) @@ -149,6 +143,7 @@ func main() { // TODO: Manage the TopicStore, UserStore, and ForumStore // TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high // TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task? + // TODO: Rescan the static files for changes // TODO: Add a plugin hook here case <-fifteenMinuteTicker.C: diff --git a/member_routes.go b/member_routes.go index 8d3b40f1..87af4c42 100644 --- a/member_routes.go +++ b/member_routes.go @@ -28,6 +28,9 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid st return } } + if fid == 0 { + fid = config.DefaultForum + } headerVars, ok := ForumUserCheck(w, r, &user, fid) if !ok { diff --git a/mod_routes.go b/mod_routes.go index f916af3c..fa13d74b 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -574,7 +574,7 @@ func routeIps(w http.ResponseWriter, r *http.Request, user User) { return } - ip := html.EscapeString(r.URL.Path[len("/users/ips/"):]) + ip := r.FormValue("ip") var uid int var reqUserList = make(map[int]bool) diff --git a/pages.go b/pages.go index 723da854..3f54ff31 100644 --- a/pages.go +++ b/pages.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" "sync" + "time" ) type HeaderVars struct { @@ -227,7 +228,23 @@ type PanelEditGroupPermsPage struct { GlobalPerms []NameLangToggle } -type Log struct { +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 unneccesary 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 @@ -238,7 +255,7 @@ type PanelLogsPage struct { CurrentUser User Header *HeaderVars Stats PanelStats - Logs []Log + Logs []logItem PageList []int Page int LastPage int @@ -283,6 +300,7 @@ func init() { urlReg = regexp.MustCompile(urlpattern) } +// TODO: Write a test for this func shortcodeToUnicode(msg string) string { //re := regexp.MustCompile(":(.):") msg = strings.Replace(msg, ":grinning:", "😀", -1) @@ -421,6 +439,7 @@ func preparseMessage(msg string) string { return shortcodeToUnicode(msg) } +// TODO: Write a test for this func parseMessage(msg string /*, user User*/) string { msg = strings.Replace(msg, ":)", "😀", -1) msg = strings.Replace(msg, ":(", "😞", -1) @@ -634,6 +653,7 @@ func parseMessage(msg string /*, user User*/) string { return msg } +// TODO: Write a test for this func regexParseMessage(msg string) string { msg = strings.Replace(msg, ":)", "😀", -1) msg = strings.Replace(msg, ":D", "😃", -1) @@ -648,6 +668,7 @@ func regexParseMessage(msg string) string { // 6, 7, 8, 6, 7 // ftp://, http://, https:// git://, mailto: (not a URL, just here for length comparison purposes) +// TODO: Write a test for this func validateURLBytes(data []byte) bool { datalen := len(data) i := 0 @@ -670,6 +691,7 @@ func validateURLBytes(data []byte) bool { return true } +// TODO: Write a test for this func validatedURLBytes(data []byte) (url []byte) { datalen := len(data) i := 0 @@ -694,6 +716,7 @@ func validatedURLBytes(data []byte) (url []byte) { return url } +// TODO: Write a test for this func partialURLBytes(data []byte) (url []byte) { datalen := len(data) i := 0 @@ -719,6 +742,7 @@ func partialURLBytes(data []byte) (url []byte) { return url } +// TODO: Write a test for this func partialURLBytesLen(data []byte) int { datalen := len(data) i := 0 @@ -745,6 +769,7 @@ func partialURLBytesLen(data []byte) int { return datalen } +// TODO: Write a test for this func parseMediaBytes(data []byte) (protocol []byte, url []byte) { datalen := len(data) i := 0 @@ -774,6 +799,7 @@ func parseMediaBytes(data []byte) (protocol []byte, url []byte) { return protocol, data[i:] } +// TODO: Write a test for this func coerceIntBytes(data []byte) (res int, length int) { if !(data[0] > 47 && data[0] < 58) { return 0, 1 diff --git a/panel_routes.go b/panel_routes.go index 14f10a53..4f26c602 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -6,8 +6,11 @@ import ( "fmt" "html" "html/template" + "io/ioutil" "log" "net/http" + "os" + "path/filepath" "strconv" "strings" @@ -190,6 +193,7 @@ func routePanelForums(w http.ResponseWriter, r *http.Request, user User) { return } + // TODO: Paginate this? var forumList []interface{} forums, err := fstore.GetAll() if err != nil { @@ -197,6 +201,7 @@ func routePanelForums(w http.ResponseWriter, r *http.Request, user User) { return } + // ? - Should we generate something similar to the forumView? It might be a little overkill for a page which is rarely loaded in comparison to /forums/ for _, forum := range forums { if forum.Name != "" && forum.ParentID == 0 { fadmin := ForumAdmin{forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, forum.TopicCount, presetToLang(forum.Preset)} @@ -1940,6 +1945,59 @@ func routePanelThemesSetDefault(w http.ResponseWriter, r *http.Request, user Use http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther) } +func routePanelBackups(w http.ResponseWriter, r *http.Request, user User, backupURL string) { + headerVars, stats, ok := PanelUserCheck(w, r, &user) + if !ok { + return + } + if !user.IsSuperAdmin { + NoPermissions(w, r, user) + return + } + + if backupURL != "" { + // We don't want them trying to break out of this directory, it shouldn't hurt since it's a super admin, but it's always good to practice good security hygiene, especially if this is one of many instances on a managed server not controlled by the superadmin/s + backupURL = Stripslashes(backupURL) + + var ext = filepath.Ext("./backups/" + backupURL) + if ext == ".sql" { + info, err := os.Stat("./backups/" + backupURL) + if err != nil { + NotFound(w, r) + return + } + // TODO: Change the served filename to gosora_backup_%timestamp%.sql, the time the file was generated, not when it was modified aka what the name of it should be + w.Header().Set("Content-Disposition", "attachment; filename=gosora_backup.sql") + w.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10)) + // TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side + http.ServeFile(w, r, "./backups/"+backupURL) + return + } + NotFound(w, r) + return + } + + var backupList []backupItem + backupFiles, err := ioutil.ReadDir("./backups") + if err != nil { + InternalError(err, w) + return + } + for _, backupFile := range backupFiles { + var ext = filepath.Ext(backupFile.Name()) + if ext != ".sql" { + continue + } + backupList = append(backupList, backupItem{backupFile.Name(), backupFile.ModTime()}) + } + + pi := PanelBackupPage{"Backups", user, headerVars, stats, backupList} + err = templates.ExecuteTemplate(w, "panel-backups.html", pi) + if err != nil { + InternalError(err, w) + } +} + func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user User) { headerVars, stats, ok := PanelUserCheck(w, r, &user) if !ok { @@ -1964,7 +2022,7 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user User) { } defer rows.Close() - var logs []Log + var logs []logItem var action, elementType, ipaddress, doneAt string var elementID, actorID int for rows.Next() { @@ -2035,7 +2093,7 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user User) { default: action = "Unknown action '" + action + "' by " + actor.Name + "" } - logs = append(logs, Log{Action: template.HTML(action), IPAddress: ipaddress, DoneAt: doneAt}) + logs = append(logs, logItem{Action: template.HTML(action), IPAddress: ipaddress, DoneAt: doneAt}) } err = rows.Err() if err != nil { diff --git a/router_gen/routes.go b/router_gen/routes.go index 4fd5a8ba..f3619696 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -81,6 +81,7 @@ func routes() { Route{"routePanelGroupsEditPermsSubmit", "/panel/groups/edit/perms/submit/", "", []string{"extra_data"}}, Route{"routePanelGroupsCreateSubmit", "/panel/groups/create/", "", []string{}}, + Route{"routePanelBackups", "/panel/backups/", "", []string{"extra_data"}}, Route{"routePanelLogsMod", "/panel/logs/mod/", "", []string{}}, Route{"routePanelDebug", "/panel/debug/", "", []string{}}, ) diff --git a/routes.go b/routes.go index e97721d4..be184aed 100644 --- a/routes.go +++ b/routes.go @@ -45,6 +45,9 @@ func routeStatic(w http.ResponseWriter, r *http.Request) { //log.Print("Outputting static file '" + r.URL.Path + "'") file, ok := staticFiles[r.URL.Path] if !ok { + if dev.DebugMode { + log.Print("Failed to find '" + r.URL.Path + "'") + } w.WriteHeader(http.StatusNotFound) return } @@ -75,14 +78,9 @@ func routeStatic(w http.ResponseWriter, r *http.Request) { } // Deprecated: Test route for stopping the server during a performance analysis -/*func route_exit(w http.ResponseWriter, r *http.Request){ +/*func routeExit(w http.ResponseWriter, r *http.Request, user User){ db.Close() os.Exit(0) -} - -// Deprecated: Test route to see which file serving method is faster -func route_fstatic(w http.ResponseWriter, r *http.Request){ - http.ServeFile(w,r,r.URL.Path) }*/ // TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else? diff --git a/site.go b/site.go index db8504af..6f2f526d 100644 --- a/site.go +++ b/site.go @@ -1,6 +1,10 @@ package main -import "net/http" +import ( + "errors" + "net/http" + "strings" +) var site = &Site{Name: "Magical Fairy Land", Language: "english"} var dbConfig = DBConfig{Host: "localhost"} @@ -8,7 +12,7 @@ var config Config var dev DevConfig type Site struct { - Name string // ? - Move this into the settings table? + Name string // ? - Move this into the settings table? Should we make a second version of this for the abbreviation shown in the navbar? Email string // ? - Move this into the settings table? URL string Port string @@ -40,13 +44,13 @@ type Config struct { SMTPPassword string SMTPPort string - DefaultRoute func(http.ResponseWriter, *http.Request, User) - DefaultGroup int - ActivationGroup int - StaffCSS string // ? - Move this into the settings table? Might be better to implement this as Group CSS - UncategorisedForumVisible bool - MinifyTemplates bool - MultiServer bool + DefaultRoute func(http.ResponseWriter, *http.Request, User) + DefaultGroup int + ActivationGroup int + StaffCSS string // ? - Move this into the settings table? Might be better to implement this as Group CSS + DefaultForum int // The forum posts go in by default, this used to be covered by the Uncategorised Forum, but we want to replace it with a more robust solution. Make this a setting? + MinifyTemplates bool + MultiServer bool Noavatar string // ? - Move this into the settings table? ItemsPerPage int // ? - Move this into the settings table? @@ -57,3 +61,20 @@ type DevConfig struct { SuperDebug bool Profiling bool } + +func processConfig() { + config.Noavatar = strings.Replace(config.Noavatar, "{site_url}", site.URL, -1) + if site.Port != "80" && site.Port != "443" { + site.URL = strings.TrimSuffix(site.URL, "/") + site.URL = strings.TrimSuffix(site.URL, "\\") + site.URL = strings.TrimSuffix(site.URL, ":") + site.URL = site.URL + ":" + site.Port + } +} + +func verifyConfig() error { + if !fstore.Exists(config.DefaultForum) { + return errors.New("Invalid default forum") + } + return nil +} diff --git a/template_list.go b/template_list.go index 80f3309b..4c52b488 100644 --- a/template_list.go +++ b/template_list.go @@ -134,9 +134,9 @@ var topic_28 = []byte(`   `) var topic_29 = []byte(` - `) + `) var topic_33 = []byte(``) var topic_35 = []byte(``) var topic_43 = []byte(``) -var topic_45 = []byte(``) var topic_47 = []byte(` `) var topic_69 = []byte(`   `) var topic_70 = []byte(``) +var topic_71 = []byte(`" class="mod_button" title="Love it" style="color:#202020;">`) var topic_74 = []byte(``) var topic_76 = []byte(``) -var topic_78 = []byte(``) var topic_80 = []byte(` -
- +
+
+

IP Search

+
-
-
Searching for {{.IP}}
+ +
+
+
+ + +
-
- {{range .ItemList}}
+ +{{if .IP}} +
+ {{range .ItemList}} - {{else}}
No users found.
{{end}} + {{else}}
No users found.
{{end}}
+{{end}} {{template "footer.html" . }} diff --git a/templates/panel-backups.html b/templates/panel-backups.html new file mode 100644 index 00000000..931cf2c6 --- /dev/null +++ b/templates/panel-backups.html @@ -0,0 +1,20 @@ +{{template "header.html" . }} +{{template "panel-menu.html" . }} +
+
+

Backups

+
+
+ {{range .Backups}} +
+ {{.SQLURL}} + + Download + +
+ {{else}} +
There aren't any backups available at this time.
+ {{end}} +
+
+{{template "footer.html" . }} diff --git a/templates/panel-forums.html b/templates/panel-forums.html index 76b7fc0e..f73cff14 100644 --- a/templates/panel-forums.html +++ b/templates/panel-forums.html @@ -11,14 +11,14 @@
{{range .ItemList}} -
+
- {{if gt .ID 0}}Edit - {{end}} + Edit + {{if gt .ID 1}}Delete{{end}} Full Edit diff --git a/templates/panel-inner-menu.html b/templates/panel-inner-menu.html index 42a36004..b09eb968 100644 --- a/templates/panel-inner-menu.html +++ b/templates/panel-inner-menu.html @@ -31,6 +31,9 @@ {{if .CurrentUser.Perms.ManagePlugins}}{{end}} + {{if .CurrentUser.IsSuperAdmin}}
+ Backups +
{{end}} diff --git a/templates/topic.html b/templates/topic.html index e49c04db..3e90c3c1 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -30,7 +30,7 @@ {{.Topic.CreatedByName}}   {{if .CurrentUser.Perms.LikeItem}} - {{end}} + {{end}} {{if .CurrentUser.Perms.EditTopic}}{{end}} @@ -39,7 +39,7 @@ {{if .CurrentUser.Perms.CloseTopic}}{{if .Topic.IsClosed}}{{else}}{{end}}{{end}} {{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}{{else}}{{end}}{{end}} - {{if .CurrentUser.Perms.ViewIPs}}{{end}} + {{if .CurrentUser.Perms.ViewIPs}}{{end}} {{if .Topic.LikeCount}}{{end}} @@ -61,12 +61,12 @@ {{.CreatedByName}}   - {{if $.CurrentUser.Perms.LikeItem}}{{end}} + {{if $.CurrentUser.Perms.LikeItem}}{{end}} {{if $.CurrentUser.Perms.EditReply}}{{end}} {{if $.CurrentUser.Perms.DeleteReply}}{{end}} - {{if $.CurrentUser.Perms.ViewIPs}}{{end}} + {{if $.CurrentUser.Perms.ViewIPs}}{{end}} {{if .LikeCount}}{{end}} diff --git a/themes.go b/themes.go index 2463ec84..bb0d11e6 100644 --- a/themes.go +++ b/themes.go @@ -70,6 +70,11 @@ type ThemeResource struct { Location string } +func init() { + defaultThemeBox.Store(fallbackTheme) +} + +// ? - Delete themes which no longer exist in the themes folder from the database? func LoadThemes() error { changeDefaultThemeMutex.Lock() rows, err := getThemesStmt.Query() @@ -92,32 +97,16 @@ func LoadThemes() error { continue } - theme.TemplatesMap = make(map[string]string) - theme.TmplPtr = make(map[string]interface{}) - if theme.Templates != nil { - for _, themeTmpl := range theme.Templates { - theme.TemplatesMap[themeTmpl.Name] = themeTmpl.Source - theme.TmplPtr[themeTmpl.Name] = tmplPtrMap["o_"+themeTmpl.Source] - } - } - - theme.ResourceTemplates = template.New("") - template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + uname + "/public/*.css")) - if defaultThemeSwitch { - log.Print("Loading the theme '" + theme.Name + "'") + log.Print("Loading the default theme '" + theme.Name + "'") theme.Active = true - defaultThemeBox.Store(uname) + defaultThemeBox.Store(theme.Name) mapThemeTemplates(theme) } else { + log.Print("Loading the theme '" + theme.Name + "'") theme.Active = false } - // It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes - err = addThemeStaticFiles(theme) - if err != nil { - return err - } themes[uname] = theme } changeDefaultThemeMutex.Unlock() @@ -160,6 +149,24 @@ func initThemes() error { } } + theme.TemplatesMap = make(map[string]string) + theme.TmplPtr = make(map[string]interface{}) + if theme.Templates != nil { + for _, themeTmpl := range theme.Templates { + theme.TemplatesMap[themeTmpl.Name] = themeTmpl.Source + theme.TmplPtr[themeTmpl.Name] = tmplPtrMap["o_"+themeTmpl.Source] + } + } + + theme.ResourceTemplates = template.New("") + template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + theme.Name + "/public/*.css")) + + // It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes + err = addThemeStaticFiles(theme) + if err != nil { + return err + } + themes[theme.Name] = theme } return nil diff --git a/themes/shadow/public/main.css b/themes/shadow/public/main.css index 49c00d8d..886c3dc4 100644 --- a/themes/shadow/public/main.css +++ b/themes/shadow/public/main.css @@ -146,7 +146,6 @@ a { margin-top: 8px; padding: 12px; } - .rowitem h1 { font-size: 16px; font-weight: normal; @@ -154,7 +153,6 @@ a { -webkit-margin-after: 0; display: inline; } - .rowsmall { font-size: 12px; } @@ -181,6 +179,11 @@ a { padding: 10px; } +/* Algin to right in a flex head */ +.to_right { + margin-left: auto; +} + /* Topic View */ /* TODO: How should we handle the sticky headers? */ @@ -223,7 +226,6 @@ a { display: block; float: left; } - .mod_button button { border: none; background: none; @@ -288,7 +290,6 @@ a { color: rgb(205,205,205); float: right; } - .level { margin-left: 3px; } @@ -408,7 +409,11 @@ textarea.large { background-size: 40px; padding-left: 46px; } - +.bgavatars:not(.rowlist) .rowitem { + background-repeat: no-repeat; + background-size: 40px; + padding-left: 46px; +} .rowlist .formrow, .rowlist .formrow:first-child { margin-top: 0px; } @@ -475,7 +480,7 @@ input, select, textarea { } /* Forum View */ -.rowhead { +.rowhead, .opthead, .colstack_head { display: flex; flex-direction: row; } @@ -533,9 +538,6 @@ input, select, textarea { white-space: nowrap; } -.topic_item { - display: flex; -} .topic_name_input { width: 100%; margin-right: 10px; @@ -596,6 +598,32 @@ input, select, textarea { padding-left: 136px; } +.ip_search_block .rowitem { + display: flex; + flex-direction: row; +} + +.ip_search_block input { + background-color: #444444; + border: 1px solid #555555; + color: #999999; + margin-top: -3px; + margin-bottom: -3px; + padding: 4px; + padding-bottom: 3px; +} + +.ip_search_input { + font-size: 15px; + width: 100%; + margin-left: 0px; +} + +.ip_search_search { + font-size: 14px; + margin-left: 8px; +} + .colstack_grid { display: grid; grid-template-columns: repeat(3, 1fr); diff --git a/utils.go b/utils.go index 7795c300..2ddba612 100644 --- a/utils.go +++ b/utils.go @@ -29,6 +29,7 @@ type Version struct { TagID int } +// TODO: Write a test for this func (version *Version) String() (out string) { out = strconv.Itoa(version.Major) + "." + strconv.Itoa(version.Minor) + "." + strconv.Itoa(version.Patch) if version.Tag != "" { @@ -40,7 +41,8 @@ func (version *Version) String() (out string) { return } -// GenerateSafeString is for generating a cryptographically secure set of random bytes.. +// GenerateSafeString is for generating a cryptographically secure set of random bytes... +// TODO: Write a test for this func GenerateSafeString(length int) (string, error) { rb := make([]byte, length) _, err := rand.Read(rb) @@ -50,6 +52,7 @@ func GenerateSafeString(length int) (string, error) { return base64.URLEncoding.EncodeToString(rb), nil } +// TODO: Write a test for this func relativeTime(in string) (string, error) { if in == "" { return "", nil @@ -97,6 +100,7 @@ func relativeTime(in string) (string, error) { } } +// TODO: Write a test for this func convertByteUnit(bytes float64) (float64, string) { switch { case bytes >= float64(terabyte): @@ -112,6 +116,7 @@ func convertByteUnit(bytes float64) (float64, string) { } } +// TODO: Write a test for this func convertByteInUnit(bytes float64, unit string) (count float64) { switch unit { case "TB": @@ -132,6 +137,7 @@ func convertByteInUnit(bytes float64, unit string) (count float64) { return } +// TODO: Write a test for this func convertUnit(num int) (int, string) { switch { case num >= 1000000000000: @@ -147,6 +153,7 @@ func convertUnit(num int) (int, string) { } } +// TODO: Write a test for this func convertFriendlyUnit(num int) (int, string) { switch { case num >= 1000000000000: @@ -231,6 +238,7 @@ func SendEmail(email string, subject string, msg string) (res bool) { return true } +// TODO: Write a test for this func weakPassword(password string) error { if len(password) < 8 { return errors.New("your password needs to be at-least eight characters long") @@ -283,6 +291,7 @@ func weakPassword(password string) error { return nil } +// TODO: Write a test for this func createFile(name string) error { f, err := os.Create(name) if err != nil { @@ -291,6 +300,7 @@ func createFile(name string) error { return f.Close() } +// TODO: Write a test for this func writeFile(name string, content string) (err error) { f, err := os.Create(name) if err != nil { @@ -307,6 +317,13 @@ func writeFile(name string, content string) (err error) { return f.Close() } +// TODO: Write a test for this +func Stripslashes(text string) string { + text = strings.Replace(text, "/", "", -1) + return strings.Replace(text, "\\", "", -1) +} + +// TODO: Write a test for this func wordCount(input string) (count int) { input = strings.TrimSpace(input) if input == "" { @@ -326,6 +343,7 @@ func wordCount(input string) (count int) { return count + 1 } +// TODO: Write a test for this func getLevel(score int) (level int) { var base float64 = 25 var current, prev float64 @@ -346,6 +364,7 @@ func getLevel(score int) (level int) { return level } +// TODO: Write a test for this func getLevelScore(getLevel int) (score int) { var base float64 = 25 var current, prev float64 @@ -367,6 +386,7 @@ func getLevelScore(getLevel int) (score int) { return int(math.Ceil(current)) } +// TODO: Write a test for this func getLevels(maxLevel int) []float64 { var base float64 = 25 var current, prev float64 // = 0