From a7fec1117002e752eebd9ee3d88325c20af164c5 Mon Sep 17 00:00:00 2001 From: Azareal Date: Mon, 22 Jan 2018 08:15:45 +0000 Subject: [PATCH] IP Search now searches profile replies. Added the subscription and attachment stores. Refactored the routes to use the new subscription and attachment stores. Fixed up the buttons on the profile comments for Cosora. Made various enhancements to Cosora. Renamed routePanel to routePanelDashboard. Changed routeCreateTopicSubmit into an upload action and moved it into /routes/topic.go and refactored some of the redundant bits away. --- common/attachments.go | 29 ++++++++ common/ip_search.go | 20 +++-- common/subscription.go | 27 +++++++ gen_mssql.go | 16 ---- gen_mysql.go | 14 ---- gen_router.go | 33 +++++---- main.go | 8 ++ member_routes.go | 131 +-------------------------------- panel_routes.go | 2 +- query_gen/main.go | 4 - router_gen/routes.go | 4 +- routes/topic.go | 124 +++++++++++++++++++++++++++++++ templates/panel_users.html | 4 +- themes/cosora/public/main.css | 28 ++++++- themes/cosora/public/panel.css | 15 +++- 15 files changed, 266 insertions(+), 193 deletions(-) create mode 100644 common/attachments.go create mode 100644 common/subscription.go diff --git a/common/attachments.go b/common/attachments.go new file mode 100644 index 00000000..5d419022 --- /dev/null +++ b/common/attachments.go @@ -0,0 +1,29 @@ +package common + +import ( + "database/sql" + + "../query_gen/lib" +) + +var Attachments AttachmentStore + +type AttachmentStore interface { + Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) error +} + +type DefaultAttachmentStore struct { + add *sql.Stmt +} + +func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) { + acc := qgen.Builder.Accumulator() + return &DefaultAttachmentStore{ + add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(), + }, acc.FirstError() +} + +func (store *DefaultAttachmentStore) Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) error { + _, err := store.add.Exec(sectionID, sectionTable, originID, originTable, uploadedBy, path) + return err +} diff --git a/common/ip_search.go b/common/ip_search.go index c81dfec5..4407db84 100644 --- a/common/ip_search.go +++ b/common/ip_search.go @@ -13,18 +13,20 @@ type IPSearcher interface { } type DefaultIPSearcher struct { - searchUsers *sql.Stmt - searchReplies *sql.Stmt - searchTopics *sql.Stmt + searchUsers *sql.Stmt + searchTopics *sql.Stmt + searchReplies *sql.Stmt + searchUsersReplies *sql.Stmt } // NewDefaultIPSearcher gives you a new instance of DefaultIPSearcher func NewDefaultIPSearcher() (*DefaultIPSearcher, error) { acc := qgen.Builder.Accumulator() return &DefaultIPSearcher{ - searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(), - searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(), - searchReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(), + searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(), + searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(), + searchReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(), + searchUsersReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("users_replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(), }, acc.FirstError() } @@ -53,16 +55,18 @@ func (searcher *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) { if err != nil { return uids, err } - err = runQuery(searcher.searchTopics) if err != nil { return uids, err } - err = runQuery(searcher.searchReplies) if err != nil { return uids, err } + err = runQuery(searcher.searchUsersReplies) + if err != nil { + return uids, err + } // Convert the user ID map to a slice, then bulk load the users uids = make([]int, len(reqUserList)) diff --git a/common/subscription.go b/common/subscription.go new file mode 100644 index 00000000..8ad83715 --- /dev/null +++ b/common/subscription.go @@ -0,0 +1,27 @@ +package common + +import "database/sql" +import "../query_gen/lib" + +var Subscriptions SubscriptionStore + +// ? Should we have a subscription store for each zone? topic, forum, etc? +type SubscriptionStore interface { + Add(uid int, elementID int, elementType string) error +} + +type DefaultSubscriptionStore struct { + add *sql.Stmt +} + +func NewDefaultSubscriptionStore() (*DefaultSubscriptionStore, error) { + acc := qgen.Builder.Accumulator() + return &DefaultSubscriptionStore{ + add: acc.Insert("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Prepare(), + }, acc.FirstError() +} + +func (store *DefaultSubscriptionStore) Add(uid int, elementID int, elementType string) error { + _, err := store.add.Exec(uid, elementID, elementType) + return err +} diff --git a/gen_mssql.go b/gen_mssql.go index a3a9ec1e..76bfac36 100644 --- a/gen_mssql.go +++ b/gen_mssql.go @@ -35,11 +35,9 @@ type Stmts struct { addActivity *sql.Stmt notifyOne *sql.Stmt addEmail *sql.Stmt - addSubscription *sql.Stmt addForumPermsToForum *sql.Stmt addPlugin *sql.Stmt addTheme *sql.Stmt - addAttachment *sql.Stmt createWordFilter *sql.Stmt editReply *sql.Stmt updatePlugin *sql.Stmt @@ -256,13 +254,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing addSubscription statement.") - stmts.addSubscription, err = db.Prepare("INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)") - if err != nil { - log.Print("Bad Query: ","INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)") - return err - } - log.Print("Preparing addForumPermsToForum statement.") stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) VALUES (?,?,?,?)") if err != nil { @@ -284,13 +275,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing addAttachment statement.") - stmts.addAttachment, err = db.Prepare("INSERT INTO [attachments] ([sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path]) VALUES (?,?,?,?,?,?)") - if err != nil { - log.Print("Bad Query: ","INSERT INTO [attachments] ([sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path]) VALUES (?,?,?,?,?,?)") - return err - } - log.Print("Preparing createWordFilter statement.") stmts.createWordFilter, err = db.Prepare("INSERT INTO [word_filters] ([find],[replacement]) VALUES (?,?)") if err != nil { diff --git a/gen_mysql.go b/gen_mysql.go index 55e07e0d..9f3fb318 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -37,11 +37,9 @@ type Stmts struct { addActivity *sql.Stmt notifyOne *sql.Stmt addEmail *sql.Stmt - addSubscription *sql.Stmt addForumPermsToForum *sql.Stmt addPlugin *sql.Stmt addTheme *sql.Stmt - addAttachment *sql.Stmt createWordFilter *sql.Stmt editReply *sql.Stmt updatePlugin *sql.Stmt @@ -232,12 +230,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing addSubscription statement.") - stmts.addSubscription, err = db.Prepare("INSERT INTO `activity_subscriptions`(`user`,`targetID`,`targetType`,`level`) VALUES (?,?,?,2)") - if err != nil { - return err - } - log.Print("Preparing addForumPermsToForum statement.") stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)") if err != nil { @@ -256,12 +248,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing addAttachment statement.") - stmts.addAttachment, err = db.Prepare("INSERT INTO `attachments`(`sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path`) VALUES (?,?,?,?,?,?)") - if err != nil { - return err - } - log.Print("Preparing createWordFilter statement.") stmts.createWordFilter, err = db.Prepare("INSERT INTO `word_filters`(`find`,`replacement`) VALUES (?,?)") if err != nil { diff --git a/gen_router.go b/gen_router.go index 536e9e2e..f889e4f9 100644 --- a/gen_router.go +++ b/gen_router.go @@ -69,7 +69,7 @@ var RouteMap = map[string]interface{}{ "routePanelBackups": routePanelBackups, "routePanelLogsMod": routePanelLogsMod, "routePanelDebug": routePanelDebug, - "routePanel": routePanel, + "routePanelDashboard": routePanelDashboard, "routeAccountEditCritical": routeAccountEditCritical, "routeAccountEditCriticalSubmit": routeAccountEditCriticalSubmit, "routeAccountEditAvatar": routeAccountEditAvatar, @@ -83,7 +83,7 @@ var RouteMap = map[string]interface{}{ "routes.UnbanUser": routes.UnbanUser, "routes.ActivateUser": routes.ActivateUser, "routes.IPSearch": routes.IPSearch, - "routeCreateTopicSubmit": routeCreateTopicSubmit, + "routes.CreateTopicSubmit": routes.CreateTopicSubmit, "routes.EditTopicSubmit": routes.EditTopicSubmit, "routes.DeleteTopicSubmit": routes.DeleteTopicSubmit, "routes.StickTopicSubmit": routes.StickTopicSubmit, @@ -164,7 +164,7 @@ var routeMapEnum = map[string]int{ "routePanelBackups": 50, "routePanelLogsMod": 51, "routePanelDebug": 52, - "routePanel": 53, + "routePanelDashboard": 53, "routeAccountEditCritical": 54, "routeAccountEditCriticalSubmit": 55, "routeAccountEditAvatar": 56, @@ -178,7 +178,7 @@ var routeMapEnum = map[string]int{ "routes.UnbanUser": 64, "routes.ActivateUser": 65, "routes.IPSearch": 66, - "routeCreateTopicSubmit": 67, + "routes.CreateTopicSubmit": 67, "routes.EditTopicSubmit": 68, "routes.DeleteTopicSubmit": 69, "routes.StickTopicSubmit": 70, @@ -257,7 +257,7 @@ var reverseRouteMapEnum = map[int]string{ 50: "routePanelBackups", 51: "routePanelLogsMod", 52: "routePanelDebug", - 53: "routePanel", + 53: "routePanelDashboard", 54: "routeAccountEditCritical", 55: "routeAccountEditCriticalSubmit", 56: "routeAccountEditAvatar", @@ -271,7 +271,7 @@ var reverseRouteMapEnum = map[int]string{ 64: "routes.UnbanUser", 65: "routes.ActivateUser", 66: "routes.IPSearch", - 67: "routeCreateTopicSubmit", + 67: "routes.CreateTopicSubmit", 68: "routes.EditTopicSubmit", 69: "routes.DeleteTopicSubmit", 70: "routes.StickTopicSubmit", @@ -951,7 +951,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { err = routePanelDebug(w,req,user) default: common.RouteViewCounter.Bump(53) - err = routePanel(w,req,user) + err = routePanelDashboard(w,req,user) } if err != nil { router.handleError(err,w,req,user) @@ -1130,20 +1130,25 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { case "/topic": switch(req.URL.Path) { case "/topic/create/submit/": - err = common.NoSessionMismatch(w,req,user) - if err != nil { - router.handleError(err,w,req,user) - return - } - err = common.MemberOnly(w,req,user) if err != nil { router.handleError(err,w,req,user) return } + err = common.HandleUploadRoute(w,req,user,common.Config.MaxRequestSize) + if err != nil { + router.handleError(err,w,req,user) + return + } + err = common.NoUploadSessionMismatch(w,req,user) + if err != nil { + router.handleError(err,w,req,user) + return + } + common.RouteViewCounter.Bump(67) - err = routeCreateTopicSubmit(w,req,user) + err = routes.CreateTopicSubmit(w,req,user) case "/topic/edit/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { diff --git a/main.go b/main.go index e2a5c135..830d425b 100644 --- a/main.go +++ b/main.go @@ -84,6 +84,14 @@ func afterDBInit() (err error) { if err != nil { return err } + common.Subscriptions, err = common.NewDefaultSubscriptionStore() + if err != nil { + return err + } + common.Attachments, err = common.NewDefaultAttachmentStore() + if err != nil { + return err + } common.GlobalViewCounter, err = common.NewGlobalViewCounter() if err != nil { diff --git a/member_routes.go b/member_routes.go index 30ab8243..285c1b9e 100644 --- a/member_routes.go +++ b/member_routes.go @@ -16,135 +16,6 @@ import ( "./common" ) -func routeCreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - // TODO: Reduce this to 1MB for attachments for each file? - if r.ContentLength > int64(common.Config.MaxRequestSize) { - size, unit := common.ConvertByteUnit(float64(common.Config.MaxRequestSize)) - return common.CustomError("Your attachments are too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user) - } - r.Body = http.MaxBytesReader(w, r.Body, int64(common.Config.MaxRequestSize)) - - err := r.ParseMultipartForm(int64(common.Megabyte)) - if err != nil { - return common.LocalError("Unable to parse the form", w, r, user) - } - - fid, err := strconv.Atoi(r.PostFormValue("topic-board")) - if err != nil { - return common.LocalError("The provided ForumID is not a valid number.", w, r, user) - } - - // 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.CreateTopic { - return common.NoPermissions(w, r, user) - } - - topicName := html.EscapeString(strings.Replace(r.PostFormValue("topic-name"), "\n", "", -1)) - content := common.PreparseMessage(r.PostFormValue("topic-content")) - // TODO: Fully parse the post and store it in the parsed column - tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP) - if err != nil { - switch err { - case common.ErrNoRows: - return common.LocalError("Something went wrong, perhaps the forum got deleted?", w, r, user) - case common.ErrNoTitle: - return common.LocalError("This topic doesn't have a title", w, r, user) - case common.ErrNoBody: - return common.LocalError("This topic doesn't have a body", w, r, user) - default: - return common.InternalError(err, w, r) - } - } - - _, err = stmts.addSubscription.Exec(user.ID, tid, "topic") - if err != nil { - return common.InternalError(err, w, r) - } - - err = user.IncreasePostStats(common.WordCount(content), true) - if err != nil { - return common.InternalError(err, w, r) - } - - // Handle the file attachments - // TODO: Stop duplicating this code - if user.Perms.UploadFiles { - files, ok := r.MultipartForm.File["upload_files"] - if ok { - if len(files) > 5 { - return common.LocalError("You can't attach more than five files", w, r, user) - } - - for _, file := range files { - if common.Dev.DebugMode { - log.Print("file.Filename ", file.Filename) - } - extarr := strings.Split(file.Filename, ".") - if len(extarr) < 2 { - return common.LocalError("Bad file", w, r, user) - } - ext := extarr[len(extarr)-1] - - // TODO: Can we do this without a regex? - reg, err := regexp.Compile("[^A-Za-z0-9]+") - if err != nil { - return common.LocalError("Bad file extension", w, r, user) - } - ext = strings.ToLower(reg.ReplaceAllString(ext, "")) - if !common.AllowedFileExts.Contains(ext) { - return common.LocalError("You're not allowed to upload files with this extension", w, r, user) - } - - infile, err := file.Open() - if err != nil { - return common.LocalError("Upload failed", w, r, user) - } - defer infile.Close() - - hasher := sha256.New() - _, err = io.Copy(hasher, infile) - if err != nil { - return common.LocalError("Upload failed [Hashing Failed]", w, r, user) - } - infile.Close() - - checksum := hex.EncodeToString(hasher.Sum(nil)) - filename := checksum + "." + ext - outfile, err := os.Create("." + "/attachs/" + filename) - if err != nil { - return common.LocalError("Upload failed [File Creation Failed]", w, r, user) - } - defer outfile.Close() - - infile, err = file.Open() - if err != nil { - return common.LocalError("Upload failed", w, r, user) - } - defer infile.Close() - - _, err = io.Copy(outfile, infile) - if err != nil { - return common.LocalError("Upload failed [Copy Failed]", w, r, user) - } - - _, err = stmts.addAttachment.Exec(fid, "forums", tid, "topics", user.ID, filename) - if err != nil { - return common.InternalError(err, w, r) - } - } - } - } - - common.PostCounter.Bump() - common.TopicCounter.Bump() - http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) - return nil -} - func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { tid, err := strconv.Atoi(r.PostFormValue("tid")) if err != nil { @@ -226,7 +97,7 @@ func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common. return common.LocalError("Upload failed [Copy Failed]", w, r, user) } - _, err = stmts.addAttachment.Exec(topic.ParentID, "forums", tid, "replies", user.ID, filename) + err = common.Attachments.Add(topic.ParentID, "forums", tid, "replies", user.ID, filename) if err != nil { return common.InternalError(err, w, r) } diff --git a/panel_routes.go b/panel_routes.go index 1b61829b..1bc94edd 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -43,7 +43,7 @@ func panelRenderTemplate(tmplName string, w http.ResponseWriter, r *http.Request return nil } -func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { +func routePanelDashboard(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) if ferr != nil { return ferr diff --git a/query_gen/main.go b/query_gen/main.go index 3e73dcda..72be2475 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -288,16 +288,12 @@ func writeInserts(adapter qgen.Adapter) error { build.Insert("addEmail").Table("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Parse() - build.Insert("addSubscription").Table("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Parse() - build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse() build.Insert("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse() build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse() - build.Insert("addAttachment").Table("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Parse() - build.Insert("createWordFilter").Table("word_filters").Columns("find, replacement").Fields("?,?").Parse() return nil diff --git a/router_gen/routes.go b/router_gen/routes.go index 49149be2..4dbde9da 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -65,7 +65,7 @@ func buildTopicRoutes() { topicGroup := newRouteGroup("/topic/") topicGroup.Routes( View("routeTopicID", "/topic/", "extraData"), - Action("routeCreateTopicSubmit", "/topic/create/submit/"), + UploadAction("routes.CreateTopicSubmit", "/topic/create/submit/").MaxSizeVar("common.Config.MaxRequestSize"), Action("routes.EditTopicSubmit", "/topic/edit/submit/", "extraData"), Action("routes.DeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"), Action("routes.StickTopicSubmit", "/topic/stick/submit/", "extraData"), @@ -120,7 +120,7 @@ func buildAccountRoutes() { func buildPanelRoutes() { panelGroup := newRouteGroup("/panel/").Before("SuperModOnly") panelGroup.Routes( - View("routePanel", "/panel/"), + View("routePanelDashboard", "/panel/"), View("routePanelForums", "/panel/forums/"), Action("routePanelForumsCreateSubmit", "/panel/forums/create/"), Action("routePanelForumsDelete", "/panel/forums/delete/", "extraData"), diff --git a/routes/topic.go b/routes/topic.go index 8de77a0b..4a4fe46d 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -1,11 +1,18 @@ package routes import ( + "crypto/sha256" "database/sql" + "encoding/hex" "encoding/json" + "html" + "io" "log" "net/http" + "os" + "regexp" "strconv" + "strings" "../common" ) @@ -99,6 +106,123 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid return nil } +func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + fid, err := strconv.Atoi(r.PostFormValue("topic-board")) + if err != nil { + return common.LocalError("The provided ForumID is not a valid number.", w, r, user) + } + + // 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.CreateTopic { + return common.NoPermissions(w, r, user) + } + + topicName := html.EscapeString(strings.Replace(r.PostFormValue("topic-name"), "\n", "", -1)) + content := common.PreparseMessage(r.PostFormValue("topic-content")) + // TODO: Fully parse the post and store it in the parsed column + tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP) + if err != nil { + switch err { + case common.ErrNoRows: + return common.LocalError("Something went wrong, perhaps the forum got deleted?", w, r, user) + case common.ErrNoTitle: + return common.LocalError("This topic doesn't have a title", w, r, user) + case common.ErrNoBody: + return common.LocalError("This topic doesn't have a body", w, r, user) + default: + return common.InternalError(err, w, r) + } + } + + err = common.Subscriptions.Add(user.ID, tid, "topic") + if err != nil { + return common.InternalError(err, w, r) + } + + err = user.IncreasePostStats(common.WordCount(content), true) + if err != nil { + return common.InternalError(err, w, r) + } + + // Handle the file attachments + // TODO: Stop duplicating this code + if user.Perms.UploadFiles { + files, ok := r.MultipartForm.File["upload_files"] + if ok { + if len(files) > 5 { + return common.LocalError("You can't attach more than five files", w, r, user) + } + + for _, file := range files { + if common.Dev.DebugMode { + log.Print("file.Filename ", file.Filename) + } + extarr := strings.Split(file.Filename, ".") + if len(extarr) < 2 { + return common.LocalError("Bad file", w, r, user) + } + ext := extarr[len(extarr)-1] + + // TODO: Can we do this without a regex? + reg, err := regexp.Compile("[^A-Za-z0-9]+") + if err != nil { + return common.LocalError("Bad file extension", w, r, user) + } + ext = strings.ToLower(reg.ReplaceAllString(ext, "")) + if !common.AllowedFileExts.Contains(ext) { + return common.LocalError("You're not allowed to upload files with this extension", w, r, user) + } + + infile, err := file.Open() + if err != nil { + return common.LocalError("Upload failed", w, r, user) + } + defer infile.Close() + + hasher := sha256.New() + _, err = io.Copy(hasher, infile) + if err != nil { + return common.LocalError("Upload failed [Hashing Failed]", w, r, user) + } + infile.Close() + + checksum := hex.EncodeToString(hasher.Sum(nil)) + filename := checksum + "." + ext + outfile, err := os.Create("." + "/attachs/" + filename) + if err != nil { + return common.LocalError("Upload failed [File Creation Failed]", w, r, user) + } + defer outfile.Close() + + infile, err = file.Open() + if err != nil { + return common.LocalError("Upload failed", w, r, user) + } + defer infile.Close() + + _, err = io.Copy(outfile, infile) + if err != nil { + return common.LocalError("Upload failed [Copy Failed]", w, r, user) + } + + err = common.Attachments.Add(fid, "forums", tid, "topics", user.ID, filename) + if err != nil { + return common.InternalError(err, w, r) + } + } + } + } + + common.PostCounter.Bump() + common.TopicCounter.Bump() + http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) + return nil +} + // TODO: Update the stats after edits so that we don't under or over decrement stats during deletes // TODO: Disable stat updates in posts handled by plugin_guilds func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { diff --git a/templates/panel_users.html b/templates/panel_users.html index f88adb13..d050fabc 100644 --- a/templates/panel_users.html +++ b/templates/panel_users.html @@ -10,8 +10,8 @@ {{range .ItemList}}
{{.Name}}'s Avatar - {{.Name}} - Profile + {{.Name}} + Profile {{if (.Tag) and (.IsSuperMod)}}{{.Tag}}{{end}} diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index 298939fb..06f507f1 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -642,6 +642,10 @@ textarea { .rowlist.bgavatars .rowitem { flex-direction: column; } +.rowlist.bgavatars .rowitem { + padding-top: 16px; + padding-bottom: 10px; +} .bgavatars .bgsub { border-radius: 30px; height: 48px; @@ -1060,6 +1064,28 @@ textarea { #profile_comments .content_column { margin-bottom: 16px; } +#profile_comments button { + background: inherit; + color: var(--lighter-text-color); + padding-left: 8px; + padding-right: 8px; + cursor: pointer; +} +#profile_comments button:hover { + color: var(--light-text-color); +} +#profile_comments button.edit_item:after { + font: normal normal normal 14px/1 FontAwesome; + content: "\f040"; +} +#profile_comments button.delete_item:after { + font: normal normal normal 14px/1 FontAwesome; + content: "\f1f8"; +} +#profile_comments button.report_item:after { + font: normal normal normal 14px/1 FontAwesome; + content: "\f024"; +} #profile_comments_head { margin-top: 6px; } @@ -1167,7 +1193,7 @@ textarea { /* TODO: Highlight the one we're currently on? */ .pageset { display: flex; - margin-left: 16px; + margin-left: 14px; } .pageitem { padding: 8px; diff --git a/themes/cosora/public/panel.css b/themes/cosora/public/panel.css index 1d6ea155..da44b372 100644 --- a/themes/cosora/public/panel.css +++ b/themes/cosora/public/panel.css @@ -91,6 +91,9 @@ #panel_users .panel_tag:not(.panel_right_button) { display: none; } +.panel_right_button + .panel_right_button { + margin-left: 3px; +} .panel_group_perms .formitem a { margin-top: 5px; @@ -128,6 +131,12 @@ display: block; } +#panel_users .rowitem .rowTitle { + border-bottom: 1px solid var(--lighter-text-color); + padding-bottom: 4px; + margin-bottom: 4px; +} + #forum_quick_perms .formitem { display: flex; } @@ -189,4 +198,8 @@ padding: 14px; font-size: 16px; } -*/ \ No newline at end of file +*/ + +.pageset { + margin-left: 16px; +} \ No newline at end of file