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.
This commit is contained in:
Azareal 2018-01-20 06:50:29 +00:00
parent ef839a601c
commit 2997135e80
30 changed files with 851 additions and 747 deletions

View File

@ -96,9 +96,7 @@ func buildAlert(asid int, event string, elementType string, actorID int, targetU
endFrag = "'s profile" endFrag = "'s profile"
url = targetUser.Link url = targetUser.Link
case "post": case "post":
reply := common.BlankReply() topic, err := common.TopicByReplyID(elementID)
reply.ID = elementID
topic, err := reply.Topic()
if err != nil { if err != nil {
return "", errors.New("Unable to find the linked reply or parent topic") return "", errors.New("Unable to find the linked reply or parent topic")
} }

View File

@ -67,6 +67,9 @@ type TopicsPage struct {
TopicList []*TopicsRow TopicList []*TopicsRow
ForumList []Forum ForumList []Forum
DefaultForum int DefaultForum int
PageList []int
Page int
LastPage int
} }
type ForumPage struct { type ForumPage struct {

View File

@ -177,7 +177,7 @@ func PreparseMessage(msg string) string {
msg = RunSshook("preparse_preassign", msg) msg = RunSshook("preparse_preassign", msg)
} }
msg = html.EscapeString(msg) msg = html.EscapeString(msg)
msg = strings.Replace(msg," ","",-1) msg = strings.Replace(msg, " ", "", -1)
var runes = []rune(msg) var runes = []rune(msg)
msg = "" msg = ""
@ -353,9 +353,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
rid, intLen := CoerceIntBytes(msgbytes[start:]) rid, intLen := CoerceIntBytes(msgbytes[start:])
i += intLen i += intLen
reply := BlankReply() topic, err := TopicByReplyID(rid)
reply.ID = rid
topic, err := reply.Topic()
if err != nil || !Forums.Exists(topic.ParentID) { if err != nil || !Forums.Exists(topic.ParentID) {
outbytes = append(outbytes, InvalidTopic...) outbytes = append(outbytes, InvalidTopic...)
lastItem = i lastItem = i

58
common/profile_reply.go Normal file
View File

@ -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)
}

View File

@ -9,7 +9,7 @@ import (
var Prstore ProfileReplyStore var Prstore ProfileReplyStore
type ProfileReplyStore interface { 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) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error)
} }
@ -28,8 +28,8 @@ func NewSQLProfileReplyStore() (*SQLProfileReplyStore, error) {
}, acc.FirstError() }, acc.FirstError()
} }
func (store *SQLProfileReplyStore) Get(id int) (*Reply, error) { func (store *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) {
reply := Reply{ID: id} reply := ProfileReply{ID: id}
err := store.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress) err := store.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress)
return &reply, err return &reply, err
} }

View File

