From 2997135e803886906c14a657430a88ab16254061 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sat, 20 Jan 2018 06:50:29 +0000 Subject: [PATCH] Added proper pagination to the topic list. Fixed two existence checks. Tweaked the profile CSS for Cosora. Added the TopicByReplyID function. Split off the profile logic from Reply into ProfileReply. Moved various hard-coded bits in the profile reply routes into ProfileReply. Moved four reply routes into /routes/reply.go Moved six topic routes into /routes/topic.go We should now capture more suspicious activity. Changed the definition of the revisions table. --- alerts.go | 4 +- common/pages.go | 3 + common/parser.go | 6 +- common/profile_reply.go | 58 ++++ common/profile_reply_store.go | 6 +- common/reply.go | 29 +- common/template_init.go | 2 +- common/topic.go | 9 + gen_mssql.go | 32 -- gen_mysql.go | 28 -- gen_pgsql.go | 7 - gen_router.go | 137 ++++---- mod_routes.go | 498 ---------------------------- panel_routes.go | 2 +- query_gen/main.go | 8 - query_gen/tables.go | 3 +- router_gen/main.go | 57 ++-- router_gen/routes.go | 20 +- routes.go | 16 +- routes/reply.go | 196 +++++++++++ routes/topic.go | 316 ++++++++++++++++++ schema/mssql/query_revisions.sql | 5 +- schema/mysql/query_revisions.sql | 5 +- schema/pgsql/query_revisions.sql | 5 +- template_list.go | 62 ++-- template_profile.go | 20 +- template_topics.go | 24 ++ templates/profile_comments_row.html | 20 +- templates/topics.html | 10 + themes/cosora/public/main.css | 10 + 30 files changed, 851 insertions(+), 747 deletions(-) create mode 100644 common/profile_reply.go create mode 100644 routes/reply.go diff --git a/alerts.go b/alerts.go index dbc920d5..bdf5b664 100644 --- a/alerts.go +++ b/alerts.go @@ -96,9 +96,7 @@ func buildAlert(asid int, event string, elementType string, actorID int, targetU endFrag = "'s profile" url = targetUser.Link case "post": - reply := common.BlankReply() - reply.ID = elementID - topic, err := reply.Topic() + topic, err := common.TopicByReplyID(elementID) if err != nil { return "", errors.New("Unable to find the linked reply or parent topic") } diff --git a/common/pages.go b/common/pages.go index a9634c4d..c72ee81e 100644 --- a/common/pages.go +++ b/common/pages.go @@ -67,6 +67,9 @@ type TopicsPage struct { TopicList []*TopicsRow ForumList []Forum DefaultForum int + PageList []int + Page int + LastPage int } type ForumPage struct { diff --git a/common/parser.go b/common/parser.go index ad053d65..e4c33137 100644 --- a/common/parser.go +++ b/common/parser.go @@ -177,7 +177,7 @@ func PreparseMessage(msg string) string { msg = RunSshook("preparse_preassign", msg) } msg = html.EscapeString(msg) - msg = strings.Replace(msg," ","",-1) + msg = strings.Replace(msg, " ", "", -1) var runes = []rune(msg) msg = "" @@ -353,9 +353,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/) rid, intLen := CoerceIntBytes(msgbytes[start:]) i += intLen - reply := BlankReply() - reply.ID = rid - topic, err := reply.Topic() + topic, err := TopicByReplyID(rid) if err != nil || !Forums.Exists(topic.ParentID) { outbytes = append(outbytes, InvalidTopic...) lastItem = i diff --git a/common/profile_reply.go b/common/profile_reply.go new file mode 100644 index 00000000..ab56f463 --- /dev/null +++ b/common/profile_reply.go @@ -0,0 +1,58 @@ +package common + +import ( + "database/sql" + "html" + "time" + + "../query_gen/lib" +) + +var profileReplyStmts ProfileReplyStmts + +type ProfileReply struct { + ID int + ParentID int + Content string + CreatedBy int + Group int + CreatedAt time.Time + RelativeCreatedAt string + LastEdit int + LastEditBy int + ContentLines int + IPAddress string +} + +type ProfileReplyStmts struct { + edit *sql.Stmt + delete *sql.Stmt +} + +func init() { + DbInits.Add(func(acc *qgen.Accumulator) error { + profileReplyStmts = ProfileReplyStmts{ + edit: acc.Update("users_replies").Set("content = ?, parsed_content = ?").Where("rid = ?").Prepare(), + delete: acc.Delete("users_replies").Where("rid = ?").Prepare(), + } + return acc.FirstError() + }) +} + +// TODO: Write tests for this +func (reply *ProfileReply) Delete() error { + _, err := profileReplyStmts.delete.Exec(reply.ID) + return err +} + +func (reply *ProfileReply) SetBody(content string) error { + content = PreparseMessage(html.UnescapeString(content)) + parsedContent := ParseMessage(content, 0, "") + _, err := profileReplyStmts.edit.Exec(content, parsedContent, reply.ID) + return err +} + +// TODO: We can get this from the topic store instead of a query which will always miss the cache... +func (reply *ProfileReply) Creator() (*User, error) { + return Users.Get(reply.CreatedBy) +} diff --git a/common/profile_reply_store.go b/common/profile_reply_store.go index 3a79a3a1..185f371f 100644 --- a/common/profile_reply_store.go +++ b/common/profile_reply_store.go @@ -9,7 +9,7 @@ import ( var Prstore ProfileReplyStore type ProfileReplyStore interface { - Get(id int) (*Reply, error) + Get(id int) (*ProfileReply, error) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error) } @@ -28,8 +28,8 @@ func NewSQLProfileReplyStore() (*SQLProfileReplyStore, error) { }, acc.FirstError() } -func (store *SQLProfileReplyStore) Get(id int) (*Reply, error) { - reply := Reply{ID: id} +func (store *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) { + reply := ProfileReply{ID: id} err := store.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress) return &reply, err } diff --git a/common/reply.go b/common/reply.go index 0ec1cfcf..55229c65 100644 --- a/common/reply.go +++ b/common/reply.go @@ -9,6 +9,7 @@ package common import ( "database/sql" "errors" + "html" "time" "../query_gen/lib" @@ -64,10 +65,10 @@ var replyStmts ReplyStmts type ReplyStmts struct { isLiked *sql.Stmt createLike *sql.Stmt + edit *sql.Stmt delete *sql.Stmt addLikesToReply *sql.Stmt removeRepliesFromTopic *sql.Stmt - getParent *sql.Stmt } func init() { @@ -75,10 +76,10 @@ func init() { replyStmts = ReplyStmts{ isLiked: acc.Select("likes").Columns("targetItem").Where("sentBy = ? and targetItem = ? and targetType = 'replies'").Prepare(), createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy").Fields("?,?,?,?").Prepare(), + edit: acc.Update("replies").Set("content = ?, parsed_content = ?").Where("rid = ?").Prepare(), delete: acc.Delete("replies").Where("rid = ?").Prepare(), addLikesToReply: acc.Update("replies").Set("likeCount = likeCount + ?").Where("rid = ?").Prepare(), removeRepliesFromTopic: acc.Update("topics").Set("postCount = postCount - ?").Where("tid = ?").Prepare(), - getParent: acc.SimpleLeftJoin("replies", "topics", "topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, topics.data", "replies.tid = topics.tid", "rid = ?", "", ""), } return acc.FirstError() }) @@ -119,22 +120,22 @@ func (reply *Reply) Delete() error { return err } +func (reply *Reply) SetBody(content string) error { + topic, err := reply.Topic() + if err != nil { + return err + } + content = PreparseMessage(html.UnescapeString(content)) + parsedContent := ParseMessage(content, topic.ParentID, "forums") + _, err = replyStmts.edit.Exec(content, parsedContent, reply.ID) + return err +} + func (reply *Reply) Topic() (*Topic, error) { - topic := Topic{ID: 0} - err := replyStmts.getParent.QueryRow(reply.ID).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) - topic.Link = BuildTopicURL(NameToSlug(topic.Title), topic.ID) - return &topic, err + return Topics.Get(reply.ParentID) } // Copy gives you a non-pointer concurrency safe copy of the reply func (reply *Reply) Copy() Reply { return *reply } - -func BlankReply(ids ...int) *Reply { - var id int - if len(ids) != 0 { - id = ids[0] - } - return &Reply{ID: id} -} diff --git a/common/template_init.go b/common/template_init.go index dc8ec8d7..b39443d8 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -149,7 +149,7 @@ func compileTemplates() error { var topicsList []*TopicsRow topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, "Date", time.Now(), "Date", user3.ID, 1, "", "127.0.0.1", 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"}) - topicsPage := TopicsPage{"Topic List", user, headerVars, topicsList, forumList, Config.DefaultForum} + topicsPage := TopicsPage{"Topic List", user, headerVars, topicsList, forumList, Config.DefaultForum, []int{1}, 1, 1} topicsTmpl, err := c.Compile("topics.html", "templates/", "common.TopicsPage", topicsPage, varList) if err != nil { return err diff --git a/common/topic.go b/common/topic.go index 64cca91e..2da01d50 100644 --- a/common/topic.go +++ b/common/topic.go @@ -123,6 +123,7 @@ type TopicStmts struct { createActionReply *sql.Stmt getTopicUser *sql.Stmt // TODO: Can we get rid of this? + getByReplyID *sql.Stmt } var topicStmts TopicStmts @@ -144,6 +145,7 @@ func init() { createActionReply: acc.Insert("replies").Columns("tid, actionType, ipaddress, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(), getTopicUser: acc.SimpleLeftJoin("topics", "users", "topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level", "topics.createdBy = users.uid", "tid = ?", "", ""), + getByReplyID: acc.SimpleLeftJoin("replies", "topics", "topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, topics.data", "replies.tid = topics.tid", "rid = ?", "", ""), } return acc.FirstError() }) @@ -272,6 +274,13 @@ func (topic *Topic) Copy() Topic { return *topic } +func TopicByReplyID(rid int) (*Topic, error) { + topic := Topic{ID: 0} + err := topicStmts.getByReplyID.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) + topic.Link = BuildTopicURL(NameToSlug(topic.Title), topic.ID) + return &topic, err +} + // TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser func GetTopicUser(tid int) (TopicUser, error) { tcache := Topics.GetCache() diff --git a/gen_mssql.go b/gen_mssql.go index 881f07f6..eb618180 100644 --- a/gen_mssql.go +++ b/gen_mssql.go @@ -16,9 +16,7 @@ type Stmts struct { getModlogs *sql.Stmt getModlogsOffset *sql.Stmt getAdminlogsOffset *sql.Stmt - getReplyTID *sql.Stmt getTopicFID *sql.Stmt - getUserReplyUID *sql.Stmt getUserName *sql.Stmt getEmailsByUser *sql.Stmt getTopicBasic *sql.Stmt @@ -44,7 +42,6 @@ type Stmts struct { addAttachment *sql.Stmt createWordFilter *sql.Stmt editReply *sql.Stmt - editProfileReply *sql.Stmt updatePlugin *sql.Stmt updatePluginInstall *sql.Stmt updateTheme *sql.Stmt @@ -56,7 +53,6 @@ type Stmts struct { setTempGroup *sql.Stmt updateWordFilter *sql.Stmt bumpSync *sql.Stmt - deleteProfileReply *sql.Stmt deleteActivityStreamMatch *sql.Stmt deleteWordFilter *sql.Stmt reportExists *sql.Stmt @@ -130,13 +126,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing getReplyTID statement.") - stmts.getReplyTID, err = db.Prepare("SELECT [tid] FROM [replies] WHERE [rid] = ?1") - if err != nil { - log.Print("Bad Query: ","SELECT [tid] FROM [replies] WHERE [rid] = ?1") - return err - } - log.Print("Preparing getTopicFID statement.") stmts.getTopicFID, err = db.Prepare("SELECT [parentID] FROM [topics] WHERE [tid] = ?1") if err != nil { @@ -144,13 +133,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing getUserReplyUID statement.") - stmts.getUserReplyUID, err = db.Prepare("SELECT [uid] FROM [users_replies] WHERE [rid] = ?1") - if err != nil { - log.Print("Bad Query: ","SELECT [uid] FROM [users_replies] WHERE [rid] = ?1") - return err - } - log.Print("Preparing getUserName statement.") stmts.getUserName, err = db.Prepare("SELECT [name] FROM [users] WHERE [uid] = ?1") if err != nil { @@ -326,13 +308,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing editProfileReply statement.") - stmts.editProfileReply, err = db.Prepare("UPDATE [users_replies] SET [content] = ?,[parsed_content] = ? WHERE [rid] = ?") - if err != nil { - log.Print("Bad Query: ","UPDATE [users_replies] SET [content] = ?,[parsed_content] = ? WHERE [rid] = ?") - return err - } - log.Print("Preparing updatePlugin statement.") stmts.updatePlugin, err = db.Prepare("UPDATE [plugins] SET [active] = ? WHERE [uname] = ?") if err != nil { @@ -410,13 +385,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing deleteProfileReply statement.") - stmts.deleteProfileReply, err = db.Prepare("DELETE FROM [users_replies] WHERE [rid] = ?") - if err != nil { - log.Print("Bad Query: ","DELETE FROM [users_replies] WHERE [rid] = ?") - return err - } - log.Print("Preparing deleteActivityStreamMatch statement.") stmts.deleteActivityStreamMatch, err = db.Prepare("DELETE FROM [activity_stream_matches] WHERE [watcher] = ? AND [asid] = ?") if err != nil { diff --git a/gen_mysql.go b/gen_mysql.go index caffba4d..5297a126 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -18,9 +18,7 @@ type Stmts struct { getModlogs *sql.Stmt getModlogsOffset *sql.Stmt getAdminlogsOffset *sql.Stmt - getReplyTID *sql.Stmt getTopicFID *sql.Stmt - getUserReplyUID *sql.Stmt getUserName *sql.Stmt getEmailsByUser *sql.Stmt getTopicBasic *sql.Stmt @@ -46,7 +44,6 @@ type Stmts struct { addAttachment *sql.Stmt createWordFilter *sql.Stmt editReply *sql.Stmt - editProfileReply *sql.Stmt updatePlugin *sql.Stmt updatePluginInstall *sql.Stmt updateTheme *sql.Stmt @@ -58,7 +55,6 @@ type Stmts struct { setTempGroup *sql.Stmt updateWordFilter *sql.Stmt bumpSync *sql.Stmt - deleteProfileReply *sql.Stmt deleteActivityStreamMatch *sql.Stmt deleteWordFilter *sql.Stmt reportExists *sql.Stmt @@ -125,24 +121,12 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing getReplyTID statement.") - stmts.getReplyTID, err = db.Prepare("SELECT `tid` FROM `replies` WHERE `rid` = ?") - if err != nil { - return err - } - log.Print("Preparing getTopicFID statement.") stmts.getTopicFID, err = db.Prepare("SELECT `parentID` FROM `topics` WHERE `tid` = ?") if err != nil { return err } - log.Print("Preparing getUserReplyUID statement.") - stmts.getUserReplyUID, err = db.Prepare("SELECT `uid` FROM `users_replies` WHERE `rid` = ?") - if err != nil { - return err - } - log.Print("Preparing getUserName statement.") stmts.getUserName, err = db.Prepare("SELECT `name` FROM `users` WHERE `uid` = ?") if err != nil { @@ -293,12 +277,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing editProfileReply statement.") - stmts.editProfileReply, err = db.Prepare("UPDATE `users_replies` SET `content` = ?,`parsed_content` = ? WHERE `rid` = ?") - if err != nil { - return err - } - log.Print("Preparing updatePlugin statement.") stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?") if err != nil { @@ -365,12 +343,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing deleteProfileReply statement.") - stmts.deleteProfileReply, err = db.Prepare("DELETE FROM `users_replies` WHERE `rid` = ?") - if err != nil { - return err - } - log.Print("Preparing deleteActivityStreamMatch statement.") stmts.deleteActivityStreamMatch, err = db.Prepare("DELETE FROM `activity_stream_matches` WHERE `watcher` = ? AND `asid` = ?") if err != nil { diff --git a/gen_pgsql.go b/gen_pgsql.go index c7dc2749..0db9f8ac 100644 --- a/gen_pgsql.go +++ b/gen_pgsql.go @@ -10,7 +10,6 @@ import "./common" // nolint type Stmts struct { editReply *sql.Stmt - editProfileReply *sql.Stmt updatePlugin *sql.Stmt updatePluginInstall *sql.Stmt updateTheme *sql.Stmt @@ -48,12 +47,6 @@ func _gen_pgsql() (err error) { return err } - log.Print("Preparing editProfileReply statement.") - stmts.editProfileReply, err = db.Prepare("UPDATE `users_replies` SET `content` = ?,`parsed_content` = ? WHERE `rid` = ?") - if err != nil { - return err - } - log.Print("Preparing updatePlugin statement.") stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?") if err != nil { diff --git a/gen_router.go b/gen_router.go index d39d02ae..59aeb0dd 100644 --- a/gen_router.go +++ b/gen_router.go @@ -85,21 +85,21 @@ var RouteMap = map[string]interface{}{ "routeIps": routeIps, "routeTopicCreateSubmit": routeTopicCreateSubmit, "routes.EditTopicSubmit": routes.EditTopicSubmit, - "routeDeleteTopicSubmit": routeDeleteTopicSubmit, - "routeStickTopicSubmit": routeStickTopicSubmit, - "routeUnstickTopicSubmit": routeUnstickTopicSubmit, - "routeLockTopicSubmit": routeLockTopicSubmit, - "routeUnlockTopicSubmit": routeUnlockTopicSubmit, - "routeMoveTopicSubmit": routeMoveTopicSubmit, + "routes.DeleteTopicSubmit": routes.DeleteTopicSubmit, + "routes.StickTopicSubmit": routes.StickTopicSubmit, + "routes.UnstickTopicSubmit": routes.UnstickTopicSubmit, + "routes.LockTopicSubmit": routes.LockTopicSubmit, + "routes.UnlockTopicSubmit": routes.UnlockTopicSubmit, + "routes.MoveTopicSubmit": routes.MoveTopicSubmit, "routeLikeTopicSubmit": routeLikeTopicSubmit, "routeTopicID": routeTopicID, "routeCreateReplySubmit": routeCreateReplySubmit, - "routeReplyEditSubmit": routeReplyEditSubmit, - "routeReplyDeleteSubmit": routeReplyDeleteSubmit, + "routes.ReplyEditSubmit": routes.ReplyEditSubmit, + "routes.ReplyDeleteSubmit": routes.ReplyDeleteSubmit, "routeReplyLikeSubmit": routeReplyLikeSubmit, "routeProfileReplyCreateSubmit": routeProfileReplyCreateSubmit, - "routeProfileReplyEditSubmit": routeProfileReplyEditSubmit, - "routeProfileReplyDeleteSubmit": routeProfileReplyDeleteSubmit, + "routes.ProfileReplyEditSubmit": routes.ProfileReplyEditSubmit, + "routes.ProfileReplyDeleteSubmit": routes.ProfileReplyDeleteSubmit, "routeLogin": routeLogin, "routeRegister": routeRegister, "routeLogout": routeLogout, @@ -180,21 +180,21 @@ var routeMapEnum = map[string]int{ "routeIps": 66, "routeTopicCreateSubmit": 67, "routes.EditTopicSubmit": 68, - "routeDeleteTopicSubmit": 69, - "routeStickTopicSubmit": 70, - "routeUnstickTopicSubmit": 71, - "routeLockTopicSubmit": 72, - "routeUnlockTopicSubmit": 73, - "routeMoveTopicSubmit": 74, + "routes.DeleteTopicSubmit": 69, + "routes.StickTopicSubmit": 70, + "routes.UnstickTopicSubmit": 71, + "routes.LockTopicSubmit": 72, + "routes.UnlockTopicSubmit": 73, + "routes.MoveTopicSubmit": 74, "routeLikeTopicSubmit": 75, "routeTopicID": 76, "routeCreateReplySubmit": 77, - "routeReplyEditSubmit": 78, - "routeReplyDeleteSubmit": 79, + "routes.ReplyEditSubmit": 78, + "routes.ReplyDeleteSubmit": 79, "routeReplyLikeSubmit": 80, "routeProfileReplyCreateSubmit": 81, - "routeProfileReplyEditSubmit": 82, - "routeProfileReplyDeleteSubmit": 83, + "routes.ProfileReplyEditSubmit": 82, + "routes.ProfileReplyDeleteSubmit": 83, "routeLogin": 84, "routeRegister": 85, "routeLogout": 86, @@ -273,21 +273,21 @@ var reverseRouteMapEnum = map[int]string{ 66: "routeIps", 67: "routeTopicCreateSubmit", 68: "routes.EditTopicSubmit", - 69: "routeDeleteTopicSubmit", - 70: "routeStickTopicSubmit", - 71: "routeUnstickTopicSubmit", - 72: "routeLockTopicSubmit", - 73: "routeUnlockTopicSubmit", - 74: "routeMoveTopicSubmit", + 69: "routes.DeleteTopicSubmit", + 70: "routes.StickTopicSubmit", + 71: "routes.UnstickTopicSubmit", + 72: "routes.LockTopicSubmit", + 73: "routes.UnlockTopicSubmit", + 74: "routes.MoveTopicSubmit", 75: "routeLikeTopicSubmit", 76: "routeTopicID", 77: "routeCreateReplySubmit", - 78: "routeReplyEditSubmit", - 79: "routeReplyDeleteSubmit", + 78: "routes.ReplyEditSubmit", + 79: "routes.ReplyDeleteSubmit", 80: "routeReplyLikeSubmit", 81: "routeProfileReplyCreateSubmit", - 82: "routeProfileReplyEditSubmit", - 83: "routeProfileReplyDeleteSubmit", + 82: "routes.ProfileReplyEditSubmit", + 83: "routes.ProfileReplyDeleteSubmit", 84: "routeLogin", 85: "routeRegister", 86: "routeLogout", @@ -395,25 +395,6 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' { w.WriteHeader(405) w.Write([]byte("")) - return - } - - // TODO: Cover more suspicious strings and at a lower layer than this - for _, char := range req.URL.Path { - if char != '&' && !(char > 44 && char < 58) && char != '=' && char != '?' && !(char > 64 && char < 91) && char != '\\' && char != '_' && !(char > 96 && char < 123) { - log.Print("Suspicious UA: ", req.UserAgent()) - log.Print("Method: ", req.Method) - for key, value := range req.Header { - for _, vvalue := range value { - log.Print("Header '" + key + "': " + vvalue + "!!") - } - } - log.Print("req.URL.Path: ", req.URL.Path) - log.Print("req.Referer(): ", req.Referer()) - log.Print("req.RemoteAddr: ", req.RemoteAddr) - } - } - if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") { log.Print("Suspicious UA: ", req.UserAgent()) log.Print("Method: ", req.Method) for key, value := range req.Header { @@ -422,8 +403,43 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) + return + } + + if common.Dev.DebugMode { + // TODO: Cover more suspicious strings and at a lower layer than this + for _, char := range req.URL.Path { + if char != '&' && !(char > 44 && char < 58) && char != '=' && char != '?' && !(char > 64 && char < 91) && char != '\\' && char != '_' && !(char > 96 && char < 123) { + log.Print("Suspicious UA: ", req.UserAgent()) + log.Print("Method: ", req.Method) + for key, value := range req.Header { + for _, vvalue := range value { + log.Print("Header '" + key + "': " + vvalue + "!!") + } + } + log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) + log.Print("req.Referer(): ", req.Referer()) + log.Print("req.RemoteAddr: ", req.RemoteAddr) + break + } + } + if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") { + log.Print("Suspicious UA: ", req.UserAgent()) + log.Print("Method: ", req.Method) + for key, value := range req.Header { + for _, vvalue := range value { + log.Print("Header '" + key + "': " + vvalue + "!!") + } + } + log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) + log.Print("req.Referer(): ", req.Referer()) + log.Print("req.RemoteAddr: ", req.RemoteAddr) + } } var prefix, extraData string @@ -443,6 +459,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } log.Print("prefix: ", prefix) log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) @@ -506,6 +523,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } log.Print("prefix: ", prefix) log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) @@ -522,6 +540,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } log.Print("prefix: ", prefix) log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) @@ -1155,7 +1174,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { req.URL.Path += extraData common.RouteViewCounter.Bump(69) - err = routeDeleteTopicSubmit(w,req,user) + err = routes.DeleteTopicSubmit(w,req,user) case "/topic/stick/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1170,7 +1189,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } common.RouteViewCounter.Bump(70) - err = routeStickTopicSubmit(w,req,user,extraData) + err = routes.StickTopicSubmit(w,req,user,extraData) case "/topic/unstick/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1185,7 +1204,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } common.RouteViewCounter.Bump(71) - err = routeUnstickTopicSubmit(w,req,user,extraData) + err = routes.UnstickTopicSubmit(w,req,user,extraData) case "/topic/lock/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1201,7 +1220,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { req.URL.Path += extraData common.RouteViewCounter.Bump(72) - err = routeLockTopicSubmit(w,req,user) + err = routes.LockTopicSubmit(w,req,user) case "/topic/unlock/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1216,7 +1235,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } common.RouteViewCounter.Bump(73) - err = routeUnlockTopicSubmit(w,req,user,extraData) + err = routes.UnlockTopicSubmit(w,req,user,extraData) case "/topic/move/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1231,7 +1250,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } common.RouteViewCounter.Bump(74) - err = routeMoveTopicSubmit(w,req,user,extraData) + err = routes.MoveTopicSubmit(w,req,user,extraData) case "/topic/like/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1290,7 +1309,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } common.RouteViewCounter.Bump(78) - err = routeReplyEditSubmit(w,req,user,extraData) + err = routes.ReplyEditSubmit(w,req,user,extraData) case "/reply/delete/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1305,7 +1324,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } common.RouteViewCounter.Bump(79) - err = routeReplyDeleteSubmit(w,req,user,extraData) + err = routes.ReplyDeleteSubmit(w,req,user,extraData) case "/reply/like/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1356,7 +1375,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } common.RouteViewCounter.Bump(82) - err = routeProfileReplyEditSubmit(w,req,user,extraData) + err = routes.ProfileReplyEditSubmit(w,req,user,extraData) case "/profile/reply/delete/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1371,7 +1390,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } common.RouteViewCounter.Bump(83) - err = routeProfileReplyDeleteSubmit(w,req,user,extraData) + err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) } if err != nil { router.handleError(err,w,req,user) diff --git a/mod_routes.go b/mod_routes.go index 2382fad5..9d5b9b00 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -1,9 +1,6 @@ package main import ( - "encoding/json" - "html" - "log" "net/http" "strconv" "time" @@ -11,501 +8,6 @@ import ( "./common" ) -// TODO: Add support for soft-deletion and add a permission for hard delete in addition to the usual -// TODO: Disable stat updates in posts handled by plugin_guilds -func routeDeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - // TODO: Move this to some sort of middleware - var tids []int - var isJs = false - if common.ReqIsJson(r) { - if r.Body == nil { - return common.PreErrorJS("No request body", w, r) - } - //log.Print("r.Body: ", r.Body) - err := json.NewDecoder(r.Body).Decode(&tids) - if err != nil { - //log.Print("parse err: ", err) - return common.PreErrorJS("We weren't able to parse your data", w, r) - } - isJs = true - } else { - tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):]) - if err != nil { - return common.PreError("The provided TopicID is not a valid number.", w, r) - } - tids = append(tids, tid) - } - if len(tids) == 0 { - return common.LocalErrorJSQ("You haven't provided any IDs", w, r, user, isJs) - } - - for _, tid := range tids { - topic, err := common.Topics.Get(tid) - if err == ErrNoRows { - return common.PreErrorJSQ("The topic you tried to delete 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.DeleteTopic { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - // We might be able to handle this err better - err = topic.Delete() - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - err = common.ModLogs.Create("delete", tid, "topic", user.LastIP, user.ID) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - // ? - We might need to add soft-delete before we can do an action reply for this - /*_, err = stmts.createActionReply.Exec(tid,"delete",ipaddress,user.ID) - if err != nil { - return common.InternalErrorJSQ(err,w,r,isJs) - }*/ - - log.Printf("Topic #%d was deleted by common.User #%d", tid, user.ID) - } - http.Redirect(w, r, "/", http.StatusSeeOther) - return nil -} - -func routeStickTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { - tid, err := strconv.Atoi(stid) - if err != nil { - return common.PreError("The provided TopicID is not a valid number.", w, r) - } - - topic, err := common.Topics.Get(tid) - if err == ErrNoRows { - return common.PreError("The topic you tried to pin doesn't exist.", w, r) - } else if err != nil { - return common.InternalError(err, w, r) - } - - // 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.PinTopic { - return common.NoPermissions(w, r, user) - } - - err = topic.Stick() - if err != nil { - return common.InternalError(err, w, r) - } - - err = common.ModLogs.Create("stick", tid, "topic", user.LastIP, user.ID) - if err != nil { - return common.InternalError(err, w, r) - } - err = topic.CreateActionReply("stick", user.LastIP, user) - if err != nil { - return common.InternalError(err, w, r) - } - http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) - return nil -} - -func routeUnstickTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { - tid, err := strconv.Atoi(stid) - if err != nil { - return common.PreError("The provided TopicID is not a valid number.", w, r) - } - - topic, err := common.Topics.Get(tid) - if err == ErrNoRows { - return common.PreError("The topic you tried to unpin doesn't exist.", w, r) - } else if err != nil { - return common.InternalError(err, w, r) - } - - // 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.PinTopic { - return common.NoPermissions(w, r, user) - } - - err = topic.Unstick() - if err != nil { - return common.InternalError(err, w, r) - } - - err = common.ModLogs.Create("unstick", tid, "topic", user.LastIP, user.ID) - if err != nil { - return common.InternalError(err, w, r) - } - err = topic.CreateActionReply("unstick", user.LastIP, user) - if err != nil { - return common.InternalError(err, w, r) - } - - http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) - return nil -} - -func routeLockTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - // TODO: Move this to some sort of middleware - var tids []int - var isJs = false - if common.ReqIsJson(r) { - if r.Body == nil { - return common.PreErrorJS("No request body", w, r) - } - err := json.NewDecoder(r.Body).Decode(&tids) - if err != nil { - return common.PreErrorJS("We weren't able to parse your data", w, r) - } - isJs = true - } else { - tid, err := strconv.Atoi(r.URL.Path[len("/topic/lock/submit/"):]) - if err != nil { - return common.PreError("The provided TopicID is not a valid number.", w, r) - } - tids = append(tids, tid) - } - if len(tids) == 0 { - return common.LocalErrorJSQ("You haven't provided any IDs", w, r, user, isJs) - } - - for _, tid := range tids { - topic, err := common.Topics.Get(tid) - if err == ErrNoRows { - return common.PreErrorJSQ("The topic you tried to lock 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.CloseTopic { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - err = topic.Lock() - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - err = common.ModLogs.Create("lock", tid, "topic", user.LastIP, user.ID) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - err = topic.CreateActionReply("lock", user.LastIP, user) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - } - - if len(tids) == 1 { - http.Redirect(w, r, "/topic/"+strconv.Itoa(tids[0]), http.StatusSeeOther) - } - return nil -} - -func routeUnlockTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { - tid, err := strconv.Atoi(stid) - if err != nil { - return common.PreError("The provided TopicID is not a valid number.", w, r) - } - - topic, err := common.Topics.Get(tid) - if err == ErrNoRows { - return common.PreError("The topic you tried to unlock doesn't exist.", w, r) - } else if err != nil { - return common.InternalError(err, w, r) - } - - // 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.CloseTopic { - return common.NoPermissions(w, r, user) - } - - err = topic.Unlock() - if err != nil { - return common.InternalError(err, w, r) - } - - err = common.ModLogs.Create("unlock", tid, "topic", user.LastIP, user.ID) - if err != nil { - return common.InternalError(err, w, r) - } - err = topic.CreateActionReply("unlock", user.LastIP, user) - if err != nil { - return common.InternalError(err, w, r) - } - - http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) - return nil -} - -// ! JS only route -// TODO: Figure a way to get this route to work without JS -func routeMoveTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError { - fid, err := strconv.Atoi(sfid) - if err != nil { - return common.PreErrorJS("The provided Forum ID is not a valid number.", w, r) - } - - // TODO: Move this to some sort of middleware - var tids []int - if r.Body == nil { - return common.PreErrorJS("No request body", w, r) - } - err = json.NewDecoder(r.Body).Decode(&tids) - if err != nil { - return common.PreErrorJS("We weren't able to parse your data", w, r) - } - if len(tids) == 0 { - return common.LocalErrorJS("You haven't provided any IDs", w, r) - } - - for _, tid := range tids { - topic, err := common.Topics.Get(tid) - if err == ErrNoRows { - return common.PreErrorJS("The topic you tried to move doesn't exist.", w, r) - } else if err != nil { - return common.InternalErrorJS(err, w, r) - } - - // 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.MoveTopic { - return common.NoPermissionsJS(w, r, user) - } - _, ferr = common.SimpleForumUserCheck(w, r, &user, fid) - if ferr != nil { - return ferr - } - if !user.Perms.ViewTopic || !user.Perms.MoveTopic { - return common.NoPermissionsJS(w, r, user) - } - - err = topic.MoveTo(fid) - if err != nil { - return common.InternalErrorJS(err, w, r) - } - - // TODO: Log more data so we can list the destination forum in the action post? - err = common.ModLogs.Create("move", tid, "topic", user.LastIP, user.ID) - if err != nil { - return common.InternalErrorJS(err, w, r) - } - err = topic.CreateActionReply("move", user.LastIP, user) - if err != nil { - return common.InternalErrorJS(err, w, r) - } - } - - if len(tids) == 1 { - http.Redirect(w, r, "/topic/"+strconv.Itoa(tids[0]), http.StatusSeeOther) - } - return nil -} - -// TODO: Disable stat updates in posts handled by plugin_guilds -// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes -func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { - isJs := (r.PostFormValue("js") == "1") - - rid, err := strconv.Atoi(srid) - if err != nil { - return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs) - } - - // Get the Reply ID.. - var tid int - err = stmts.getReplyTID.QueryRow(rid).Scan(&tid) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - var fid int - err = stmts.getTopicFID.QueryRow(tid).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.EditReply { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - content := common.PreparseMessage(html.UnescapeString(r.PostFormValue("edit_item"))) - _, err = stmts.editReply.Exec(content, common.ParseMessage(content, fid, "forums"), rid) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - if !isJs { - http.Redirect(w, r, "/topic/"+strconv.Itoa(tid)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther) - } else { - w.Write(successJSONBytes) - } - return nil -} - -// TODO: Refactor this -// TODO: Disable stat updates in posts handled by plugin_guilds -func routeReplyDeleteSubmit(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("The reply you tried to delete 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.DeleteReply { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - err = reply.Delete() - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - //log.Printf("Reply #%d was deleted by common.User #%d", rid, user.ID) - if !isJs { - http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther) - } else { - w.Write(successJSONBytes) - } - - replyCreator, err := common.Users.Get(reply.CreatedBy) - if err == nil { - wcount := common.WordCount(reply.Content) - err = replyCreator.DecreasePostStats(wcount, false) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - } else if err != ErrNoRows { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - err = common.ModLogs.Create("delete", reply.ParentID, "reply", user.LastIP, user.ID) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - return nil -} - -func routeProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { - isJs := (r.PostFormValue("js") == "1") - - rid, err := strconv.Atoi(srid) - if err != nil { - return common.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, isJs) - } - - // Get the Reply ID.. - var uid int - err = stmts.getUserReplyUID.QueryRow(rid).Scan(&uid) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - if user.ID != uid && !user.Perms.EditReply { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - content := common.PreparseMessage(html.UnescapeString(r.PostFormValue("edit_item"))) - _, err = stmts.editProfileReply.Exec(content, common.ParseMessage(content, 0, ""), rid) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - if !isJs { - http.Redirect(w, r, "/user/"+strconv.Itoa(uid)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther) - } else { - w.Write(successJSONBytes) - } - return nil -} - -func routeProfileReplyDeleteSubmit(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.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, isJs) - } - - var uid int - err = stmts.getUserReplyUID.QueryRow(rid).Scan(&uid) - if err == ErrNoRows { - return common.LocalErrorJSQ("The reply you tried to delete doesn't exist.", w, r, user, isJs) - } else if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - if user.ID != uid && !user.Perms.DeleteReply { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - _, err = stmts.deleteProfileReply.Exec(rid) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - //log.Printf("The profile post '%d' was deleted by common.User #%d", rid, user.ID) - - if !isJs { - //http.Redirect(w,r, "/user/" + strconv.Itoa(uid), http.StatusSeeOther) - } else { - w.Write(successJSONBytes) - } - return nil -} - func routeIps(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { headerVars, ferr := common.UserCheck(w, r, &user) if ferr != nil { diff --git a/panel_routes.go b/panel_routes.go index 4d28336f..1b61829b 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -2236,7 +2236,7 @@ func modlogsElementType(action string, elementType string, elementID int, actor out = fmt.Sprintf(out, targetUser.Link, targetUser.Name, actor.Link, actor.Name) case "reply": if action == "delete" { - topic := handleUnknownTopic(common.BlankReply(elementID).Topic()) + topic := handleUnknownTopic(common.TopicByReplyID(elementID)) out = fmt.Sprintf("A reply in %s was deleted by %s", topic.Link, topic.Title, actor.Link, actor.Name) } } diff --git a/query_gen/main.go b/query_gen/main.go index bd016496..3e73dcda 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -233,12 +233,8 @@ func writeSelects(adapter qgen.Adapter) error { build.Select("getAdminlogsOffset").Table("administration_logs").Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Orderby("doneAt DESC").Limit("?,?").Parse() - build.Select("getReplyTID").Table("replies").Columns("tid").Where("rid = ?").Parse() - build.Select("getTopicFID").Table("topics").Columns("parentID").Where("tid = ?").Parse() - build.Select("getUserReplyUID").Table("users_replies").Columns("uid").Where("rid = ?").Parse() - build.Select("getUserName").Table("users").Columns("name").Where("uid = ?").Parse() build.Select("getEmailsByUser").Table("emails").Columns("email, validated, token").Where("uid = ?").Parse() @@ -312,8 +308,6 @@ func writeUpdates(adapter qgen.Adapter) error { build.Update("editReply").Table("replies").Set("content = ?, parsed_content = ?").Where("rid = ?").Parse() - build.Update("editProfileReply").Table("users_replies").Set("content = ?, parsed_content = ?").Where("rid = ?").Parse() - build.Update("updatePlugin").Table("plugins").Set("active = ?").Where("uname = ?").Parse() build.Update("updatePluginInstall").Table("plugins").Set("installed = ?").Where("uname = ?").Parse() @@ -342,8 +336,6 @@ func writeUpdates(adapter qgen.Adapter) error { func writeDeletes(adapter qgen.Adapter) error { build := adapter.Builder() - build.Delete("deleteProfileReply").Table("users_replies").Where("rid = ?").Parse() - //build.Delete("deleteForumPermsByForum").Table("forums_permissions").Where("fid = ?").Parse() build.Delete("deleteActivityStreamMatch").Table("activity_stream_matches").Where("watcher = ? AND asid = ?").Parse() diff --git a/query_gen/tables.go b/query_gen/tables.go index 43f0e20d..487f0511 100644 --- a/query_gen/tables.go +++ b/query_gen/tables.go @@ -207,10 +207,11 @@ func createTables(adapter qgen.Adapter) error { qgen.Install.CreateTable("revisions", "utf8mb4", "utf8mb4_general_ci", []qgen.DBTableColumn{ - qgen.DBTableColumn{"index", "int", 0, false, false, ""}, // TODO: Replace this with a proper revision ID x.x + qgen.DBTableColumn{"reviseID", "int", 0, false, true, ""}, qgen.DBTableColumn{"content", "text", 0, false, false, ""}, qgen.DBTableColumn{"contentID", "int", 0, false, false, ""}, qgen.DBTableColumn{"contentType", "varchar", 100, false, false, "replies"}, + qgen.DBTableColumn{"createdAt", "createdAt", 0, false, false, ""}, }, []qgen.DBTableKey{}, ) diff --git a/router_gen/main.go b/router_gen/main.go index 0a9bd90c..4d6b8d43 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -279,25 +279,6 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' { w.WriteHeader(405) w.Write([]byte("")) - return - } - - // TODO: Cover more suspicious strings and at a lower layer than this - for _, char := range req.URL.Path { - if char != '&' && !(char > 44 && char < 58) && char != '=' && char != '?' && !(char > 64 && char < 91) && char != '\\' && char != '_' && !(char > 96 && char < 123) { - log.Print("Suspicious UA: ", req.UserAgent()) - log.Print("Method: ", req.Method) - for key, value := range req.Header { - for _, vvalue := range value { - log.Print("Header '" + key + "': " + vvalue + "!!") - } - } - log.Print("req.URL.Path: ", req.URL.Path) - log.Print("req.Referer(): ", req.Referer()) - log.Print("req.RemoteAddr: ", req.RemoteAddr) - } - } - if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") { log.Print("Suspicious UA: ", req.UserAgent()) log.Print("Method: ", req.Method) for key, value := range req.Header { @@ -306,8 +287,43 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) + return + } + + if common.Dev.DebugMode { + // TODO: Cover more suspicious strings and at a lower layer than this + for _, char := range req.URL.Path { + if char != '&' && !(char > 44 && char < 58) && char != '=' && char != '?' && !(char > 64 && char < 91) && char != '\\' && char != '_' && !(char > 96 && char < 123) { + log.Print("Suspicious UA: ", req.UserAgent()) + log.Print("Method: ", req.Method) + for key, value := range req.Header { + for _, vvalue := range value { + log.Print("Header '" + key + "': " + vvalue + "!!") + } + } + log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) + log.Print("req.Referer(): ", req.Referer()) + log.Print("req.RemoteAddr: ", req.RemoteAddr) + break + } + } + if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") { + log.Print("Suspicious UA: ", req.UserAgent()) + log.Print("Method: ", req.Method) + for key, value := range req.Header { + for _, vvalue := range value { + log.Print("Header '" + key + "': " + vvalue + "!!") + } + } + log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) + log.Print("req.Referer(): ", req.Referer()) + log.Print("req.RemoteAddr: ", req.RemoteAddr) + } } var prefix, extraData string @@ -327,6 +343,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } log.Print("prefix: ", prefix) log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) @@ -390,6 +407,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } log.Print("prefix: ", prefix) log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) @@ -406,6 +424,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } log.Print("prefix: ", prefix) log.Print("req.URL.Path: ", req.URL.Path) + log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) diff --git a/router_gen/routes.go b/router_gen/routes.go index 6cdad277..7a3a76da 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -67,12 +67,12 @@ func buildTopicRoutes() { View("routeTopicID", "/topic/", "extraData"), Action("routeTopicCreateSubmit", "/topic/create/submit/"), Action("routes.EditTopicSubmit", "/topic/edit/submit/", "extraData"), - Action("routeDeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"), - Action("routeStickTopicSubmit", "/topic/stick/submit/", "extraData"), - Action("routeUnstickTopicSubmit", "/topic/unstick/submit/", "extraData"), - Action("routeLockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"), - Action("routeUnlockTopicSubmit", "/topic/unlock/submit/", "extraData"), - Action("routeMoveTopicSubmit", "/topic/move/submit/", "extraData"), + Action("routes.DeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"), + Action("routes.StickTopicSubmit", "/topic/stick/submit/", "extraData"), + Action("routes.UnstickTopicSubmit", "/topic/unstick/submit/", "extraData"), + 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"), ) addRouteGroup(topicGroup) @@ -85,8 +85,8 @@ func buildReplyRoutes() { replyGroup.Routes( // TODO: Reduce this to 1MB for attachments for each file? UploadAction("routeCreateReplySubmit", "/reply/create/").MaxSizeVar("common.Config.MaxRequestSize"), // TODO: Rename the route so it's /reply/create/submit/ - Action("routeReplyEditSubmit", "/reply/edit/submit/", "extraData"), - Action("routeReplyDeleteSubmit", "/reply/delete/submit/", "extraData"), + Action("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"), + Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"), Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData"), ) addRouteGroup(replyGroup) @@ -98,8 +98,8 @@ func buildProfileReplyRoutes() { pReplyGroup := newRouteGroup("/profile/") pReplyGroup.Routes( Action("routeProfileReplyCreateSubmit", "/profile/reply/create/"), // TODO: Add /submit/ to the end - Action("routeProfileReplyEditSubmit", "/profile/reply/edit/submit/", "extraData"), - Action("routeProfileReplyDeleteSubmit", "/profile/reply/delete/submit/", "extraData"), + Action("routes.ProfileReplyEditSubmit", "/profile/reply/edit/submit/", "extraData"), + Action("routes.ProfileReplyDeleteSubmit", "/profile/reply/delete/submit/", "extraData"), ) addRouteGroup(pReplyGroup) } diff --git a/routes.go b/routes.go index 53d54634..355783ee 100644 --- a/routes.go +++ b/routes.go @@ -179,18 +179,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo // Get the current page page, _ := strconv.Atoi(r.FormValue("page")) - - // Calculate the offset - var offset int - lastPage := (topicCount / common.Config.ItemsPerPage) + 1 - if page > 1 { - offset = (common.Config.ItemsPerPage * page) - common.Config.ItemsPerPage - } else if page == -1 { - page = lastPage - offset = (common.Config.ItemsPerPage * page) - common.Config.ItemsPerPage - } else { - page = 1 - } + offset, page, lastPage := common.PageOffset(topicCount, page, common.Config.ItemsPerPage) var topicList []*common.TopicsRow stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID IN("+qlist+")", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?") @@ -258,7 +247,8 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo topicItem.LastUser = userList[topicItem.LastReplyBy] } - pi := common.TopicsPage{common.GetTitlePhrase("topics"), user, headerVars, topicList, forumList, common.Config.DefaultForum} + pageList := common.Paginate(topicCount, common.Config.ItemsPerPage, 5) + pi := common.TopicsPage{common.GetTitlePhrase("topics"), user, headerVars, topicList, forumList, common.Config.DefaultForum, pageList, page, lastPage} if common.PreRenderHooks["pre_render_topic_list"] != nil { if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) { return nil diff --git a/routes/reply.go b/routes/reply.go new file mode 100644 index 00000000..30c05627 --- /dev/null +++ b/routes/reply.go @@ -0,0 +1,196 @@ +package routes + +import ( + "database/sql" + "net/http" + "strconv" + + "../common" +) + +// TODO: Disable stat updates in posts handled by plugin_guilds +// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes +func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { + isJs := (r.PostFormValue("js") == "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("The target reply doesn't exist.", w, r, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + topic, err := reply.Topic() + 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.EditReply { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + err = reply.SetBody(r.PostFormValue("edit_item")) + 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) + } + + if !isJs { + http.Redirect(w, r, "/topic/"+strconv.Itoa(topic.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther) + } else { + w.Write(successJSONBytes) + } + return nil +} + +// TODO: Refactor this +// TODO: Disable stat updates in posts handled by plugin_guilds +func ReplyDeleteSubmit(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("The reply you tried to delete 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.DeleteReply { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + err = reply.Delete() + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + //log.Printf("Reply #%d was deleted by common.User #%d", rid, user.ID) + if !isJs { + http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther) + } else { + w.Write(successJSONBytes) + } + + replyCreator, err := common.Users.Get(reply.CreatedBy) + if err == nil { + wcount := common.WordCount(reply.Content) + err = replyCreator.DecreasePostStats(wcount, false) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + } else if err != sql.ErrNoRows { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + err = common.ModLogs.Create("delete", reply.ParentID, "reply", user.LastIP, user.ID) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + return nil +} + +func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { + isJs := (r.PostFormValue("js") == "1") + + rid, err := strconv.Atoi(srid) + if err != nil { + return common.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, isJs) + } + + reply, err := common.Prstore.Get(rid) + if err == sql.ErrNoRows { + return common.PreErrorJSQ("The target reply doesn't exist.", w, r, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + creator, err := common.Users.Get(reply.CreatedBy) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + // ? Does the admin understand that this group perm affects this? + if user.ID != creator.ID && !user.Perms.EditReply { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + err = reply.SetBody(r.PostFormValue("edit_item")) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + if !isJs { + http.Redirect(w, r, "/user/"+strconv.Itoa(creator.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther) + } else { + w.Write(successJSONBytes) + } + return nil +} + +func ProfileReplyDeleteSubmit(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.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, isJs) + } + + reply, err := common.Prstore.Get(rid) + if err == sql.ErrNoRows { + return common.PreErrorJSQ("The target reply doesn't exist.", w, r, isJs) + } else if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + creator, err := common.Users.Get(reply.CreatedBy) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + if user.ID != creator.ID && !user.Perms.DeleteReply { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + err = reply.Delete() + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + //log.Printf("The profile post '%d' was deleted by common.User #%d", reply.ID, user.ID) + + if !isJs { + //http.Redirect(w,r, "/user/" + strconv.Itoa(creator.ID), http.StatusSeeOther) + } else { + w.Write(successJSONBytes) + } + return nil +} diff --git a/routes/topic.go b/routes/topic.go index b2fb1ade..723eb5c0 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -2,6 +2,8 @@ package routes import ( "database/sql" + "encoding/json" + "log" "net/http" "strconv" @@ -53,3 +55,317 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, s } return nil } + +// TODO: Add support for soft-deletion and add a permission for hard delete in addition to the usual +// TODO: Disable stat updates in posts handled by plugin_guilds +func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + // TODO: Move this to some sort of middleware + var tids []int + var isJs = false + if common.ReqIsJson(r) { + if r.Body == nil { + return common.PreErrorJS("No request body", w, r) + } + err := json.NewDecoder(r.Body).Decode(&tids) + if err != nil { + return common.PreErrorJS("We weren't able to parse your data", w, r) + } + isJs = true + } else { + tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):]) + if err != nil { + return common.PreError("The provided TopicID is not a valid number.", w, r) + } + tids = append(tids, tid) + } + if len(tids) == 0 { + return common.LocalErrorJSQ("You haven't provided any IDs", w, r, user, isJs) + } + + for _, tid := range tids { + topic, err := common.Topics.Get(tid) + if err == sql.ErrNoRows { + return common.PreErrorJSQ("The topic you tried to delete 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.DeleteTopic { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + // We might be able to handle this err better + err = topic.Delete() + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + err = common.ModLogs.Create("delete", tid, "topic", user.LastIP, user.ID) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + // ? - We might need to add soft-delete before we can do an action reply for this + /*_, err = stmts.createActionReply.Exec(tid,"delete",ipaddress,user.ID) + if err != nil { + return common.InternalErrorJSQ(err,w,r,isJs) + }*/ + + log.Printf("Topic #%d was deleted by common.User #%d", tid, user.ID) + } + http.Redirect(w, r, "/", http.StatusSeeOther) + return nil +} + +func StickTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { + tid, err := strconv.Atoi(stid) + if err != nil { + return common.PreError("The provided TopicID is not a valid number.", w, r) + } + + topic, err := common.Topics.Get(tid) + if err == sql.ErrNoRows { + return common.PreError("The topic you tried to pin doesn't exist.", w, r) + } else if err != nil { + return common.InternalError(err, w, r) + } + + // 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.PinTopic { + return common.NoPermissions(w, r, user) + } + + err = topic.Stick() + if err != nil { + return common.InternalError(err, w, r) + } + + err = common.ModLogs.Create("stick", tid, "topic", user.LastIP, user.ID) + if err != nil { + return common.InternalError(err, w, r) + } + err = topic.CreateActionReply("stick", user.LastIP, user) + if err != nil { + return common.InternalError(err, w, r) + } + http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) + return nil +} + +func UnstickTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { + tid, err := strconv.Atoi(stid) + if err != nil { + return common.PreError("The provided TopicID is not a valid number.", w, r) + } + + topic, err := common.Topics.Get(tid) + if err == sql.ErrNoRows { + return common.PreError("The topic you tried to unpin doesn't exist.", w, r) + } else if err != nil { + return common.InternalError(err, w, r) + } + + // 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.PinTopic { + return common.NoPermissions(w, r, user) + } + + err = topic.Unstick() + if err != nil { + return common.InternalError(err, w, r) + } + + err = common.ModLogs.Create("unstick", tid, "topic", user.LastIP, user.ID) + if err != nil { + return common.InternalError(err, w, r) + } + err = topic.CreateActionReply("unstick", user.LastIP, user) + if err != nil { + return common.InternalError(err, w, r) + } + + http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) + return nil +} + +func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + // TODO: Move this to some sort of middleware + var tids []int + var isJs = false + if common.ReqIsJson(r) { + if r.Body == nil { + return common.PreErrorJS("No request body", w, r) + } + err := json.NewDecoder(r.Body).Decode(&tids) + if err != nil { + return common.PreErrorJS("We weren't able to parse your data", w, r) + } + isJs = true + } else { + tid, err := strconv.Atoi(r.URL.Path[len("/topic/lock/submit/"):]) + if err != nil { + return common.PreError("The provided TopicID is not a valid number.", w, r) + } + tids = append(tids, tid) + } + if len(tids) == 0 { + return common.LocalErrorJSQ("You haven't provided any IDs", w, r, user, isJs) + } + + for _, tid := range tids { + topic, err := common.Topics.Get(tid) + if err == sql.ErrNoRows { + return common.PreErrorJSQ("The topic you tried to lock 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.CloseTopic { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + err = topic.Lock() + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + err = common.ModLogs.Create("lock", tid, "topic", user.LastIP, user.ID) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + err = topic.CreateActionReply("lock", user.LastIP, user) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + } + + if len(tids) == 1 { + http.Redirect(w, r, "/topic/"+strconv.Itoa(tids[0]), http.StatusSeeOther) + } + return nil +} + +func UnlockTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { + tid, err := strconv.Atoi(stid) + if err != nil { + return common.PreError("The provided TopicID is not a valid number.", w, r) + } + + topic, err := common.Topics.Get(tid) + if err == sql.ErrNoRows { + return common.PreError("The topic you tried to unlock doesn't exist.", w, r) + } else if err != nil { + return common.InternalError(err, w, r) + } + + // 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.CloseTopic { + return common.NoPermissions(w, r, user) + } + + err = topic.Unlock() + if err != nil { + return common.InternalError(err, w, r) + } + + err = common.ModLogs.Create("unlock", tid, "topic", user.LastIP, user.ID) + if err != nil { + return common.InternalError(err, w, r) + } + err = topic.CreateActionReply("unlock", user.LastIP, user) + if err != nil { + return common.InternalError(err, w, r) + } + + http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) + return nil +} + +// ! JS only route +// TODO: Figure a way to get this route to work without JS +func MoveTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError { + fid, err := strconv.Atoi(sfid) + if err != nil { + return common.PreErrorJS("The provided Forum ID is not a valid number.", w, r) + } + + // TODO: Move this to some sort of middleware + var tids []int + if r.Body == nil { + return common.PreErrorJS("No request body", w, r) + } + err = json.NewDecoder(r.Body).Decode(&tids) + if err != nil { + return common.PreErrorJS("We weren't able to parse your data", w, r) + } + if len(tids) == 0 { + return common.LocalErrorJS("You haven't provided any IDs", w, r) + } + + for _, tid := range tids { + topic, err := common.Topics.Get(tid) + if err == sql.ErrNoRows { + return common.PreErrorJS("The topic you tried to move doesn't exist.", w, r) + } else if err != nil { + return common.InternalErrorJS(err, w, r) + } + + // 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.MoveTopic { + return common.NoPermissionsJS(w, r, user) + } + _, ferr = common.SimpleForumUserCheck(w, r, &user, fid) + if ferr != nil { + return ferr + } + if !user.Perms.ViewTopic || !user.Perms.MoveTopic { + return common.NoPermissionsJS(w, r, user) + } + + err = topic.MoveTo(fid) + if err != nil { + return common.InternalErrorJS(err, w, r) + } + + // TODO: Log more data so we can list the destination forum in the action post? + err = common.ModLogs.Create("move", tid, "topic", user.LastIP, user.ID) + if err != nil { + return common.InternalErrorJS(err, w, r) + } + err = topic.CreateActionReply("move", user.LastIP, user) + if err != nil { + return common.InternalErrorJS(err, w, r) + } + } + + if len(tids) == 1 { + http.Redirect(w, r, "/topic/"+strconv.Itoa(tids[0]), http.StatusSeeOther) + } + return nil +} diff --git a/schema/mssql/query_revisions.sql b/schema/mssql/query_revisions.sql index 2840faf1..dcb01ed8 100644 --- a/schema/mssql/query_revisions.sql +++ b/schema/mssql/query_revisions.sql @@ -1,6 +1,7 @@ CREATE TABLE [revisions] ( - [index] int not null, + [reviseID] int not null IDENTITY, [content] nvarchar (MAX) not null, [contentID] int not null, - [contentType] nvarchar (100) DEFAULT 'replies' not null + [contentType] nvarchar (100) DEFAULT 'replies' not null, + [createdAt] datetime not null ); \ No newline at end of file diff --git a/schema/mysql/query_revisions.sql b/schema/mysql/query_revisions.sql index 61cb705e..b38d2eb1 100644 --- a/schema/mysql/query_revisions.sql +++ b/schema/mysql/query_revisions.sql @@ -1,6 +1,7 @@ CREATE TABLE `revisions` ( - `index` int not null, + `reviseID` int not null AUTO_INCREMENT, `content` text not null, `contentID` int not null, - `contentType` varchar(100) DEFAULT 'replies' not null + `contentType` varchar(100) DEFAULT 'replies' not null, + `createdAt` datetime not null ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/pgsql/query_revisions.sql b/schema/pgsql/query_revisions.sql index 55a9a107..73cec218 100644 --- a/schema/pgsql/query_revisions.sql +++ b/schema/pgsql/query_revisions.sql @@ -1,6 +1,7 @@ CREATE TABLE `revisions` ( - `index` int not null, + `reviseID` serial not null, `content` text not null, `contentID` int not null, - `contentType` varchar (100) DEFAULT 'replies' not null + `contentType` varchar (100) DEFAULT 'replies' not null, + `createdAt` timestamp not null ); \ No newline at end of file diff --git a/template_list.go b/template_list.go index 325d55f5..bc65f5b0 100644 --- a/template_list.go +++ b/template_list.go @@ -669,39 +669,41 @@ var profile_comments_row_18 = []byte(` var profile_comments_row_19 = []byte(`
-
- +
+ `)
 var profile_comments_row_22 = []byte(`'s Avatar - - + `) var profile_comments_row_25 = []byte(` - `) + `) var profile_comments_row_26 = []byte(``) var profile_comments_row_27 = []byte(``) var profile_comments_row_28 = []byte(` + +
+ + `) +var profile_comments_row_29 = []byte(` + + + `) +var profile_comments_row_34 = []byte(` +
`) -var profile_comments_row_29 = []byte(` - - `) -var profile_comments_row_30 = []byte(` - - - `) -var profile_comments_row_35 = []byte(` - - +var profile_comments_row_37 = []byte(`
`) @@ -975,6 +977,24 @@ var topics_61 = []byte(``) var topics_62 = []byte(` +`) +var topics_63 = []byte(` +
+ `) +var topics_64 = []byte(``) +var topics_66 = []byte(` + + `) +var topics_69 = []byte(``) +var topics_71 = []byte(` +
+`) +var topics_72 = []byte(` + `) var forum_0 = []byte(`
{{.CreatedByName}} - {{if .Tag}}{{.Tag}}{{end}} - -
-
- {{.ContentHtml}} +
+
+ {{.CreatedByName}}'s Avatar + + {{.CreatedByName}} + {{if .Tag}}{{.Tag}}{{end}} + +
{{if $.CurrentUser.IsMod}} @@ -36,6 +35,9 @@
+
+ {{.ContentHtml}} +
{{end}} {{end}} \ No newline at end of file diff --git a/templates/topics.html b/templates/topics.html index d0bbb01d..46924161 100644 --- a/templates/topics.html +++ b/templates/topics.html @@ -115,5 +115,15 @@
{{else}}
There aren't any topics yet.{{if .CurrentUser.Perms.CreateTopic}} Start one?{{end}}
{{end}}
+{{if gt .LastPage 1}} +
+ {{if gt .Page 1}}{{end}} + {{range .PageList}} + + {{end}} + {{if ne .LastPage .Page}}{{end}} +
+{{end}} + {{template "footer.html" . }} diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index ad6551a3..298939fb 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -1050,6 +1050,16 @@ textarea { #profile_right_lane .topic_reply_form { width: auto; } +#profile_comments .topRow { + display: flex; +} +#profile_comments .topRow .controls { + padding-top: 16px; + padding-right: 16px; +} +#profile_comments .content_column { + margin-bottom: 16px; +} #profile_comments_head { margin-top: 6px; }