diff --git a/common/files.go b/common/files.go index 361f64be..cf22554f 100644 --- a/common/files.go +++ b/common/files.go @@ -3,7 +3,6 @@ package common import ( "bytes" "errors" - "fmt" "mime" "strings" "sync" @@ -38,9 +37,10 @@ type CSSData struct { } func (list SFileList) JSTmplInit() error { + DebugLog("Initialising the client side templates") var fragMap = make(map[string][][]byte) fragMap["alert"] = tmpl.GetFrag("alert") - fmt.Println("fragMap: ", fragMap) + DebugLog("fragMap: ", fragMap) return filepath.Walk("./tmpl_client", func(path string, f os.FileInfo, err error) error { if f.IsDir() { return nil @@ -75,15 +75,15 @@ func (list SFileList) JSTmplInit() error { if !hasBrace { return errors.New("no right brace found after the template function name") } - fmt.Println("spaceIndex: ", spaceIndex) - fmt.Println("endBrace: ", endBrace) - fmt.Println("string(data[spaceIndex:endBrace]): ", string(data[spaceIndex:endBrace])) + //fmt.Println("spaceIndex: ", spaceIndex) + //fmt.Println("endBrace: ", endBrace) + //fmt.Println("string(data[spaceIndex:endBrace]): ", string(data[spaceIndex:endBrace])) preLen := len(data) data = replace(data, string(data[spaceIndex:endBrace]), "") data = replace(data, "))\n", "\n") endBrace -= preLen - len(data) // Offset it as we've deleted portions - var showPos = func(data []byte, index int) (out string) { + /*var showPos = func(data []byte, index int) (out string) { out = "[" for j, char := range data { if index == j { @@ -93,16 +93,16 @@ func (list SFileList) JSTmplInit() error { } } return out + "]" - } + }*/ // ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter var each = func(phrase string, handle func(index int)) { - fmt.Println("find each '" + phrase + "'") + //fmt.Println("find each '" + phrase + "'") var index = endBrace var foundIt bool for { - fmt.Println("in index: ", index) - fmt.Println("pos: ", showPos(data, index)) + //fmt.Println("in index: ", index) + //fmt.Println("pos: ", showPos(data, index)) index, foundIt = skipAllUntilCharsExist(data, index, []byte(phrase)) if !foundIt { break @@ -136,7 +136,7 @@ func (list SFileList) JSTmplInit() error { } }) each("if ", func(index int) { - fmt.Println("if index: ", index) + //fmt.Println("if index: ", index) braceAt, hasBrace := skipUntilIfExists(data, index, '{') if hasBrace { if data[braceAt-1] != ' ' { @@ -161,7 +161,7 @@ func (list SFileList) JSTmplInit() error { tmplName := strings.TrimSuffix(path, ".go") fragset, ok := fragMap[strings.TrimPrefix(tmplName, "template_")] if !ok { - fmt.Println("tmplName: ", tmplName) + DebugLog("tmplName: ", tmplName) return errors.New("couldn't find template in fragmap") } diff --git a/common/pages.go b/common/pages.go index 86a82ead..c464dc61 100644 --- a/common/pages.go +++ b/common/pages.go @@ -254,6 +254,7 @@ type PanelThemesPage struct { } type PanelMenuListItem struct { + Name string ID int ItemCount int } diff --git a/config_default.noparse b/config_default.noparse index dc8ac451..3d11ee93 100644 --- a/config_default.noparse +++ b/config_default.noparse @@ -1,4 +1,4 @@ -package main +package config import "../common" diff --git a/gen_mssql.go b/gen_mssql.go index 03e2a5b2..a4439713 100644 --- a/gen_mssql.go +++ b/gen_mssql.go @@ -21,7 +21,6 @@ type Stmts struct { getTopicBasic *sql.Stmt forumEntryExists *sql.Stmt groupEntryExists *sql.Stmt - getAttachment *sql.Stmt getForumTopics *sql.Stmt createReport *sql.Stmt addForumPermsToForum *sql.Stmt @@ -154,14 +153,6 @@ func _gen_mssql() (err error) { return err } - common.DebugLog("Preparing getAttachment statement.") - stmts.getAttachment, err = db.Prepare("SELECT [sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path] FROM [attachments] WHERE [path] = ?1 AND [sectionID] = ?2 AND [sectionTable] = ?3") - if err != nil { - log.Print("Error in getAttachment statement.") - log.Print("Bad Query: ","SELECT [sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path] FROM [attachments] WHERE [path] = ?1 AND [sectionID] = ?2 AND [sectionTable] = ?3") - return err - } - common.DebugLog("Preparing getForumTopics statement.") stmts.getForumTopics, err = db.Prepare("SELECT [topics].[tid],[topics].[title],[topics].[content],[topics].[createdBy],[topics].[is_closed],[topics].[sticky],[topics].[createdAt],[topics].[lastReplyAt],[topics].[parentID],[users].[name],[users].[avatar] FROM [topics] LEFT JOIN [users] ON [topics].[createdBy] = [users].[uid] WHERE [topics].[parentID] = ?1 ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") if err != nil { diff --git a/gen_mysql.go b/gen_mysql.go index 18d71b9e..f40d843a 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -23,7 +23,6 @@ type Stmts struct { getTopicBasic *sql.Stmt forumEntryExists *sql.Stmt groupEntryExists *sql.Stmt - getAttachment *sql.Stmt getForumTopics *sql.Stmt createReport *sql.Stmt addForumPermsToForum *sql.Stmt @@ -144,13 +143,6 @@ func _gen_mysql() (err error) { return err } - common.DebugLog("Preparing getAttachment statement.") - stmts.getAttachment, err = db.Prepare("SELECT `sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path` FROM `attachments` WHERE `path` = ? AND `sectionID` = ? AND `sectionTable` = ?") - if err != nil { - log.Print("Error in getAttachment statement.") - return err - } - common.DebugLog("Preparing getForumTopics statement.") stmts.getForumTopics, err = db.Prepare("SELECT `topics`.`tid`, `topics`.`title`, `topics`.`content`, `topics`.`createdBy`, `topics`.`is_closed`, `topics`.`sticky`, `topics`.`createdAt`, `topics`.`lastReplyAt`, `topics`.`parentID`, `users`.`name`, `users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` WHERE `topics`.`parentID` = ? ORDER BY `topics`.`sticky` DESC,`topics`.`lastReplyAt` DESC,`topics`.`createdBy` DESC") if err != nil { diff --git a/gen_router.go b/gen_router.go index 3dde6ba4..bff1313d 100644 --- a/gen_router.go +++ b/gen_router.go @@ -24,8 +24,8 @@ var RouteMap = map[string]interface{}{ "routes.CustomPage": routes.CustomPage, "routes.ForumList": routes.ForumList, "routes.ViewForum": routes.ViewForum, - "routeChangeTheme": routeChangeTheme, - "routeShowAttachment": routeShowAttachment, + "routes.ChangeTheme": routes.ChangeTheme, + "routes.ShowAttachment": routes.ShowAttachment, "common.RouteWebsockets": common.RouteWebsockets, "routeReportSubmit": routeReportSubmit, "routes.CreateTopic": routes.CreateTopic, @@ -109,28 +109,28 @@ var RouteMap = map[string]interface{}{ "routes.LockTopicSubmit": routes.LockTopicSubmit, "routes.UnlockTopicSubmit": routes.UnlockTopicSubmit, "routes.MoveTopicSubmit": routes.MoveTopicSubmit, - "routeLikeTopicSubmit": routeLikeTopicSubmit, + "routes.LikeTopicSubmit": routes.LikeTopicSubmit, "routes.ViewTopic": routes.ViewTopic, "routes.CreateReplySubmit": routes.CreateReplySubmit, "routes.ReplyEditSubmit": routes.ReplyEditSubmit, "routes.ReplyDeleteSubmit": routes.ReplyDeleteSubmit, - "routeReplyLikeSubmit": routeReplyLikeSubmit, - "routeProfileReplyCreateSubmit": routeProfileReplyCreateSubmit, + "routes.ReplyLikeSubmit": routes.ReplyLikeSubmit, + "routes.ProfileReplyCreateSubmit": routes.ProfileReplyCreateSubmit, "routes.ProfileReplyEditSubmit": routes.ProfileReplyEditSubmit, "routes.ProfileReplyDeleteSubmit": routes.ProfileReplyDeleteSubmit, "routes.PollVote": routes.PollVote, "routes.PollResults": routes.PollResults, "routes.AccountLogin": routes.AccountLogin, "routes.AccountRegister": routes.AccountRegister, - "routeLogout": routeLogout, + "routes.AccountLogout": routes.AccountLogout, "routes.AccountLoginSubmit": routes.AccountLoginSubmit, "routes.AccountRegisterSubmit": routes.AccountRegisterSubmit, - "routeDynamic": routeDynamic, - "routeUploads": routeUploads, + "routes.DynamicRoute": routes.DynamicRoute, + "routes.UploadedFile": routes.UploadedFile, "routes.StaticFile": routes.StaticFile, "routes.RobotsTxt": routes.RobotsTxt, "routes.SitemapXml": routes.SitemapXml, - "BadRoute": BadRoute, + "routes.BadRoute": routes.BadRoute, } // ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS @@ -140,8 +140,8 @@ var routeMapEnum = map[string]int{ "routes.CustomPage": 2, "routes.ForumList": 3, "routes.ViewForum": 4, - "routeChangeTheme": 5, - "routeShowAttachment": 6, + "routes.ChangeTheme": 5, + "routes.ShowAttachment": 6, "common.RouteWebsockets": 7, "routeReportSubmit": 8, "routes.CreateTopic": 9, @@ -225,28 +225,28 @@ var routeMapEnum = map[string]int{ "routes.LockTopicSubmit": 87, "routes.UnlockTopicSubmit": 88, "routes.MoveTopicSubmit": 89, - "routeLikeTopicSubmit": 90, + "routes.LikeTopicSubmit": 90, "routes.ViewTopic": 91, "routes.CreateReplySubmit": 92, "routes.ReplyEditSubmit": 93, "routes.ReplyDeleteSubmit": 94, - "routeReplyLikeSubmit": 95, - "routeProfileReplyCreateSubmit": 96, + "routes.ReplyLikeSubmit": 95, + "routes.ProfileReplyCreateSubmit": 96, "routes.ProfileReplyEditSubmit": 97, "routes.ProfileReplyDeleteSubmit": 98, "routes.PollVote": 99, "routes.PollResults": 100, "routes.AccountLogin": 101, "routes.AccountRegister": 102, - "routeLogout": 103, + "routes.AccountLogout": 103, "routes.AccountLoginSubmit": 104, "routes.AccountRegisterSubmit": 105, - "routeDynamic": 106, - "routeUploads": 107, + "routes.DynamicRoute": 106, + "routes.UploadedFile": 107, "routes.StaticFile": 108, "routes.RobotsTxt": 109, "routes.SitemapXml": 110, - "BadRoute": 111, + "routes.BadRoute": 111, } var reverseRouteMapEnum = map[int]string{ 0: "routeAPI", @@ -254,8 +254,8 @@ var reverseRouteMapEnum = map[int]string{ 2: "routes.CustomPage", 3: "routes.ForumList", 4: "routes.ViewForum", - 5: "routeChangeTheme", - 6: "routeShowAttachment", + 5: "routes.ChangeTheme", + 6: "routes.ShowAttachment", 7: "common.RouteWebsockets", 8: "routeReportSubmit", 9: "routes.CreateTopic", @@ -339,28 +339,28 @@ var reverseRouteMapEnum = map[int]string{ 87: "routes.LockTopicSubmit", 88: "routes.UnlockTopicSubmit", 89: "routes.MoveTopicSubmit", - 90: "routeLikeTopicSubmit", + 90: "routes.LikeTopicSubmit", 91: "routes.ViewTopic", 92: "routes.CreateReplySubmit", 93: "routes.ReplyEditSubmit", 94: "routes.ReplyDeleteSubmit", - 95: "routeReplyLikeSubmit", - 96: "routeProfileReplyCreateSubmit", + 95: "routes.ReplyLikeSubmit", + 96: "routes.ProfileReplyCreateSubmit", 97: "routes.ProfileReplyEditSubmit", 98: "routes.ProfileReplyDeleteSubmit", 99: "routes.PollVote", 100: "routes.PollResults", 101: "routes.AccountLogin", 102: "routes.AccountRegister", - 103: "routeLogout", + 103: "routes.AccountLogout", 104: "routes.AccountLoginSubmit", 105: "routes.AccountRegisterSubmit", - 106: "routeDynamic", - 107: "routeUploads", + 106: "routes.DynamicRoute", + 107: "routes.UploadedFile", 108: "routes.StaticFile", 109: "routes.RobotsTxt", 110: "routes.SitemapXml", - 111: "BadRoute", + 111: "routes.BadRoute", } var osMapEnum = map[string]int{ "unknown": 0, @@ -860,7 +860,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(5) - err = routeChangeTheme(w,req,user) + err = routes.ChangeTheme(w,req,user) if err != nil { router.handleError(err,w,req,user) } @@ -872,7 +872,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(6) - err = routeShowAttachment(w,req,user,extraData) + err = routes.ShowAttachment(w,req,user,extraData) if err != nil { router.handleError(err,w,req,user) } @@ -1632,7 +1632,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(90) - err = routeLikeTopicSubmit(w,req,user,extraData) + err = routes.LikeTopicSubmit(w,req,user,extraData) default: counters.RouteViewCounter.Bump(91) err = routes.ViewTopic(w,req,user, extraData) @@ -1712,7 +1712,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(95) - err = routeReplyLikeSubmit(w,req,user,extraData) + err = routes.ReplyLikeSubmit(w,req,user,extraData) } if err != nil { router.handleError(err,w,req,user) @@ -1733,7 +1733,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(96) - err = routeProfileReplyCreateSubmit(w,req,user) + err = routes.ProfileReplyCreateSubmit(w,req,user) case "/profile/reply/edit/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1814,7 +1814,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(103) - err = routeLogout(w,req,user) + err = routes.AccountLogout(w,req,user) case "/accounts/login/submit/": err = common.ParseForm(w,req,user) if err != nil { diff --git a/langs/english.json b/langs/english.json index 29dfce23..ff7dc157 100644 --- a/langs/english.json +++ b/langs/english.json @@ -701,6 +701,7 @@ "panel_themes_make_default":"Make Default", "panel_themes_menus_head":"Menus", + "panel_themes_menus_main":"Main Menu", "panel_themes_menus_item_edit_button_aria":"Edit menu item", "panel_themes_menus_item_delete_button_aria":"Delete menu item", diff --git a/main.go b/main.go index bd4996b5..aceeed77 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ import ( "./common/counters" "./config" "./query_gen/lib" + "./routes" "github.com/fsnotify/fsnotify" ) @@ -445,7 +446,7 @@ func main() { // TODO: Redirect to port 443 go func() { log.Print("Listening on port 80") - err = newServer(":80", &HTTPSRedirect{}).ListenAndServe() + err = newServer(":80", &routes.HTTPSRedirect{}).ListenAndServe() if err != nil { log.Fatal(err) } diff --git a/member_routes.go b/member_routes.go index 57c9296c..dc9d6c6e 100644 --- a/member_routes.go +++ b/member_routes.go @@ -2,166 +2,12 @@ package main import ( "net/http" - "path/filepath" "strconv" - "strings" "./common" "./common/counters" ) -// TODO: Refactor this -func routeLikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { - isJs := (r.PostFormValue("isJs") == "1") - tid, err := strconv.Atoi(stid) - if err != nil { - return common.PreErrorJSQ("Topic IDs can only ever be numbers.", w, r, isJs) - } - - topic, err := common.Topics.Get(tid) - if err == ErrNoRows { - return common.PreErrorJSQ("The requested topic doesn't exist.", w, r, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - // TODO: Add hooks to make use of headerLite - _, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID) - if ferr != nil { - return ferr - } - if !user.Perms.ViewTopic || !user.Perms.LikeItem { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - if topic.CreatedBy == user.ID { - return common.LocalErrorJSQ("You can't like your own topics", w, r, user, isJs) - } - - _, err = common.Users.Get(topic.CreatedBy) - if err != nil && err == ErrNoRows { - return common.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - score := 1 - err = topic.Like(score, user.ID) - //log.Print("likeErr: ", err) - if err == common.ErrAlreadyLiked { - return common.LocalErrorJSQ("You already liked this", w, r, user, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - err = common.AddActivityAndNotifyTarget(user.ID, topic.CreatedBy, "like", "topic", tid) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - if !isJs { - http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) - } else { - _, _ = w.Write(successJSONBytes) - } - return nil -} - -func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { - isJs := (r.PostFormValue("isJs") == "1") - - rid, err := strconv.Atoi(srid) - if err != nil { - return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs) - } - - reply, err := common.Rstore.Get(rid) - if err == ErrNoRows { - return common.PreErrorJSQ("You can't like something which doesn't exist!", w, r, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - var fid int - err = stmts.getTopicFID.QueryRow(reply.ParentID).Scan(&fid) - if err == ErrNoRows { - return common.PreErrorJSQ("The parent topic doesn't exist.", w, r, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - // TODO: Add hooks to make use of headerLite - _, ferr := common.SimpleForumUserCheck(w, r, &user, fid) - if ferr != nil { - return ferr - } - if !user.Perms.ViewTopic || !user.Perms.LikeItem { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - if reply.CreatedBy == user.ID { - return common.LocalErrorJSQ("You can't like your own replies", w, r, user, isJs) - } - - _, err = common.Users.Get(reply.CreatedBy) - if err != nil && err != ErrNoRows { - return common.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - err = reply.Like(user.ID) - if err == common.ErrAlreadyLiked { - return common.LocalErrorJSQ("You've already liked this!", w, r, user, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - err = common.AddActivityAndNotifyTarget(user.ID, reply.CreatedBy, "like", "post", rid) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - if !isJs { - http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther) - } else { - _, _ = w.Write(successJSONBytes) - } - return nil -} - -func routeProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - if !user.Perms.ViewTopic || !user.Perms.CreateReply { - return common.NoPermissions(w, r, user) - } - - uid, err := strconv.Atoi(r.PostFormValue("uid")) - if err != nil { - return common.LocalError("Invalid UID", w, r, user) - } - - profileOwner, err := common.Users.Get(uid) - if err == ErrNoRows { - return common.LocalError("The profile you're trying to post on doesn't exist.", w, r, user) - } else if err != nil { - return common.InternalError(err, w, r) - } - - content := common.PreparseMessage(r.PostFormValue("reply-content")) - // TODO: Fully parse the post and store it in the parsed column - _, err = common.Prstore.Create(profileOwner.ID, content, user.ID, user.LastIP) - if err != nil { - return common.InternalError(err, w, r) - } - - err = common.AddActivityAndNotifyTarget(user.ID, profileOwner.ID, "reply", "user", profileOwner.ID) - if err != nil { - return common.InternalError(err, w, r) - } - - counters.PostCounter.Bump() - http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther) - return nil -} - func routeReportSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError { itemID, err := strconv.Atoi(sitemID) if err != nil { @@ -292,9 +138,13 @@ func routeAccountEditEmail(w http.ResponseWriter, r *http.Request, user common.U email.Primary = true emailList = append(emailList, email) } + if !common.Site.EnableEmails { headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_disabled")) } + if r.FormValue("verified") == "1" { + headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_verify_success")) + } pi := common.Page{"Email Manager", user, headerVars, emailList, nil} if common.RunPreRenderHook("pre_render_account_own_edit_email", w, r, &user, &pi) { @@ -361,72 +211,7 @@ func routeAccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, us return common.InternalError(err, w, r) } } + http.Redirect(w, r, "/user/edit/email/?verified=1", http.StatusSeeOther) - if !common.Site.EnableEmails { - headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_disabled")) - } - headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_verify_success")) - pi := common.Page{"Email Manager", user, headerVars, emailList, nil} - if common.RunPreRenderHook("pre_render_account_own_edit_email", w, r, &user, &pi) { - return nil - } - err = common.Templates.ExecuteTemplate(w, "account_own_edit_email.html", pi) - if err != nil { - return common.InternalError(err, w, r) - } - return nil -} - -func routeLogout(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - if !user.Loggedin { - return common.LocalError("You can't logout without logging in first.", w, r, user) - } - common.Auth.Logout(w, user.ID) - http.Redirect(w, r, "/", http.StatusSeeOther) - return nil -} - -func routeShowAttachment(w http.ResponseWriter, r *http.Request, user common.User, filename string) common.RouteError { - filename = common.Stripslashes(filename) - var ext = filepath.Ext("./attachs/" + filename) - //log.Print("ext ", ext) - //log.Print("filename ", filename) - if !common.AllowedFileExts.Contains(strings.TrimPrefix(ext, ".")) { - return common.LocalError("Bad extension", w, r, user) - } - - sectionID, err := strconv.Atoi(r.FormValue("sectionID")) - if err != nil { - return common.LocalError("The sectionID is not an integer", w, r, user) - } - var sectionTable = r.FormValue("sectionType") - - var originTable string - var originID, uploadedBy int - err = stmts.getAttachment.QueryRow(filename, sectionID, sectionTable).Scan(§ionID, §ionTable, &originID, &originTable, &uploadedBy, &filename) - if err == ErrNoRows { - return common.NotFound(w, r, nil) - } else if err != nil { - return common.InternalError(err, w, r) - } - - if sectionTable == "forums" { - _, ferr := common.SimpleForumUserCheck(w, r, &user, sectionID) - if ferr != nil { - return ferr - } - if !user.Perms.ViewTopic { - return common.NoPermissions(w, r, user) - } - } else { - return common.LocalError("Unknown section", w, r, user) - } - - if originTable != "topics" && originTable != "replies" { - return common.LocalError("Unknown origin", w, r, user) - } - - // TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side - http.ServeFile(w, r, "./attachs/"+filename) return nil } diff --git a/panel_routes.go b/panel_routes.go index cbb510ba..adc6704e 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -2397,7 +2397,12 @@ func routePanelThemesMenus(w http.ResponseWriter, r *http.Request, user common.U var menuList []common.PanelMenuListItem for mid, list := range common.Menus.GetAllMap() { + var name = "" + if mid == 1 { + name = common.GetTmplPhrase("panel_themes_menus_main") + } menuList = append(menuList, common.PanelMenuListItem{ + Name: name, ID: mid, ItemCount: len(list.List), }) diff --git a/patcher/main.go b/patcher/main.go index 91322a1e..a87ce9aa 100644 --- a/patcher/main.go +++ b/patcher/main.go @@ -92,13 +92,16 @@ func patcher(scanner *bufio.Scanner) error { } fmt.Println("Applying the patches") - if dbVersion < 1 { - err := patch0(scanner) - if err != nil { - return err + if dbVersion < 2 { + if dbVersion < 1 { + err := patch0(scanner) + if err != nil { + return err + } } + return patch1(scanner) } - return patch1(scanner) + return patch2(scanner) } func execStmt(stmt *sql.Stmt, err error) error { diff --git a/patcher/patches.go b/patcher/patches.go index 6fef4d26..42ce668c 100644 --- a/patcher/patches.go +++ b/patcher/patches.go @@ -152,3 +152,57 @@ func patch1(scanner *bufio.Scanner) error { return nil } + +func patch2(scanner *bufio.Scanner) error { + // ! Don't reuse this function blindly, it doesn't escape apostrophes + var replaceTextWhere = func(replaceThis string, withThis string) error { + return execStmt(qgen.Builder.SimpleUpdate("viewchunks", "route = '"+withThis+"'", "route = '"+replaceThis+"'")) + } + + err := replaceTextWhere("routeLogout", "routes.AccountLogout") + if err != nil { + return err + } + + err = replaceTextWhere("routeShowAttachment", "routes.ShowAttachment") + if err != nil { + return err + } + + err = replaceTextWhere("routeChangeTheme", "routes.ChangeTheme") + if err != nil { + return err + } + + err = replaceTextWhere("routeProfileReplyCreateSubmit", "routes.ProfileReplyCreateSubmit") + if err != nil { + return err + } + + err = replaceTextWhere("routeLikeTopicSubmit", "routes.LikeTopicSubmit") + if err != nil { + return err + } + + err = replaceTextWhere("routeReplyLikeSubmit", "routes.ReplyLikeSubmit") + if err != nil { + return err + } + + err = replaceTextWhere("routeDynamic", "routes.DynamicRoute") + if err != nil { + return err + } + + err = replaceTextWhere("routeUploads", "routes.UploadedFile") + if err != nil { + return err + } + + err = replaceTextWhere("BadRoute", "routes.BadRoute") + if err != nil { + return err + } + + return nil +} diff --git a/public/global.js b/public/global.js index e75db775..d354f862 100644 --- a/public/global.js +++ b/public/global.js @@ -217,16 +217,30 @@ function runWebSockets() { } } +function loadScript(name, callback) { + let url = "//" +siteURL+"/static/"+name + $.getScript(url) + .done(callback) + .fail((e,xhr,settings,ex) => { + console.log("Unable to get script '"+url+"'"); + console.log("e: ", e); + console.log("xhr: ", xhr); + console.log("settings: ", settings); + console.log("ex: ",ex); + console.trace(); + }); +} + $(document).ready(function(){ runHook("start_init"); - $.getScript( "./static/template_alert.js", () => { + loadScript("template_alert.js",() => { console.log("Loaded template_alert.js"); alertsInitted = true; var alertMenuList = document.getElementsByClassName("menu_alerts"); for(var i = 0; i < alertMenuList.length; i++) { loadAlerts(alertMenuList[i]); } - }); + }) if(window["WebSocket"]) runWebSockets(); else conn = false; diff --git a/query_gen/main.go b/query_gen/main.go index d77fe4eb..eed0036b 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -283,8 +283,6 @@ func writeSelects(adapter qgen.Adapter) error { build.Select("groupEntryExists").Table("users_groups").Columns("gid").Where("name = ''").Orderby("gid ASC").Limit("0,1").Parse() - build.Select("getAttachment").Table("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Where("path = ? AND sectionID = ? AND sectionTable = ?").Parse() - return nil } diff --git a/router_gen/main.go b/router_gen/main.go index 6c3296f3..6d0bd3aa 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -155,12 +155,12 @@ func main() { } // Stubs for us to refer to these routes through - mapIt("routeDynamic") - mapIt("routeUploads") + mapIt("routes.DynamicRoute") + mapIt("routes.UploadedFile") mapIt("routes.StaticFile") mapIt("routes.RobotsTxt") mapIt("routes.SitemapXml") - mapIt("BadRoute") + mapIt("routes.BadRoute") tmplVars.AllRouteNames = allRouteNames tmplVars.AllRouteMap = allRouteMap @@ -652,7 +652,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { common.NotFound(w,req,nil) return } - counters.RouteViewCounter.Bump({{.AllRouteMap.routeUploads}}) + counters.RouteViewCounter.Bump({{index .AllRouteMap "routes.UploadedFile" }}) req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? router.UploadHandler(w,req) // TODO: Count these views @@ -697,7 +697,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { router.RUnlock() if ok { - counters.RouteViewCounter.Bump({{.AllRouteMap.routeDynamic}}) // TODO: Be more specific about *which* dynamic route it is + counters.RouteViewCounter.Bump({{index .AllRouteMap "routes.DynamicRoute" }}) // TODO: Be more specific about *which* dynamic route it is req.URL.Path += extraData err = handle(w,req,user) if err != nil { @@ -712,7 +712,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } else { router.DumpRequest(req,"Bad Route") } - counters.RouteViewCounter.Bump({{.AllRouteMap.BadRoute}}) + counters.RouteViewCounter.Bump({{index .AllRouteMap "routes.BadRoute" }}) common.NotFound(w,req,nil) } } diff --git a/router_gen/routes.go b/router_gen/routes.go index d571a1eb..a7bf715b 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -7,9 +7,9 @@ func routes() { addRoute(View("routes.CustomPage", "/pages/", "extraData")) addRoute(View("routes.ForumList", "/forums/" /*,"&forums"*/)) addRoute(View("routes.ViewForum", "/forum/", "extraData")) - addRoute(AnonAction("routeChangeTheme", "/theme/")) + addRoute(AnonAction("routes.ChangeTheme", "/theme/")) addRoute( - View("routeShowAttachment", "/attachs/", "extraData").Before("ParseForm"), + View("routes.ShowAttachment", "/attachs/", "extraData").Before("ParseForm"), ) // TODO: Reduce the number of Befores. With a new method, perhaps? @@ -74,7 +74,7 @@ func buildTopicRoutes() { Action("routes.LockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"), Action("routes.UnlockTopicSubmit", "/topic/unlock/submit/", "extraData"), Action("routes.MoveTopicSubmit", "/topic/move/submit/", "extraData"), - Action("routeLikeTopicSubmit", "/topic/like/submit/", "extraData").Before("ParseForm"), + Action("routes.LikeTopicSubmit", "/topic/like/submit/", "extraData").Before("ParseForm"), ) addRouteGroup(topicGroup) } @@ -88,7 +88,7 @@ func buildReplyRoutes() { UploadAction("routes.CreateReplySubmit", "/reply/create/").MaxSizeVar("int(common.Config.MaxRequestSize)"), // TODO: Rename the route so it's /reply/create/submit/ Action("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"), Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"), - Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData").Before("ParseForm"), + Action("routes.ReplyLikeSubmit", "/reply/like/submit/", "extraData").Before("ParseForm"), ) addRouteGroup(replyGroup) } @@ -98,7 +98,7 @@ func buildProfileReplyRoutes() { //router.HandleFunc("/user/edit/submit/", routeLogout) // routeLogout? what on earth? o.o pReplyGroup := newRouteGroup("/profile/") pReplyGroup.Routes( - Action("routeProfileReplyCreateSubmit", "/profile/reply/create/"), // TODO: Add /submit/ to the end + Action("routes.ProfileReplyCreateSubmit", "/profile/reply/create/"), // TODO: Add /submit/ to the end Action("routes.ProfileReplyEditSubmit", "/profile/reply/edit/submit/", "extraData"), Action("routes.ProfileReplyDeleteSubmit", "/profile/reply/delete/submit/", "extraData"), ) @@ -120,7 +120,7 @@ func buildAccountRoutes() { accReplyGroup.Routes( View("routes.AccountLogin", "/accounts/login/"), View("routes.AccountRegister", "/accounts/create/"), - Action("routeLogout", "/accounts/logout/"), + Action("routes.AccountLogout", "/accounts/logout/"), AnonAction("routes.AccountLoginSubmit", "/accounts/login/submit/"), // TODO: Guard this with a token, maybe the IP hashed with a rotated key? AnonAction("routes.AccountRegisterSubmit", "/accounts/create/submit/"), ) diff --git a/routes.go b/routes.go index 308b82d1..8fae9f97 100644 --- a/routes.go +++ b/routes.go @@ -7,7 +7,6 @@ package main import ( - "html" "net/http" "strconv" @@ -20,51 +19,8 @@ var tList []interface{} //var nList []string var successJSONBytes = []byte(`{"success":"1"}`) -// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS -type HTTPSRedirect struct { -} - -func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Connection", "close") - dest := "https://" + req.Host + req.URL.Path - if len(req.URL.RawQuery) > 0 { - dest += "?" + req.URL.RawQuery - } - http.Redirect(w, req, dest, http.StatusTemporaryRedirect) -} - -// Temporary stubs for view tracking -func routeDynamic() { -} -func routeUploads() { -} -func BadRoute() { -} - -// TODO: Set the cookie domain -func routeChangeTheme(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - //headerLite, _ := SimpleUserCheck(w, r, &user) - // TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly? - isJs := (r.PostFormValue("isJs") == "1") - newTheme := html.EscapeString(r.PostFormValue("newTheme")) - - theme, ok := common.Themes[newTheme] - if !ok || theme.HideFromThemes { - return common.LocalErrorJSQ("That theme doesn't exist", w, r, user, isJs) - } - - cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: int(common.Year)} - http.SetCookie(w, &cookie) - - if !isJs { - http.Redirect(w, r, "/", http.StatusSeeOther) - } else { - _, _ = w.Write(successJSONBytes) - } - return nil -} - // TODO: Refactor this +// TODO: Use the phrase system var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`) // TODO: Refactor this endpoint @@ -81,8 +37,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R return common.PreErrorJS("Invalid Action", w, r) } - module := r.FormValue("module") - switch module { + switch r.FormValue("module") { case "dismiss-alert": asid, err := strconv.Atoi(r.FormValue("asid")) if err != nil { diff --git a/routes/account.go b/routes/account.go index 78031b5e..2f6b45a3 100644 --- a/routes/account.go +++ b/routes/account.go @@ -77,6 +77,15 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user common.User return nil } +func AccountLogout(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + if !user.Loggedin { + return common.LocalError("You can't logout without logging in first.", w, r, user) + } + common.Auth.Logout(w, user.ID) + http.Redirect(w, r, "/", http.StatusSeeOther) + return nil +} + func AccountRegister(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { header, ferr := common.UserCheck(w, r, &user) if ferr != nil { diff --git a/routes/misc.go b/routes/misc.go index 59b24fdf..ffe5fb03 100644 --- a/routes/misc.go +++ b/routes/misc.go @@ -2,13 +2,17 @@ package routes import ( "bytes" + "database/sql" + "html" "io" "net/http" + "path/filepath" "strconv" "strings" "time" "../common" + "../query_gen/lib" ) var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(common.Day)) // TODO: Make this a common.Config value @@ -86,3 +90,87 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user common.User, name s } return nil } + +type AttachmentStmts struct { + get *sql.Stmt +} + +var attachmentStmts AttachmentStmts + +// TODO: Move these DbInits into a TopicList abstraction +func init() { + common.DbInits.Add(func(acc *qgen.Accumulator) error { + attachmentStmts = AttachmentStmts{ + get: acc.Select("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Where("path = ? AND sectionID = ? AND sectionTable = ?").Prepare(), + } + return acc.FirstError() + }) +} + +func ShowAttachment(w http.ResponseWriter, r *http.Request, user common.User, filename string) common.RouteError { + filename = common.Stripslashes(filename) + var ext = filepath.Ext("./attachs/" + filename) + //log.Print("ext ", ext) + //log.Print("filename ", filename) + if !common.AllowedFileExts.Contains(strings.TrimPrefix(ext, ".")) { + return common.LocalError("Bad extension", w, r, user) + } + + sectionID, err := strconv.Atoi(r.FormValue("sectionID")) + if err != nil { + return common.LocalError("The sectionID is not an integer", w, r, user) + } + var sectionTable = r.FormValue("sectionType") + + var originTable string + var originID, uploadedBy int + err = attachmentStmts.get.QueryRow(filename, sectionID, sectionTable).Scan(§ionID, §ionTable, &originID, &originTable, &uploadedBy, &filename) + if err == sql.ErrNoRows { + return common.NotFound(w, r, nil) + } else if err != nil { + return common.InternalError(err, w, r) + } + + if sectionTable == "forums" { + _, ferr := common.SimpleForumUserCheck(w, r, &user, sectionID) + if ferr != nil { + return ferr + } + if !user.Perms.ViewTopic { + return common.NoPermissions(w, r, user) + } + } else { + return common.LocalError("Unknown section", w, r, user) + } + + if originTable != "topics" && originTable != "replies" { + return common.LocalError("Unknown origin", w, r, user) + } + + // TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side + http.ServeFile(w, r, "./attachs/"+filename) + return nil +} + +// TODO: Set the cookie domain +func ChangeTheme(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + //headerLite, _ := SimpleUserCheck(w, r, &user) + // TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly? + isJs := (r.PostFormValue("isJs") == "1") + newTheme := html.EscapeString(r.PostFormValue("newTheme")) + + theme, ok := common.Themes[newTheme] + if !ok || theme.HideFromThemes { + return common.LocalErrorJSQ("That theme doesn't exist", w, r, user, isJs) + } + + cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: int(common.Year)} + http.SetCookie(w, &cookie) + + if !isJs { + http.Redirect(w, r, "/", http.StatusSeeOther) + } else { + _, _ = w.Write(successJSONBytes) + } + return nil +} diff --git a/routes/reply.go b/routes/reply.go index 1d6ddafe..af9541c0 100644 --- a/routes/reply.go +++ b/routes/reply.go @@ -297,6 +297,41 @@ func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, return nil } +// TODO: Move the profile reply routes to their own file? +func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + if !user.Perms.ViewTopic || !user.Perms.CreateReply { + return common.NoPermissions(w, r, user) + } + + uid, err := strconv.Atoi(r.PostFormValue("uid")) + if err != nil { + return common.LocalError("Invalid UID", w, r, user) + } + + profileOwner, err := common.Users.Get(uid) + if err == sql.ErrNoRows { + return common.LocalError("The profile you're trying to post on doesn't exist.", w, r, user) + } else if err != nil { + return common.InternalError(err, w, r) + } + + content := common.PreparseMessage(r.PostFormValue("reply-content")) + // TODO: Fully parse the post and store it in the parsed column + _, err = common.Prstore.Create(profileOwner.ID, content, user.ID, user.LastIP) + if err != nil { + return common.InternalError(err, w, r) + } + + err = common.AddActivityAndNotifyTarget(user.ID, profileOwner.ID, "reply", "user", profileOwner.ID) + if err != nil { + return common.InternalError(err, w, r) + } + + counters.PostCounter.Bump() + http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther) + return nil +} + func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { isJs := (r.PostFormValue("js") == "1") @@ -372,3 +407,64 @@ func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user commo } return nil } + +func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { + isJs := (r.PostFormValue("isJs") == "1") + + rid, err := strconv.Atoi(srid) + if err != nil { + return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs) + } + + reply, err := common.Rstore.Get(rid) + if err == sql.ErrNoRows { + return common.PreErrorJSQ("You can't like something which doesn't exist!", w, r, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + topic, err := common.Topics.Get(reply.ParentID) + if err == sql.ErrNoRows { + return common.PreErrorJSQ("The parent topic doesn't exist.", w, r, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + // TODO: Add hooks to make use of headerLite + _, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID) + if ferr != nil { + return ferr + } + if !user.Perms.ViewTopic || !user.Perms.LikeItem { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + if reply.CreatedBy == user.ID { + return common.LocalErrorJSQ("You can't like your own replies", w, r, user, isJs) + } + + _, err = common.Users.Get(reply.CreatedBy) + if err != nil && err != sql.ErrNoRows { + return common.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + err = reply.Like(user.ID) + if err == common.ErrAlreadyLiked { + return common.LocalErrorJSQ("You've already liked this!", w, r, user, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + err = common.AddActivityAndNotifyTarget(user.ID, reply.CreatedBy, "like", "post", rid) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + if !isJs { + http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther) + } else { + _, _ = w.Write(successJSONBytes) + } + return nil +} diff --git a/routes/stubs.go b/routes/stubs.go new file mode 100644 index 00000000..c57c847c --- /dev/null +++ b/routes/stubs.go @@ -0,0 +1,24 @@ +package routes + +import "net/http" + +// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS +type HTTPSRedirect struct { +} + +func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Connection", "close") + dest := "https://" + req.Host + req.URL.Path + if len(req.URL.RawQuery) > 0 { + dest += "?" + req.URL.RawQuery + } + http.Redirect(w, req, dest, http.StatusTemporaryRedirect) +} + +// Temporary stubs for view tracking +func DynamicRoute() { +} +func UploadedFile() { +} +func BadRoute() { +} diff --git a/routes/topic.go b/routes/topic.go index 5906c59f..dfe00c47 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -842,3 +842,59 @@ func addTopicAction(action string, topic *common.Topic, user common.User) error } return topic.CreateActionReply(action, user.LastIP, user) } + +// TODO: Refactor this +func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { + isJs := (r.PostFormValue("isJs") == "1") + tid, err := strconv.Atoi(stid) + if err != nil { + return common.PreErrorJSQ("Topic IDs can only ever be numbers.", w, r, isJs) + } + + topic, err := common.Topics.Get(tid) + if err == sql.ErrNoRows { + return common.PreErrorJSQ("The requested topic doesn't exist.", w, r, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + // TODO: Add hooks to make use of headerLite + _, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID) + if ferr != nil { + return ferr + } + if !user.Perms.ViewTopic || !user.Perms.LikeItem { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + if topic.CreatedBy == user.ID { + return common.LocalErrorJSQ("You can't like your own topics", w, r, user, isJs) + } + + _, err = common.Users.Get(topic.CreatedBy) + if err != nil && err == sql.ErrNoRows { + return common.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + score := 1 + err = topic.Like(score, user.ID) + //log.Print("likeErr: ", err) + if err == common.ErrAlreadyLiked { + return common.LocalErrorJSQ("You already liked this", w, r, user, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + err = common.AddActivityAndNotifyTarget(user.ID, topic.CreatedBy, "like", "topic", tid) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + if !isJs { + http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) + } else { + _, _ = w.Write(successJSONBytes) + } + return nil +} diff --git a/schema/schema.json b/schema/schema.json index 58f877eb..1e592ab3 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -1,5 +1,5 @@ { - "DBVersion":"2", + "DBVersion":"3", "DynamicFileVersion":"0", "MinGoVersion":"1.10", "MinVersion":"" diff --git a/templates/panel-inner-menu.html b/templates/panel-inner-menu.html index eb7f9f00..817e3be6 100644 --- a/templates/panel-inner-menu.html +++ b/templates/panel-inner-menu.html @@ -17,9 +17,15 @@
{{end}} - {{if .CurrentUser.Perms.ManageThemes}}