@ -9,6 +9,7 @@ package common
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"html"
"time" "time"
"../query_gen/lib" "../query_gen/lib"
@ -64,10 +65,10 @@ var replyStmts ReplyStmts
type ReplyStmts struct { type ReplyStmts struct {
isLiked *sql.Stmt isLiked *sql.Stmt
createLike *sql.Stmt createLike *sql.Stmt
edit *sql.Stmt
delete *sql.Stmt delete *sql.Stmt
addLikesToReply *sql.Stmt addLikesToReply *sql.Stmt
removeRepliesFromTopic *sql.Stmt removeRepliesFromTopic *sql.Stmt
getParent *sql.Stmt
} }
func init() { func init() {
@ -75,10 +76,10 @@ func init() {
replyStmts = ReplyStmts{ replyStmts = ReplyStmts{
isLiked: acc.Select("likes").Columns("targetItem").Where("sentBy = ? and targetItem = ? and targetType = 'replies'").Prepare(), 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(), 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(), delete: acc.Delete("replies").Where("rid = ?").Prepare(),
addLikesToReply: acc.Update("replies").Set("likeCount = likeCount + ?").Where("rid = ?").Prepare(), addLikesToReply: acc.Update("replies").Set("likeCount = likeCount + ?").Where("rid = ?").Prepare(),
removeRepliesFromTopic: acc.Update("topics").Set("postCount = postCount - ?").Where("tid = ?").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() return acc.FirstError()
}) })
@ -119,22 +120,22 @@ func (reply *Reply) Delete() error {
return err 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) { func (reply *Reply) Topic() (*Topic, error) {
topic := Topic{ID: 0} return Topics.Get(reply.ParentID)
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
} }
// Copy gives you a non-pointer concurrency safe copy of the reply // Copy gives you a non-pointer concurrency safe copy of the reply
func (reply *Reply) Copy() Reply { func (reply *Reply) Copy() Reply {
return *reply return *reply
} }
func BlankReply(ids ...int) *Reply {
var id int
if len(ids) != 0 {
id = ids[0]
}
return &Reply{ID: id}
}

View File

@ -149,7 +149,7 @@ func compileTemplates() error {
var topicsList []*TopicsRow 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"}) 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) topicsTmpl, err := c.Compile("topics.html", "templates/", "common.TopicsPage", topicsPage, varList)
if err != nil { if err != nil {
return err return err

View File

@ -123,6 +123,7 @@ type TopicStmts struct {
createActionReply *sql.Stmt createActionReply *sql.Stmt
getTopicUser *sql.Stmt // TODO: Can we get rid of this? getTopicUser *sql.Stmt // TODO: Can we get rid of this?
getByReplyID *sql.Stmt
} }
var topicStmts TopicStmts 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(), 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 = ?", "", ""), 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() return acc.FirstError()
}) })
@ -272,6 +274,13 @@ func (topic *Topic) Copy() Topic {
return *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 // TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
func GetTopicUser(tid int) (TopicUser, error) { func GetTopicUser(tid int) (TopicUser, error) {
tcache := Topics.GetCache() tcache := Topics.GetCache()

View File

@ -16,9 +16,7 @@ type Stmts struct {
getModlogs *sql.Stmt getModlogs *sql.Stmt
getModlogsOffset *sql.Stmt getModlogsOffset *sql.Stmt
getAdminlogsOffset *sql.Stmt getAdminlogsOffset *sql.Stmt
getReplyTID *sql.Stmt
getTopicFID *sql.Stmt getTopicFID *sql.Stmt
getUserReplyUID *sql.Stmt
getUserName *sql.Stmt getUserName *sql.Stmt
getEmailsByUser *sql.Stmt getEmailsByUser *sql.Stmt
getTopicBasic *sql.Stmt getTopicBasic *sql.Stmt
@ -44,7 +42,6 @@ type Stmts struct {
addAttachment *sql.Stmt addAttachment *sql.Stmt
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
editReply *sql.Stmt editReply *sql.Stmt
editProfileReply *sql.Stmt
updatePlugin *sql.Stmt updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
@ -56,7 +53,6 @@ type Stmts struct {
setTempGroup *sql.Stmt setTempGroup *sql.Stmt
updateWordFilter *sql.Stmt updateWordFilter *sql.Stmt
bumpSync *sql.Stmt bumpSync *sql.Stmt
deleteProfileReply *sql.Stmt
deleteActivityStreamMatch *sql.Stmt deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt deleteWordFilter *sql.Stmt
reportExists *sql.Stmt reportExists *sql.Stmt
@ -130,13 +126,6 @@ func _gen_mssql() (err error) {
return err 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.") log.Print("Preparing getTopicFID statement.")
stmts.getTopicFID, err = db.Prepare("SELECT [parentID] FROM [topics] WHERE [tid] = ?1") stmts.getTopicFID, err = db.Prepare("SELECT [parentID] FROM [topics] WHERE [tid] = ?1")
if err != nil { if err != nil {
@ -144,13 +133,6 @@ func _gen_mssql() (err error) {
return err 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.") log.Print("Preparing getUserName statement.")
stmts.getUserName, err = db.Prepare("SELECT [name] FROM [users] WHERE [uid] = ?1") stmts.getUserName, err = db.Prepare("SELECT [name] FROM [users] WHERE [uid] = ?1")
if err != nil { if err != nil {
@ -326,13 +308,6 @@ func _gen_mssql() (err error) {
return err 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.") log.Print("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE [plugins] SET [active] = ? WHERE [uname] = ?") stmts.updatePlugin, err = db.Prepare("UPDATE [plugins] SET [active] = ? WHERE [uname] = ?")
if err != nil { if err != nil {
@ -410,13 +385,6 @@ func _gen_mssql() (err error) {
return err 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.") log.Print("Preparing deleteActivityStreamMatch statement.")
stmts.deleteActivityStreamMatch, err = db.Prepare("DELETE FROM [activity_stream_matches] WHERE [watcher] = ? AND [asid] = ?") stmts.deleteActivityStreamMatch, err = db.Prepare("DELETE FROM [activity_stream_matches] WHERE [watcher] = ? AND [asid] = ?")
if err != nil { if err != nil {

View File

@ -18,9 +18,7 @@ type Stmts struct {
getModlogs *sql.Stmt getModlogs *sql.Stmt
getModlogsOffset *sql.Stmt getModlogsOffset *sql.Stmt
getAdminlogsOffset *sql.Stmt getAdminlogsOffset *sql.Stmt
getReplyTID *sql.Stmt
getTopicFID *sql.Stmt getTopicFID *sql.Stmt
getUserReplyUID *sql.Stmt
getUserName *sql.Stmt getUserName *sql.Stmt
getEmailsByUser *sql.Stmt getEmailsByUser *sql.Stmt
getTopicBasic *sql.Stmt getTopicBasic *sql.Stmt
@ -46,7 +44,6 @@ type Stmts struct {
addAttachment *sql.Stmt addAttachment *sql.Stmt
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
editReply *sql.Stmt editReply *sql.Stmt
editProfileReply *sql.Stmt
updatePlugin *sql.Stmt updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
@ -58,7 +55,6 @@ type Stmts struct {
setTempGroup *sql.Stmt setTempGroup *sql.Stmt
updateWordFilter *sql.Stmt updateWordFilter *sql.Stmt
bumpSync *sql.Stmt bumpSync *sql.Stmt
deleteProfileReply *sql.Stmt
deleteActivityStreamMatch *sql.Stmt deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt deleteWordFilter *sql.Stmt
reportExists *sql.Stmt reportExists *sql.Stmt
@ -125,24 +121,12 @@ func _gen_mysql() (err error) {
return err 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.") log.Print("Preparing getTopicFID statement.")
stmts.getTopicFID, err = db.Prepare("SELECT `parentID` FROM `topics` WHERE `tid` = ?") stmts.getTopicFID, err = db.Prepare("SELECT `parentID` FROM `topics` WHERE `tid` = ?")
if err != nil { if err != nil {
return err 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.") log.Print("Preparing getUserName statement.")
stmts.getUserName, err = db.Prepare("SELECT `name` FROM `users` WHERE `uid` = ?") stmts.getUserName, err = db.Prepare("SELECT `name` FROM `users` WHERE `uid` = ?")
if err != nil { if err != nil {
@ -293,12 +277,6 @@ func _gen_mysql() (err error) {
return err 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.") log.Print("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?") stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?")
if err != nil { if err != nil {
@ -365,12 +343,6 @@ func _gen_mysql() (err error) {
return err 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.") log.Print("Preparing deleteActivityStreamMatch statement.")
stmts.deleteActivityStreamMatch, err = db.Prepare("DELETE FROM `activity_stream_matches` WHERE `watcher` = ? AND `asid` = ?") stmts.deleteActivityStreamMatch, err = db.Prepare("DELETE FROM `activity_stream_matches` WHERE `watcher` = ? AND `asid` = ?")
if err != nil { if err != nil {

View File

@ -10,7 +10,6 @@ import "./common"
// nolint // nolint
type Stmts struct { type Stmts struct {
editReply *sql.Stmt editReply *sql.Stmt
editProfileReply *sql.Stmt
updatePlugin *sql.Stmt updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
@ -48,12 +47,6 @@ func _gen_pgsql() (err error) {
return err 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.") log.Print("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?") stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?")
if err != nil { if err != nil {

View File

@ -85,21 +85,21 @@ var RouteMap = map[string]interface{}{
"routeIps": routeIps, "routeIps": routeIps,
"routeTopicCreateSubmit": routeTopicCreateSubmit, "routeTopicCreateSubmit": routeTopicCreateSubmit,
"routes.EditTopicSubmit": routes.EditTopicSubmit, "routes.EditTopicSubmit": routes.EditTopicSubmit,
"routeDeleteTopicSubmit": routeDeleteTopicSubmit, "routes.DeleteTopicSubmit": routes.DeleteTopicSubmit,
"routeStickTopicSubmit": routeStickTopicSubmit, "routes.StickTopicSubmit": routes.StickTopicSubmit,
"routeUnstickTopicSubmit": routeUnstickTopicSubmit, "routes.UnstickTopicSubmit": routes.UnstickTopicSubmit,
"routeLockTopicSubmit": routeLockTopicSubmit, "routes.LockTopicSubmit": routes.LockTopicSubmit,
"routeUnlockTopicSubmit": routeUnlockTopicSubmit, "routes.UnlockTopicSubmit": routes.UnlockTopicSubmit,
"routeMoveTopicSubmit": routeMoveTopicSubmit, "routes.MoveTopicSubmit": routes.MoveTopicSubmit,
"routeLikeTopicSubmit": routeLikeTopicSubmit, "routeLikeTopicSubmit": routeLikeTopicSubmit,
"routeTopicID": routeTopicID, "routeTopicID": routeTopicID,
"routeCreateReplySubmit": routeCreateReplySubmit, "routeCreateReplySubmit": routeCreateReplySubmit,
"routeReplyEditSubmit": routeReplyEditSubmit, "routes.ReplyEditSubmit": routes.ReplyEditSubmit,
"routeReplyDeleteSubmit": routeReplyDeleteSubmit, "routes.ReplyDeleteSubmit": routes.ReplyDeleteSubmit,
"routeReplyLikeSubmit": routeReplyLikeSubmit, "routeReplyLikeSubmit": routeReplyLikeSubmit,
"routeProfileReplyCreateSubmit": routeProfileReplyCreateSubmit, "routeProfileReplyCreateSubmit": routeProfileReplyCreateSubmit,
"routeProfileReplyEditSubmit": routeProfileReplyEditSubmit, "routes.ProfileReplyEditSubmit": routes.ProfileReplyEditSubmit,
"routeProfileReplyDeleteSubmit": routeProfileReplyDeleteSubmit, "routes.ProfileReplyDeleteSubmit": routes.ProfileReplyDeleteSubmit,
"routeLogin": routeLogin, "routeLogin": routeLogin,
"routeRegister": routeRegister, "routeRegister": routeRegister,
"routeLogout": routeLogout, "routeLogout": routeLogout,
@ -180,21 +180,21 @@ var routeMapEnum = map[string]int{
"routeIps": 66, "routeIps": 66,
"routeTopicCreateSubmit": 67, "routeTopicCreateSubmit": 67,
"routes.EditTopicSubmit": 68, "routes.EditTopicSubmit": 68,
"routeDeleteTopicSubmit": 69, "routes.DeleteTopicSubmit": 69,
"routeStickTopicSubmit": 70, "routes.StickTopicSubmit": 70,
"routeUnstickTopicSubmit": 71, "routes.UnstickTopicSubmit": 71,
"routeLockTopicSubmit": 72, "routes.LockTopicSubmit": 72,
"routeUnlockTopicSubmit": 73, "routes.UnlockTopicSubmit": 73,
"routeMoveTopicSubmit": 74, "routes.MoveTopicSubmit": 74,
"routeLikeTopicSubmit": 75, "routeLikeTopicSubmit": 75,
"routeTopicID": 76, "routeTopicID": 76,
"routeCreateReplySubmit": 77, "routeCreateReplySubmit": 77,
"routeReplyEditSubmit": 78, "routes.ReplyEditSubmit": 78,
"routeReplyDeleteSubmit": 79, "routes.ReplyDeleteSubmit": 79,
"routeReplyLikeSubmit": 80, "routeReplyLikeSubmit": 80,
"routeProfileReplyCreateSubmit": 81, "routeProfileReplyCreateSubmit": 81,
"routeProfileReplyEditSubmit": 82, "routes.ProfileReplyEditSubmit": 82,
"routeProfileReplyDeleteSubmit": 83, "routes.ProfileReplyDeleteSubmit": 83,
"routeLogin": 84, "routeLogin": 84,
"routeRegister": 85, "routeRegister": 85,
"routeLogout": 86, "routeLogout": 86,
@ -273,21 +273,21 @@ var reverseRouteMapEnum = map[int]string{
66: "routeIps", 66: "routeIps",
67: "routeTopicCreateSubmit", 67: "routeTopicCreateSubmit",
68: "routes.EditTopicSubmit", 68: "routes.EditTopicSubmit",
69: "routeDeleteTopicSubmit", 69: "routes.DeleteTopicSubmit",
70: "routeStickTopicSubmit", 70: "routes.StickTopicSubmit",
71: "routeUnstickTopicSubmit", 71: "routes.UnstickTopicSubmit",
72: "routeLockTopicSubmit", 72: "routes.LockTopicSubmit",
73: "routeUnlockTopicSubmit", 73: "routes.UnlockTopicSubmit",
74: "routeMoveTopicSubmit", 74: "routes.MoveTopicSubmit",
75: "routeLikeTopicSubmit", 75: "routeLikeTopicSubmit",
76: "routeTopicID", 76: "routeTopicID",
77: "routeCreateReplySubmit", 77: "routeCreateReplySubmit",
78: "routeReplyEditSubmit", 78: "routes.ReplyEditSubmit",
79: "routeReplyDeleteSubmit", 79: "routes.ReplyDeleteSubmit",
80: "routeReplyLikeSubmit", 80: "routeReplyLikeSubmit",
81: "routeProfileReplyCreateSubmit", 81: "routeProfileReplyCreateSubmit",
82: "routeProfileReplyEditSubmit", 82: "routes.ProfileReplyEditSubmit",
83: "routeProfileReplyDeleteSubmit", 83: "routes.ProfileReplyDeleteSubmit",
84: "routeLogin", 84: "routeLogin",
85: "routeRegister", 85: "routeRegister",
86: "routeLogout", 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] != '/' { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
w.WriteHeader(405) w.WriteHeader(405)
w.Write([]byte("")) 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("Suspicious UA: ", req.UserAgent())
log.Print("Method: ", req.Method) log.Print("Method: ", req.Method)
for key, value := range req.Header { 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.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr) 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 var prefix, extraData string
@ -443,6 +459,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
log.Print("prefix: ", prefix) log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData) log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr) 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("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData) log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr) 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("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData) log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr) log.Print("req.RemoteAddr: ", req.RemoteAddr)
@ -1155,7 +1174,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req.URL.Path += extraData req.URL.Path += extraData
common.RouteViewCounter.Bump(69) common.RouteViewCounter.Bump(69)
err = routeDeleteTopicSubmit(w,req,user) err = routes.DeleteTopicSubmit(w,req,user)
case "/topic/stick/submit/": case "/topic/stick/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1170,7 +1189,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
common.RouteViewCounter.Bump(70) common.RouteViewCounter.Bump(70)
err = routeStickTopicSubmit(w,req,user,extraData) err = routes.StickTopicSubmit(w,req,user,extraData)
case "/topic/unstick/submit/": case "/topic/unstick/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1185,7 +1204,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
common.RouteViewCounter.Bump(71) common.RouteViewCounter.Bump(71)
err = routeUnstickTopicSubmit(w,req,user,extraData) err = routes.UnstickTopicSubmit(w,req,user,extraData)
case "/topic/lock/submit/": case "/topic/lock/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1201,7 +1220,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req.URL.Path += extraData req.URL.Path += extraData
common.RouteViewCounter.Bump(72) common.RouteViewCounter.Bump(72)
err = routeLockTopicSubmit(w,req,user) err = routes.LockTopicSubmit(w,req,user)
case "/topic/unlock/submit/": case "/topic/unlock/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1216,7 +1235,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
common.RouteViewCounter.Bump(73) common.RouteViewCounter.Bump(73)
err = routeUnlockTopicSubmit(w,req,user,extraData) err = routes.UnlockTopicSubmit(w,req,user,extraData)
case "/topic/move/submit/": case "/topic/move/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1231,7 +1250,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
common.RouteViewCounter.Bump(74) common.RouteViewCounter.Bump(74)
err = routeMoveTopicSubmit(w,req,user,extraData) err = routes.MoveTopicSubmit(w,req,user,extraData)
case "/topic/like/submit/": case "/topic/like/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1290,7 +1309,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
common.RouteViewCounter.Bump(78) common.RouteViewCounter.Bump(78)
err = routeReplyEditSubmit(w,req,user,extraData) err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/": case "/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1305,7 +1324,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
common.RouteViewCounter.Bump(79) common.RouteViewCounter.Bump(79)
err = routeReplyDeleteSubmit(w,req,user,extraData) err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/": case "/reply/like/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1356,7 +1375,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
common.RouteViewCounter.Bump(82) common.RouteViewCounter.Bump(82)
err = routeProfileReplyEditSubmit(w,req,user,extraData) err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/": case "/profile/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1371,7 +1390,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
common.RouteViewCounter.Bump(83) common.RouteViewCounter.Bump(83)
err = routeProfileReplyDeleteSubmit(w,req,user,extraData) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)

View File

@ -1,9 +1,6 @@
package main package main
import ( import (
"encoding/json"
"html"
"log"
"net/http" "net/http"
"strconv" "strconv"
"time" "time"
@ -11,501 +8,6 @@ import (
"./common" "./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 { func routeIps(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, ferr := common.UserCheck(w, r, &user) headerVars, ferr := common.UserCheck(w, r, &user)
if ferr != nil { if ferr != nil {

View File

@ -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) out = fmt.Sprintf(out, targetUser.Link, targetUser.Name, actor.Link, actor.Name)
case "reply": case "reply":
if action == "delete" { if action == "delete" {
topic := handleUnknownTopic(common.BlankReply(elementID).Topic()) topic := handleUnknownTopic(common.TopicByReplyID(elementID))
out = fmt.Sprintf("A reply in <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>", topic.Link, topic.Title, actor.Link, actor.Name) out = fmt.Sprintf("A reply in <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>", topic.Link, topic.Title, actor.Link, actor.Name)
} }
} }

View File

@ -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("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("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("getUserName").Table("users").Columns("name").Where("uid = ?").Parse()
build.Select("getEmailsByUser").Table("emails").Columns("email, validated, token").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("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("updatePlugin").Table("plugins").Set("active = ?").Where("uname = ?").Parse()
build.Update("updatePluginInstall").Table("plugins").Set("installed = ?").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 { func writeDeletes(adapter qgen.Adapter) error {
build := adapter.Builder() build := adapter.Builder()
build.Delete("deleteProfileReply").Table("users_replies").Where("rid = ?").Parse()
//build.Delete("deleteForumPermsByForum").Table("forums_permissions").Where("fid = ?").Parse() //build.Delete("deleteForumPermsByForum").Table("forums_permissions").Where("fid = ?").Parse()
build.Delete("deleteActivityStreamMatch").Table("activity_stream_matches").Where("watcher = ? AND asid = ?").Parse() build.Delete("deleteActivityStreamMatch").Table("activity_stream_matches").Where("watcher = ? AND asid = ?").Parse()

View File

@ -207,10 +207,11 @@ func createTables(adapter qgen.Adapter) error {
qgen.Install.CreateTable("revisions", "utf8mb4", "utf8mb4_general_ci", qgen.Install.CreateTable("revisions", "utf8mb4", "utf8mb4_general_ci",
[]qgen.DBTableColumn{ []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{"content", "text", 0, false, false, ""},
qgen.DBTableColumn{"contentID", "int", 0, false, false, ""}, qgen.DBTableColumn{"contentID", "int", 0, false, false, ""},
qgen.DBTableColumn{"contentType", "varchar", 100, false, false, "replies"}, qgen.DBTableColumn{"contentType", "varchar", 100, false, false, "replies"},
qgen.DBTableColumn{"createdAt", "createdAt", 0, false, false, ""},
}, },
[]qgen.DBTableKey{}, []qgen.DBTableKey{},
) )

View File

@ -279,25 +279,6 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
w.WriteHeader(405) w.WriteHeader(405)
w.Write([]byte("")) 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("Suspicious UA: ", req.UserAgent())
log.Print("Method: ", req.Method) log.Print("Method: ", req.Method)
for key, value := range req.Header { 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.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr) 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 var prefix, extraData string
@ -327,6 +343,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
log.Print("prefix: ", prefix) log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData) log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr) 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("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData) log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr) 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("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData) log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr) log.Print("req.RemoteAddr: ", req.RemoteAddr)

View File

@ -67,12 +67,12 @@ func buildTopicRoutes() {
View("routeTopicID", "/topic/", "extraData"), View("routeTopicID", "/topic/", "extraData"),
Action("routeTopicCreateSubmit", "/topic/create/submit/"), Action("routeTopicCreateSubmit", "/topic/create/submit/"),
Action("routes.EditTopicSubmit", "/topic/edit/submit/", "extraData"), Action("routes.EditTopicSubmit", "/topic/edit/submit/", "extraData"),
Action("routeDeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"), Action("routes.DeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"),
Action("routeStickTopicSubmit", "/topic/stick/submit/", "extraData"), Action("routes.StickTopicSubmit", "/topic/stick/submit/", "extraData"),
Action("routeUnstickTopicSubmit", "/topic/unstick/submit/", "extraData"), Action("routes.UnstickTopicSubmit", "/topic/unstick/submit/", "extraData"),
Action("routeLockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"), Action("routes.LockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"),
Action("routeUnlockTopicSubmit", "/topic/unlock/submit/", "extraData"), Action("routes.UnlockTopicSubmit", "/topic/unlock/submit/", "extraData"),
Action("routeMoveTopicSubmit", "/topic/move/submit/", "extraData"), Action("routes.MoveTopicSubmit", "/topic/move/submit/", "extraData"),
Action("routeLikeTopicSubmit", "/topic/like/submit/", "extraData"), Action("routeLikeTopicSubmit", "/topic/like/submit/", "extraData"),
) )
addRouteGroup(topicGroup) addRouteGroup(topicGroup)
@ -85,8 +85,8 @@ func buildReplyRoutes() {
replyGroup.Routes( replyGroup.Routes(
// TODO: Reduce this to 1MB for attachments for each file? // 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/ 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("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"),
Action("routeReplyDeleteSubmit", "/reply/delete/submit/", "extraData"), Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"),
Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData"), Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData"),
) )
addRouteGroup(replyGroup) addRouteGroup(replyGroup)
@ -98,8 +98,8 @@ func buildProfileReplyRoutes() {
pReplyGroup := newRouteGroup("/profile/") pReplyGroup := newRouteGroup("/profile/")
pReplyGroup.Routes( pReplyGroup.Routes(
Action("routeProfileReplyCreateSubmit", "/profile/reply/create/"), // TODO: Add /submit/ to the end Action("routeProfileReplyCreateSubmit", "/profile/reply/create/"), // TODO: Add /submit/ to the end
Action("routeProfileReplyEditSubmit", "/profile/reply/edit/submit/", "extraData"), Action("routes.ProfileReplyEditSubmit", "/profile/reply/edit/submit/", "extraData"),
Action("routeProfileReplyDeleteSubmit", "/profile/reply/delete/submit/", "extraData"), Action("routes.ProfileReplyDeleteSubmit", "/profile/reply/delete/submit/", "extraData"),
) )
addRouteGroup(pReplyGroup) addRouteGroup(pReplyGroup)
} }

View File

@ -179,18 +179,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo
// Get the current page // Get the current page
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
offset, page, lastPage := common.PageOffset(topicCount, page, common.Config.ItemsPerPage)
// 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
}
var topicList []*common.TopicsRow 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", "?,?") 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] 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.PreRenderHooks["pre_render_topic_list"] != nil {
if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) {
return nil return nil

196
routes/reply.go Normal file
View File

@ -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
}

View File

@ -2,6 +2,8 @@ package routes
import ( import (
"database/sql" "database/sql"
"encoding/json"
"log"
"net/http" "net/http"
"strconv" "strconv"
@ -53,3 +55,317 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, s
} }
return nil 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
}

View File

@ -1,6 +1,7 @@
CREATE TABLE [revisions] ( CREATE TABLE [revisions] (
[index] int not null, [reviseID] int not null IDENTITY,
[content] nvarchar (MAX) not null, [content] nvarchar (MAX) not null,
[contentID] int not null, [contentID] int not null,
[contentType] nvarchar (100) DEFAULT 'replies' not null [contentType] nvarchar (100) DEFAULT 'replies' not null,
[createdAt] datetime not null
); );

View File

@ -1,6 +1,7 @@
CREATE TABLE `revisions` ( CREATE TABLE `revisions` (
`index` int not null, `reviseID` int not null AUTO_INCREMENT,
`content` text not null, `content` text not null,
`contentID` int 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; ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -1,6 +1,7 @@
CREATE TABLE `revisions` ( CREATE TABLE `revisions` (
`index` int not null, `reviseID` serial not null,
`content` text not null, `content` text not null,
`contentID` int not null, `contentID` int not null,
`contentType` varchar (100) DEFAULT 'replies' not null `contentType` varchar (100) DEFAULT 'replies' not null,
`createdAt` timestamp not null
); );

View File

@ -669,39 +669,41 @@ var profile_comments_row_18 = []byte(`
var profile_comments_row_19 = []byte(` var profile_comments_row_19 = []byte(`
<div class="rowitem passive deletable_block editable_parent comment `) <div class="rowitem passive deletable_block editable_parent comment `)
var profile_comments_row_20 = []byte(`"> var profile_comments_row_20 = []byte(`">
<div class="userbit"> <div class="topRow">
<img src="`) <div class="userbit">
<img src="`)
var profile_comments_row_21 = []byte(`" alt="`) var profile_comments_row_21 = []byte(`" alt="`)
var profile_comments_row_22 = []byte(`'s Avatar" title="`) var profile_comments_row_22 = []byte(`'s Avatar" title="`)
var profile_comments_row_23 = []byte(`'s Avatar" /> var profile_comments_row_23 = []byte(`'s Avatar" />
<span class="nameAndTitle"> <span class="nameAndTitle">
<a href="`) <a href="`)
var profile_comments_row_24 = []byte(`" class="real_username username">`) var profile_comments_row_24 = []byte(`" class="real_username username">`)
var profile_comments_row_25 = []byte(`</a> var profile_comments_row_25 = []byte(`</a>
`) `)
var profile_comments_row_26 = []byte(`<a class="username hide_on_mobile user_tag" style="float: right;">`) var profile_comments_row_26 = []byte(`<a class="username hide_on_mobile user_tag" style="float: right;">`)
var profile_comments_row_27 = []byte(`</a>`) var profile_comments_row_27 = []byte(`</a>`)
var profile_comments_row_28 = []byte(` var profile_comments_row_28 = []byte(`
</span>
</div>
<span class="controls">
`)
var profile_comments_row_29 = []byte(`
<a href="/profile/reply/edit/submit/`)
var profile_comments_row_30 = []byte(`?session=`)
var profile_comments_row_31 = []byte(`" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/`)
var profile_comments_row_32 = []byte(`?session=`)
var profile_comments_row_33 = []byte(`" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>
`)
var profile_comments_row_34 = []byte(`
<a class="mod_button" href="/report/submit/`)
var profile_comments_row_35 = []byte(`?session=`)
var profile_comments_row_36 = []byte(`&type=user-reply"><button class="username report_item flag_label"></button></a>
</span> </span>
</div> </div>
<div class="content_column"> <div class="content_column">
<span class="editable_block user_content">`) <span class="editable_block user_content">`)
var profile_comments_row_29 = []byte(`</span> var profile_comments_row_37 = []byte(`</span>
<span class="controls">
`)
var profile_comments_row_30 = []byte(`
<a href="/profile/reply/edit/submit/`)
var profile_comments_row_31 = []byte(`?session=`)
var profile_comments_row_32 = []byte(`" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/`)
var profile_comments_row_33 = []byte(`?session=`)
var profile_comments_row_34 = []byte(`" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>
`)
var profile_comments_row_35 = []byte(`
<a class="mod_button" href="/report/submit/`)
var profile_comments_row_36 = []byte(`?session=`)
var profile_comments_row_37 = []byte(`&type=user-reply"><button class="username report_item flag_label"></button></a>
</span>
</div> </div>
</div> </div>
`) `)
@ -975,6 +977,24 @@ var topics_61 = []byte(`</div>`)
var topics_62 = []byte(` var topics_62 = []byte(`
</div> </div>
`)
var topics_63 = []byte(`
<div class="pageset">
`)
var topics_64 = []byte(`<div class="pageitem"><a href="?page=`)
var topics_65 = []byte(`">Prev</a></div>`)
var topics_66 = []byte(`
<div class="pageitem"><a href="?page=`)
var topics_67 = []byte(`">`)
var topics_68 = []byte(`</a></div>
`)
var topics_69 = []byte(`<div class="pageitem"><a href="?page=`)
var topics_70 = []byte(`">Next</a></div>`)
var topics_71 = []byte(`
</div>
`)
var topics_72 = []byte(`
</main> </main>
`) `)
var forum_0 = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/forum/`) var forum_0 = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/forum/`)

View File

@ -3,9 +3,9 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "net/http"
import "./common" import "./common"
import "strconv" import "strconv"
import "net/http"
// nolint // nolint
func init() { func init() {
@ -186,23 +186,23 @@ w.Write([]byte(item.Tag))
w.Write(profile_comments_row_27) w.Write(profile_comments_row_27)
} }
w.Write(profile_comments_row_28) w.Write(profile_comments_row_28)
w.Write([]byte(item.ContentHtml))
w.Write(profile_comments_row_29)
if tmpl_profile_vars.CurrentUser.IsMod { if tmpl_profile_vars.CurrentUser.IsMod {
w.Write(profile_comments_row_29)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_comments_row_30) w.Write(profile_comments_row_30)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_comments_row_31) w.Write(profile_comments_row_31)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session)) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_comments_row_32) w.Write(profile_comments_row_32)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_comments_row_33) w.Write(profile_comments_row_33)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_comments_row_34)
} }
w.Write(profile_comments_row_35) w.Write(profile_comments_row_34)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_comments_row_36) w.Write(profile_comments_row_35)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session)) w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_comments_row_36)
w.Write([]byte(item.ContentHtml))
w.Write(profile_comments_row_37) w.Write(profile_comments_row_37)
} }
} }

View File

@ -217,6 +217,30 @@ w.Write(topics_60)
w.Write(topics_61) w.Write(topics_61)
} }
w.Write(topics_62) w.Write(topics_62)
if tmpl_topics_vars.LastPage > 1 {
w.Write(topics_63)
if tmpl_topics_vars.Page > 1 {
w.Write(topics_64)
w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page - 1)))
w.Write(topics_65)
}
if len(tmpl_topics_vars.PageList) != 0 {
for _, item := range tmpl_topics_vars.PageList {
w.Write(topics_66)
w.Write([]byte(strconv.Itoa(item)))
w.Write(topics_67)
w.Write([]byte(strconv.Itoa(item)))
w.Write(topics_68)
}
}
if tmpl_topics_vars.LastPage != tmpl_topics_vars.Page {
w.Write(topics_69)
w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page + 1)))
w.Write(topics_70)
}
w.Write(topics_71)
}
w.Write(topics_72)
w.Write(footer_0) w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header))) w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header)))
w.Write(footer_1) w.Write(footer_1)

View File

@ -19,15 +19,14 @@
{{else}} {{else}}
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem passive deletable_block editable_parent comment {{.ClassName}}"> <div class="rowitem passive deletable_block editable_parent comment {{.ClassName}}">
<div class="userbit"> <div class="topRow">
<img src="{{.Avatar}}" alt="{{.CreatedByName}}'s Avatar" title="{{.CreatedByName}}'s Avatar" /> <div class="userbit">
<span class="nameAndTitle"> <img src="{{.Avatar}}" alt="{{.CreatedByName}}'s Avatar" title="{{.CreatedByName}}'s Avatar" />
<a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a> <span class="nameAndTitle">
{{if .Tag}}<a class="username hide_on_mobile user_tag" style="float: right;">{{.Tag}}</a>{{end}} <a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>
</span> {{if .Tag}}<a class="username hide_on_mobile user_tag" style="float: right;">{{.Tag}}</a>{{end}}
</div> </span>
<div class="content_column"> </div>
<span class="editable_block user_content">{{.ContentHtml}}</span>
<span class="controls"> <span class="controls">
{{if $.CurrentUser.IsMod}} {{if $.CurrentUser.IsMod}}
<a href="/profile/reply/edit/submit/{{.ID}}?session={{$.CurrentUser.Session}}" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a> <a href="/profile/reply/edit/submit/{{.ID}}?session={{$.CurrentUser.Session}}" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a>
@ -36,6 +35,9 @@
<a class="mod_button" href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label"></button></a> <a class="mod_button" href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label"></button></a>
</span> </span>
</div> </div>
<div class="content_column">
<span class="editable_block user_content">{{.ContentHtml}}</span>
</div>
</div> </div>
{{end}} {{end}}
{{end}} {{end}}

View File

@ -115,5 +115,15 @@
</div>{{else}}<div class="rowitem passive">There aren't any topics yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/">Start one?</a>{{end}}</div>{{end}} </div>{{else}}<div class="rowitem passive">There aren't any topics yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/">Start one?</a>{{end}}</div>{{end}}
</div> </div>
{{if gt .LastPage 1}}
<div class="pageset">
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}">Prev</a></div>{{end}}
{{range .PageList}}
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div>
{{end}}
{{if ne .LastPage .Page}}<div class="pageitem"><a href="?page={{add .Page 1}}">Next</a></div>{{end}}
</div>
{{end}}
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1050,6 +1050,16 @@ textarea {
#profile_right_lane .topic_reply_form { #profile_right_lane .topic_reply_form {
width: auto; 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 { #profile_comments_head {
margin-top: 6px; margin-top: 6px;
} }