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(`