Add option to delete all of a user's posts.

Add limited dynanicism to langf.
Eliminate a few queries when updating user stats.
Minor clean-up.

Namespace the profile language strings.
This commit is contained in:
Azareal 2020-01-14 15:07:00 +10:00
parent a569772e9c
commit 69a2430e5a
26 changed files with 535 additions and 298 deletions

View File

@ -37,6 +37,7 @@ type ForumStore interface {
Delete(id int) error
AddTopic(tid int, uid int, fid int) error
RemoveTopic(fid int) error
RemoveTopics(fid, count int) error
UpdateLastTopic(tid int, uid int, fid int) error
Exists(id int) bool
GetAll() ([]*Forum, error)
@ -45,7 +46,7 @@ type ForumStore interface {
GetAllVisibleIDs() ([]int, error)
//GetChildren(parentID int, parentType string) ([]*Forum,error)
//GetFirstChild(parentID int, parentType string) (*Forum,error)
Create(forumName string, forumDesc string, active bool, preset string) (int, error)
Create(name string, desc string, active bool, preset string) (int, error)
UpdateOrder(updateMap map[int]int) error
Count() int
@ -53,7 +54,7 @@ type ForumStore interface {
type ForumCache interface {
CacheGet(id int) (*Forum, error)
CacheSet(forum *Forum) error
CacheSet(f *Forum) error
CacheDelete(id int)
Length() int
}
@ -311,21 +312,18 @@ func (s *MemoryForumStore) AddTopic(tid int, uid int, fid int) error {
return s.Reload(fid)
}
// TODO: Make this update more atomic
func (s *MemoryForumStore) RemoveTopic(fid int) error {
_, err := s.removeTopics.Exec(1, fid)
if err != nil {
return err
}
func (s *MemoryForumStore) RefreshTopic(fid int) (err error) {
var tid int
err = s.lastTopic.QueryRow(fid).Scan(&tid)
if err == sql.ErrNoRows {
f, err := s.CacheGet(fid)
if err != nil || f.LastTopicID != 0 {
_, err = s.updateCache.Exec(0, 0, fid)
if err != nil {
return err
}
s.Reload(fid)
}
return nil
}
if err != nil {
@ -345,6 +343,22 @@ func (s *MemoryForumStore) RemoveTopic(fid int) error {
return nil
}
// TODO: Make this update more atomic
func (s *MemoryForumStore) RemoveTopic(fid int) error {
_, err := s.removeTopics.Exec(1, fid)
if err != nil {
return err
}
return s.RefreshTopic(fid)
}
func (s *MemoryForumStore) RemoveTopics(fid int, count int) error {
_, err := s.removeTopics.Exec(count, fid)
if err != nil {
return err
}
return s.RefreshTopic(fid)
}
// DEPRECATED. forum.Update() will be the way to do this in the future, once it's completed
// TODO: Have a pointer to the last topic rather than storing it on the forum itself
func (s *MemoryForumStore) UpdateLastTopic(tid int, uid int, fid int) error {

View File

@ -141,7 +141,7 @@ func (s *DefaultPageStore) GetByName(name string) (*CustomPage, error) {
return p, s.parseAllowedGroups(rawAllowedGroups, p)
}
func (s *DefaultPageStore) GetOffset(offset int, perPage int) (pages []*CustomPage, err error) {
func (s *DefaultPageStore) GetOffset(offset, perPage int) (pages []*CustomPage, err error) {
rows, err := s.getOffset.Query(offset, perPage)
if err != nil {
return pages, err

View File

@ -403,7 +403,7 @@ func AddHashLinkType(prefix string, handler func(*strings.Builder, string, *int)
hashLinkTypes[prefix[0]] = prefix
}
func WriteURL(sb *strings.Builder, url string, label string) {
func WriteURL(sb *strings.Builder, url, label string) {
sb.Write(URLOpen)
sb.WriteString(url)
sb.Write(URLOpen2)

View File

@ -126,7 +126,7 @@ func InitPhrases(lang string) error {
langPack.TmplIndicesToPhrases = make([][][]byte, len(langTmplIndicesToNames))
for tmplID, phraseNames := range langTmplIndicesToNames {
var phraseSet = make([][]byte, len(phraseNames))
phraseSet := make([][]byte, len(phraseNames))
for index, phraseName := range phraseNames {
phrase, ok := langPack.TmplPhrases[phraseName]
if !ok {

View File

@ -32,8 +32,8 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
ur := "users_replies"
profileReplyStmts = ProfileReplyStmts{
edit: acc.Update(ur).Set("content = ?, parsed_content = ?").Where("rid = ?").Prepare(),
delete: acc.Delete(ur).Where("rid = ?").Prepare(),
edit: acc.Update(ur).Set("content=?,parsed_content=?").Where("rid=?").Prepare(),
delete: acc.Delete(ur).Where("rid=?").Prepare(),
}
return acc.FirstError()
})
@ -45,6 +45,7 @@ func BlankProfileReply(id int) *ProfileReply {
}
// TODO: Write tests for this
// TODO: Remove alerts.
func (r *ProfileReply) Delete() error {
_, err := profileReplyStmts.delete.Exec(r.ID)
return err

View File

@ -67,13 +67,14 @@ type ReplyStmts struct {
func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
re := "replies"
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(),
edit: acc.Update("replies").Set("content = ?, parsed_content = ?").Where("rid = ? AND poll = 0").Prepare(),
setPoll: acc.Update("replies").Set("poll = ?").Where("rid = ? AND poll = 0").Prepare(),
delete: acc.Delete("replies").Where("rid = ?").Prepare(),
addLikesToReply: acc.Update("replies").Set("likeCount = likeCount + ?").Where("rid = ?").Prepare(),
edit: acc.Update(re).Set("content = ?, parsed_content = ?").Where("rid = ? AND poll = 0").Prepare(),
setPoll: acc.Update(re).Set("poll = ?").Where("rid = ? AND poll = 0").Prepare(),
delete: acc.Delete(re).Where("rid = ?").Prepare(),
addLikesToReply: acc.Update(re).Set("likeCount = likeCount + ?").Where("rid = ?").Prepare(),
removeRepliesFromTopic: acc.Update("topics").Set("postCount = postCount - ?").Where("tid = ?").Prepare(),
}
return acc.FirstError()
@ -105,6 +106,8 @@ func (r *Reply) Like(uid int) (err error) {
return err
}
// TODO: Refresh topic list?
// TODO: Remove alerts.
func (r *Reply) Delete() error {
_, err := replyStmts.delete.Exec(r.ID)
if err != nil {
@ -112,9 +115,9 @@ func (r *Reply) Delete() error {
}
// TODO: Move this bit to *Topic
_, err = replyStmts.removeRepliesFromTopic.Exec(1, r.ParentID)
tcache := Topics.GetCache()
if tcache != nil {
tcache.Remove(r.ParentID)
tc := Topics.GetCache()
if tc != nil {
tc.Remove(r.ParentID)
}
_ = Rstore.GetCache().Remove(r.ID)
return err

View File

@ -1,7 +1,7 @@
package common
import (
"log"
//"log"
"sync"
"sync/atomic"
)
@ -90,7 +90,7 @@ func (s *MemoryReplyCache) Set(item *Reply) error {
// Add adds a reply to the cache, similar to Set, but it's only intended for new items. This method might be deprecated in the near future, use Set. May return a capacity overflow error.
// ? Is this redundant if we have Set? Are the efficiency wins worth this? Is this even used?
func (s *MemoryReplyCache) Add(item *Reply) error {
log.Print("MemoryReplyCache.Add")
//log.Print("MemoryReplyCache.Add")
s.Lock()
if int(s.length) >= s.capacity {
s.Unlock()

View File

@ -1172,10 +1172,10 @@ ArgLoop:
for i := pos + 2; i < len(node.Args); i++ {
op := node.Args[i].String()
if op != "" {
if op[0] == '.' || op[0] == '$' {
if /*op[0] == '.' || */op[0] == '$' {
panic("langf args cannot be dynamic")
}
if op[0] != '"' && !unicode.IsDigit(rune(op[0])) {
if op[0] != '.' && op[0] != '"' && !unicode.IsDigit(rune(op[0])) {
break
}
olist = append(olist, op)
@ -1187,6 +1187,14 @@ ArgLoop:
ob := ","
for _, op := range olist {
if op[0] == '.' {
param, val3 := c.compileIfVarSub(con, op)
if !val3.IsValid() {
panic("val3 is invalid")
}
ob += param + ","
continue
}
allNum := true
for _, o := range op {
if !unicode.IsDigit(o) {

View File

@ -43,10 +43,10 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
t := "themes"
themeStmts = ThemeStmts{
getAll: acc.Select(t).Columns("uname, default").Prepare(),
getAll: acc.Select(t).Columns("uname,default").Prepare(),
isDefault: acc.Select(t).Columns("default").Where("uname = ?").Prepare(),
update: acc.Update(t).Set("default = ?").Where("uname = ?").Prepare(),
add: acc.Insert(t).Columns("uname, default").Fields("?,?").Prepare(),
add: acc.Insert(t).Columns("uname,default").Fields("?,?").Prepare(),
}
return acc.FirstError()
})
@ -275,7 +275,7 @@ func ResetTemplateOverrides() {
}
// CreateThemeTemplate creates a theme template on the current default theme
func CreateThemeTemplate(theme string, name string) {
func CreateThemeTemplate(theme, name string) {
Themes[theme].TmplPtr[name] = func(pi Page, w http.ResponseWriter) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap[name]
if !ok {

View File

@ -339,6 +339,7 @@ func (t *Topic) Delete() error {
return err
}
// TODO: Clear reply cache too
_, err = topicStmts.delete.Exec(t.ID)
t.cacheRemove()
if err != nil {

View File

@ -123,8 +123,6 @@ type UserStmts struct {
delete *sql.Stmt
setAvatar *sql.Stmt
setName *sql.Stmt
incTopics *sql.Stmt
updateLevel *sql.Stmt
update *sql.Stmt
// TODO: Split these into a sub-struct
@ -132,7 +130,13 @@ type UserStmts struct {
incPosts *sql.Stmt
incBigposts *sql.Stmt
incMegaposts *sql.Stmt
incPostStats *sql.Stmt
incBigpostStats *sql.Stmt
incMegapostStats *sql.Stmt
incLiked *sql.Stmt
incTopics *sql.Stmt
updateLevel *sql.Stmt
resetStats *sql.Stmt
decLiked *sql.Stmt
updateLastIP *sql.Stmt
@ -141,6 +145,10 @@ type UserStmts struct {
setPassword *sql.Stmt
scheduleAvatarResize *sql.Stmt
deletePosts *sql.Stmt
deleteProfilePosts *sql.Stmt
deleteReplyPosts *sql.Stmt
}
var userStmts UserStmts
@ -155,14 +163,19 @@ func init() {
delete: acc.Delete(u).Where(w).Prepare(),
setAvatar: acc.Update(u).Set("avatar=?").Where(w).Prepare(),
setName: acc.Update(u).Set("name=?").Where(w).Prepare(),
incTopics: acc.SimpleUpdate(u, "topics=topics+?", w),
updateLevel: acc.SimpleUpdate(u, "level=?", w),
update: acc.Update(u).Set("name=?,email=?,group=?").Where(w).Prepare(), // TODO: Implement user_count for users_groups on things which use this
incScore: acc.Update(u).Set("score=score+?").Where(w).Prepare(),
incPosts: acc.Update(u).Set("posts=posts+?").Where(w).Prepare(),
incBigposts: acc.Update(u).Set("posts=posts+?,bigposts=bigposts+?").Where(w).Prepare(),
incMegaposts: acc.Update(u).Set("posts=posts+?,bigposts=bigposts+?,megaposts=megaposts+?").Where(w).Prepare(),
incPostStats: acc.Update(u).Set("posts=posts+?,score=score+?,level=?").Where(w).Prepare(),
incBigpostStats: acc.Update(u).Set("posts=posts+?,bigposts=bigposts+?,score=score+?,level=?").Where(w).Prepare(),
incMegapostStats: acc.Update(u).Set("posts=posts+?,bigposts=bigposts+?,megaposts=megaposts+?,score=score+?,level=?").Where(w).Prepare(),
incTopics: acc.SimpleUpdate(u, "topics=topics+?", w),
updateLevel: acc.SimpleUpdate(u, "level=?", w),
resetStats: acc.Update(u).Set("score=0,posts=0,bigposts=0,megaposts=0,topics=0,level=0").Where(w).Prepare(),
incLiked: acc.Update(u).Set("liked=liked+?,lastLiked=UTC_TIMESTAMP()").Where(w).Prepare(),
decLiked: acc.Update(u).Set("liked=liked-?").Where(w).Prepare(),
//recalcLastLiked: acc...
@ -172,6 +185,10 @@ func init() {
setPassword: acc.Update(u).Set("password=?,salt=?").Where(w).Prepare(),
scheduleAvatarResize: acc.Insert("users_avatar_queue").Columns("uid").Fields("?").Prepare(),
deletePosts: acc.Select("topics").Columns("tid,parentID").Where("createdBy=?").Prepare(),
deleteProfilePosts: acc.Select("users_replies").Columns("rid").Where("createdBy=?").Prepare(),
deleteReplyPosts: acc.Select("replies").Columns("rid,tid").Where("createdBy=?").Prepare(),
}
return acc.FirstError()
})
@ -302,6 +319,107 @@ func (u *User) Delete() error {
return nil
}
func (u *User) DeletePosts() error {
rows, err := userStmts.deletePosts.Query(u.ID)
if err != nil {
return err
}
defer rows.Close()
defer TopicListThaw.Thaw()
defer u.CacheRemove()
updatedForums := make(map[int]int) // forum[count]
tc := Topics.GetCache()
for rows.Next() {
var tid, parentID int
err := rows.Scan(&tid, &parentID)
if err != nil {
return err
}
// TODO: Clear reply cache too
_, err = topicStmts.delete.Exec(tid)
if tc != nil {
tc.Remove(tid)
}
if err != nil {
return err
}
updatedForums[parentID] = updatedForums[parentID] + 1
_, err = topicStmts.deleteActivitySubs.Exec(tid)
if err != nil {
return err
}
_, err = topicStmts.deleteActivity.Exec(tid)
if err != nil {
return err
}
}
if err = rows.Err(); err != nil {
return err
}
err = u.ResetPostStats()
if err != nil {
return err
}
for fid, count := range updatedForums {
err := Forums.RemoveTopics(fid, count)
if err != nil && err != ErrNoRows {
return err
}
}
rows, err = userStmts.deleteProfilePosts.Query(u.ID)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var rid int
err := rows.Scan(&rid)
if err != nil {
return err
}
_, err = profileReplyStmts.delete.Exec(rid)
if err != nil {
return err
}
// TODO: Remove alerts.
}
err = rows.Err()
if err != nil {
return err
}
rows, err = userStmts.deleteReplyPosts.Query(u.ID)
if err != nil {
return err
}
defer rows.Close()
rc := Rstore.GetCache()
for rows.Next() {
var rid, tid int
err := rows.Scan(&rid,&tid)
if err != nil {
return err
}
_, err = replyStmts.delete.Exec(rid)
if err != nil {
return err
}
// TODO: Move this bit to *Topic
_, err = replyStmts.removeRepliesFromTopic.Exec(1, tid)
if tc != nil {
tc.Remove(tid)
}
_ = rc.Remove(rid)
// TODO: Remove alerts.
}
return rows.Err()
}
func (u *User) bindStmt(stmt *sql.Stmt, params ...interface{}) (err error) {
params = append(params, u.ID)
_, err = stmt.Exec(params...)
@ -339,7 +457,7 @@ func (u *User) ChangeGroup(group int) (err error) {
}
func (u *User) GetIP() string {
spl := strings.Split(u.LastIP,"-")
spl := strings.Split(u.LastIP, "-")
return spl[len(spl)-1]
}
@ -365,7 +483,6 @@ func (u *User) Update(name, email string, group int) (err error) {
}
func (u *User) IncreasePostStats(wcount int, topic bool) (err error) {
var mod int
baseScore := 1
if topic {
_, err = userStmts.incTopics.Exec(1, u.ID)
@ -376,38 +493,28 @@ func (u *User) IncreasePostStats(wcount int, topic bool) (err error) {
}
settings := SettingBox.Load().(SettingMap)
var mod, level int
if wcount >= settings["megapost_min_words"].(int) {
_, err = userStmts.incMegaposts.Exec(1, 1, 1, u.ID)
mod = 4
level = GetLevel(u.Score + baseScore + mod)
_, err = userStmts.incMegapostStats.Exec(1, 1, 1, baseScore+mod, level, u.ID)
} else if wcount >= settings["bigpost_min_words"].(int) {
_, err = userStmts.incBigposts.Exec(1, 1, u.ID)
mod = 1
level = GetLevel(u.Score + baseScore + mod)
_, err = userStmts.incBigpostStats.Exec(1, 1, baseScore+mod, level, u.ID)
} else {
_, err = userStmts.incPosts.Exec(1, u.ID)
level = GetLevel(u.Score + baseScore + mod)
_, err = userStmts.incPostStats.Exec(1, baseScore+mod, level, u.ID)
}
if err != nil {
return err
}
_, err = userStmts.incScore.Exec(baseScore+mod, u.ID)
if err != nil {
return err
}
//log.Print(u.Score + baseScore + mod)
// TODO: Use a transaction to prevent level desyncs?
level := GetLevel(u.Score + baseScore + mod)
//log.Print(level)
_, err = userStmts.updateLevel.Exec(level, u.ID)
if err != nil {
return err
}
err = GroupPromotions.PromoteIfEligible(u, level, u.Posts+1)
u.CacheRemove()
return err
}
func (u *User) DecreasePostStats(wcount int, topic bool) (err error) {
var mod int
baseScore := -1
if topic {
_, err = userStmts.incTopics.Exec(-1, u.ID)
@ -417,26 +524,24 @@ func (u *User) DecreasePostStats(wcount int, topic bool) (err error) {
baseScore = -2
}
// TODO: Use a transaction to prevent level desyncs?
var mod int
settings := SettingBox.Load().(SettingMap)
if wcount >= settings["megapost_min_words"].(int) {
_, err = userStmts.incMegaposts.Exec(-1, -1, -1, u.ID)
mod = 4
_, err = userStmts.incMegapostStats.Exec(-1, -1, -1, baseScore-mod, GetLevel(u.Score-baseScore-mod), u.ID)
} else if wcount >= settings["bigpost_min_words"].(int) {
_, err = userStmts.incBigposts.Exec(-1, -1, u.ID)
mod = 1
_, err = userStmts.incBigpostStats.Exec(-1, -1, baseScore-mod, GetLevel(u.Score-baseScore-mod), u.ID)
} else {
_, err = userStmts.incPosts.Exec(-1, u.ID)
_, err = userStmts.incPostStats.Exec(-1, baseScore-mod, GetLevel(u.Score-baseScore-mod), u.ID)
}
if err != nil {
u.CacheRemove()
return err
}
}
_, err = userStmts.incScore.Exec(baseScore-mod, u.ID)
if err != nil {
return err
}
// TODO: Use a transaction to prevent level desyncs?
_, err = userStmts.updateLevel.Exec(GetLevel(u.Score-baseScore-mod), u.ID)
func (u *User) ResetPostStats() (err error) {
_, err = userStmts.resetStats.Exec(u.ID)
u.CacheRemove()
return err
}

View File

@ -119,7 +119,7 @@ func (s *DefaultUserStore) GetByName(name string) (*User, error) {
// TODO: Optimise this, so we don't wind up hitting the database every-time for small gaps
// TODO: Make this a little more consistent with DefaultGroupStore's GetRange method
func (s *DefaultUserStore) GetOffset(offset int, perPage int) (users []*User, err error) {
func (s *DefaultUserStore) GetOffset(offset, perPage int) (users []*User, err error) {
rows, err := s.getOffset.Query(offset, perPage)
if err != nil {
return users, err

View File

@ -276,10 +276,10 @@ func InitWidgets() (fi error) {
return nil
}
func releaseWidgets(widgets []*Widget) {
for _, widget := range widgets {
if widget.ShutdownFunc != nil {
widget.ShutdownFunc(widget)
func releaseWidgets(ws []*Widget) {
for _, w := range ws {
if w.ShutdownFunc != nil {
w.ShutdownFunc(w)
}
}
}

View File

@ -148,6 +148,7 @@ var RouteMap = map[string]interface{}{
"routes.UnbanUser": routes.UnbanUser,
"routes.ActivateUser": routes.ActivateUser,
"routes.IPSearch": routes.IPSearch,
"routes.DeletePostsSubmit": routes.DeletePostsSubmit,
"routes.CreateTopicSubmit": routes.CreateTopicSubmit,
"routes.EditTopicSubmit": routes.EditTopicSubmit,
"routes.DeleteTopicSubmit": routes.DeleteTopicSubmit,
@ -319,48 +320,49 @@ var routeMapEnum = map[string]int{
"routes.UnbanUser": 122,
"routes.ActivateUser": 123,
"routes.IPSearch": 124,
"routes.CreateTopicSubmit": 125,
"routes.EditTopicSubmit": 126,
"routes.DeleteTopicSubmit": 127,
"routes.StickTopicSubmit": 128,
"routes.UnstickTopicSubmit": 129,
"routes.LockTopicSubmit": 130,
"routes.UnlockTopicSubmit": 131,
"routes.MoveTopicSubmit": 132,
"routes.LikeTopicSubmit": 133,
"routes.AddAttachToTopicSubmit": 134,
"routes.RemoveAttachFromTopicSubmit": 135,
"routes.ViewTopic": 136,
"routes.CreateReplySubmit": 137,
"routes.ReplyEditSubmit": 138,
"routes.ReplyDeleteSubmit": 139,
"routes.ReplyLikeSubmit": 140,
"routes.AddAttachToReplySubmit": 141,
"routes.RemoveAttachFromReplySubmit": 142,
"routes.ProfileReplyCreateSubmit": 143,
"routes.ProfileReplyEditSubmit": 144,
"routes.ProfileReplyDeleteSubmit": 145,
"routes.PollVote": 146,
"routes.PollResults": 147,
"routes.AccountLogin": 148,
"routes.AccountRegister": 149,
"routes.AccountLogout": 150,
"routes.AccountLoginSubmit": 151,
"routes.AccountLoginMFAVerify": 152,
"routes.AccountLoginMFAVerifySubmit": 153,
"routes.AccountRegisterSubmit": 154,
"routes.AccountPasswordReset": 155,
"routes.AccountPasswordResetSubmit": 156,
"routes.AccountPasswordResetToken": 157,
"routes.AccountPasswordResetTokenSubmit": 158,
"routes.DynamicRoute": 159,
"routes.UploadedFile": 160,
"routes.StaticFile": 161,
"routes.RobotsTxt": 162,
"routes.SitemapXml": 163,
"routes.OpenSearchXml": 164,
"routes.BadRoute": 165,
"routes.HTTPSRedirect": 166,
"routes.DeletePostsSubmit": 125,
"routes.CreateTopicSubmit": 126,
"routes.EditTopicSubmit": 127,
"routes.DeleteTopicSubmit": 128,
"routes.StickTopicSubmit": 129,
"routes.UnstickTopicSubmit": 130,
"routes.LockTopicSubmit": 131,
"routes.UnlockTopicSubmit": 132,
"routes.MoveTopicSubmit": 133,
"routes.LikeTopicSubmit": 134,
"routes.AddAttachToTopicSubmit": 135,
"routes.RemoveAttachFromTopicSubmit": 136,
"routes.ViewTopic": 137,
"routes.CreateReplySubmit": 138,
"routes.ReplyEditSubmit": 139,
"routes.ReplyDeleteSubmit": 140,
"routes.ReplyLikeSubmit": 141,
"routes.AddAttachToReplySubmit": 142,
"routes.RemoveAttachFromReplySubmit": 143,
"routes.ProfileReplyCreateSubmit": 144,
"routes.ProfileReplyEditSubmit": 145,
"routes.ProfileReplyDeleteSubmit": 146,
"routes.PollVote": 147,
"routes.PollResults": 148,
"routes.AccountLogin": 149,
"routes.AccountRegister": 150,
"routes.AccountLogout": 151,
"routes.AccountLoginSubmit": 152,
"routes.AccountLoginMFAVerify": 153,
"routes.AccountLoginMFAVerifySubmit": 154,
"routes.AccountRegisterSubmit": 155,
"routes.AccountPasswordReset": 156,
"routes.AccountPasswordResetSubmit": 157,
"routes.AccountPasswordResetToken": 158,
"routes.AccountPasswordResetTokenSubmit": 159,
"routes.DynamicRoute": 160,
"routes.UploadedFile": 161,
"routes.StaticFile": 162,
"routes.RobotsTxt": 163,
"routes.SitemapXml": 164,
"routes.OpenSearchXml": 165,
"routes.BadRoute": 166,
"routes.HTTPSRedirect": 167,
}
var reverseRouteMapEnum = map[int]string{
0: "routes.Overview",
@ -488,48 +490,49 @@ var reverseRouteMapEnum = map[int]string{
122: "routes.UnbanUser",
123: "routes.ActivateUser",
124: "routes.IPSearch",
125: "routes.CreateTopicSubmit",
126: "routes.EditTopicSubmit",
127: "routes.DeleteTopicSubmit",
128: "routes.StickTopicSubmit",
129: "routes.UnstickTopicSubmit",
130: "routes.LockTopicSubmit",
131: "routes.UnlockTopicSubmit",
132: "routes.MoveTopicSubmit",
133: "routes.LikeTopicSubmit",
134: "routes.AddAttachToTopicSubmit",
135: "routes.RemoveAttachFromTopicSubmit",
136: "routes.ViewTopic",
137: "routes.CreateReplySubmit",
138: "routes.ReplyEditSubmit",
139: "routes.ReplyDeleteSubmit",
140: "routes.ReplyLikeSubmit",
141: "routes.AddAttachToReplySubmit",
142: "routes.RemoveAttachFromReplySubmit",
143: "routes.ProfileReplyCreateSubmit",
144: "routes.ProfileReplyEditSubmit",
145: "routes.ProfileReplyDeleteSubmit",
146: "routes.PollVote",
147: "routes.PollResults",
148: "routes.AccountLogin",
149: "routes.AccountRegister",
150: "routes.AccountLogout",
151: "routes.AccountLoginSubmit",
152: "routes.AccountLoginMFAVerify",
153: "routes.AccountLoginMFAVerifySubmit",
154: "routes.AccountRegisterSubmit",
155: "routes.AccountPasswordReset",
156: "routes.AccountPasswordResetSubmit",
157: "routes.AccountPasswordResetToken",
158: "routes.AccountPasswordResetTokenSubmit",
159: "routes.DynamicRoute",
160: "routes.UploadedFile",
161: "routes.StaticFile",
162: "routes.RobotsTxt",
163: "routes.SitemapXml",
164: "routes.OpenSearchXml",
165: "routes.BadRoute",
166: "routes.HTTPSRedirect",
125: "routes.DeletePostsSubmit",
126: "routes.CreateTopicSubmit",
127: "routes.EditTopicSubmit",
128: "routes.DeleteTopicSubmit",
129: "routes.StickTopicSubmit",
130: "routes.UnstickTopicSubmit",
131: "routes.LockTopicSubmit",
132: "routes.UnlockTopicSubmit",
133: "routes.MoveTopicSubmit",
134: "routes.LikeTopicSubmit",
135: "routes.AddAttachToTopicSubmit",
136: "routes.RemoveAttachFromTopicSubmit",
137: "routes.ViewTopic",
138: "routes.CreateReplySubmit",
139: "routes.ReplyEditSubmit",
140: "routes.ReplyDeleteSubmit",
141: "routes.ReplyLikeSubmit",
142: "routes.AddAttachToReplySubmit",
143: "routes.RemoveAttachFromReplySubmit",
144: "routes.ProfileReplyCreateSubmit",
145: "routes.ProfileReplyEditSubmit",
146: "routes.ProfileReplyDeleteSubmit",
147: "routes.PollVote",
148: "routes.PollResults",
149: "routes.AccountLogin",
150: "routes.AccountRegister",
151: "routes.AccountLogout",
152: "routes.AccountLoginSubmit",
153: "routes.AccountLoginMFAVerify",
154: "routes.AccountLoginMFAVerifySubmit",
155: "routes.AccountRegisterSubmit",
156: "routes.AccountPasswordReset",
157: "routes.AccountPasswordResetSubmit",
158: "routes.AccountPasswordResetToken",
159: "routes.AccountPasswordResetTokenSubmit",
160: "routes.DynamicRoute",
161: "routes.UploadedFile",
162: "routes.StaticFile",
163: "routes.RobotsTxt",
164: "routes.SitemapXml",
165: "routes.OpenSearchXml",
166: "routes.BadRoute",
167: "routes.HTTPSRedirect",
}
var osMapEnum = map[string]int{
"unknown": 0,
@ -687,7 +690,7 @@ type HTTPSRedirect struct {}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
co.RouteViewCounter.Bump(166)
co.RouteViewCounter.Bump(167)
dest := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
}
@ -895,7 +898,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
co.GlobalViewCounter.Bump()
if prefix == "/s" { //old prefix: /static
co.RouteViewCounter.Bump(161)
co.RouteViewCounter.Bump(162)
req.URL.Path += extraData
routes.StaticFile(w, req)
return
@ -2132,6 +2135,19 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
err = routes.IPSearch(w,req,user,head)
case "/users/delete-posts/submit/":
err = c.NoSessionMismatch(w,req,user)
if err != nil {
return err
}
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
co.RouteViewCounter.Bump(125)
err = routes.DeletePostsSubmit(w,req,user,extraData)
}
case "/topic":
switch(req.URL.Path) {
@ -2150,7 +2166,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(125)
co.RouteViewCounter.Bump(126)
err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2163,7 +2179,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(126)
co.RouteViewCounter.Bump(127)
err = routes.EditTopicSubmit(w,req,user,extraData)
case "/topic/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2177,7 +2193,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
}
req.URL.Path += extraData
co.RouteViewCounter.Bump(127)
co.RouteViewCounter.Bump(128)
err = routes.DeleteTopicSubmit(w,req,user)
case "/topic/stick/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2190,7 +2206,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(128)
co.RouteViewCounter.Bump(129)
err = routes.StickTopicSubmit(w,req,user,extraData)
case "/topic/unstick/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2203,7 +2219,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(129)
co.RouteViewCounter.Bump(130)
err = routes.UnstickTopicSubmit(w,req,user,extraData)
case "/topic/lock/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2217,7 +2233,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
}
req.URL.Path += extraData
co.RouteViewCounter.Bump(130)
co.RouteViewCounter.Bump(131)
err = routes.LockTopicSubmit(w,req,user)
case "/topic/unlock/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2230,7 +2246,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(131)
co.RouteViewCounter.Bump(132)
err = routes.UnlockTopicSubmit(w,req,user,extraData)
case "/topic/move/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2243,7 +2259,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(132)
co.RouteViewCounter.Bump(133)
err = routes.MoveTopicSubmit(w,req,user,extraData)
case "/topic/like/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2256,7 +2272,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(133)
co.RouteViewCounter.Bump(134)
err = routes.LikeTopicSubmit(w,req,user,extraData)
case "/topic/attach/add/submit/":
err = c.MemberOnly(w,req,user)
@ -2273,7 +2289,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(134)
co.RouteViewCounter.Bump(135)
err = routes.AddAttachToTopicSubmit(w,req,user,extraData)
case "/topic/attach/remove/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2286,10 +2302,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(135)
co.RouteViewCounter.Bump(136)
err = routes.RemoveAttachFromTopicSubmit(w,req,user,extraData)
default:
co.RouteViewCounter.Bump(136)
co.RouteViewCounter.Bump(137)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2313,7 +2329,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(137)
co.RouteViewCounter.Bump(138)
err = routes.CreateReplySubmit(w,req,user)
case "/reply/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2326,7 +2342,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(138)
co.RouteViewCounter.Bump(139)
err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2339,7 +2355,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(139)
co.RouteViewCounter.Bump(140)
err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2352,7 +2368,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(140)
co.RouteViewCounter.Bump(141)
err = routes.ReplyLikeSubmit(w,req,user,extraData)
case "/reply/attach/add/submit/":
err = c.MemberOnly(w,req,user)
@ -2369,7 +2385,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(141)
co.RouteViewCounter.Bump(142)
err = routes.AddAttachToReplySubmit(w,req,user,extraData)
case "/reply/attach/remove/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2382,7 +2398,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(142)
co.RouteViewCounter.Bump(143)
err = routes.RemoveAttachFromReplySubmit(w,req,user,extraData)
}
case "/profile":
@ -2398,7 +2414,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(143)
co.RouteViewCounter.Bump(144)
err = routes.ProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2411,7 +2427,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(144)
co.RouteViewCounter.Bump(145)
err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2424,7 +2440,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(145)
co.RouteViewCounter.Bump(146)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
}
case "/poll":
@ -2440,23 +2456,23 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(146)
co.RouteViewCounter.Bump(147)
err = routes.PollVote(w,req,user,extraData)
case "/poll/results/":
co.RouteViewCounter.Bump(147)
co.RouteViewCounter.Bump(148)
err = routes.PollResults(w,req,user,extraData)
}
case "/accounts":
switch(req.URL.Path) {
case "/accounts/login/":
co.RouteViewCounter.Bump(148)
co.RouteViewCounter.Bump(149)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
err = routes.AccountLogin(w,req,user,head)
case "/accounts/create/":
co.RouteViewCounter.Bump(149)
co.RouteViewCounter.Bump(150)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2473,7 +2489,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(150)
co.RouteViewCounter.Bump(151)
err = routes.AccountLogout(w,req,user)
case "/accounts/login/submit/":
err = c.ParseForm(w,req,user)
@ -2481,10 +2497,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(151)
co.RouteViewCounter.Bump(152)
err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/mfa_verify/":
co.RouteViewCounter.Bump(152)
co.RouteViewCounter.Bump(153)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2496,7 +2512,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(153)
co.RouteViewCounter.Bump(154)
err = routes.AccountLoginMFAVerifySubmit(w,req,user)
case "/accounts/create/submit/":
err = c.ParseForm(w,req,user)
@ -2504,10 +2520,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(154)
co.RouteViewCounter.Bump(155)
err = routes.AccountRegisterSubmit(w,req,user)
case "/accounts/password-reset/":
co.RouteViewCounter.Bump(155)
co.RouteViewCounter.Bump(156)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2519,10 +2535,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(156)
co.RouteViewCounter.Bump(157)
err = routes.AccountPasswordResetSubmit(w,req,user)
case "/accounts/password-reset/token/":
co.RouteViewCounter.Bump(157)
co.RouteViewCounter.Bump(158)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2534,7 +2550,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(158)
co.RouteViewCounter.Bump(159)
err = routes.AccountPasswordResetTokenSubmit(w,req,user)
}
/*case "/sitemaps": // TODO: Count these views
@ -2551,7 +2567,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
h.Del("Content-Type")
h.Del("Content-Encoding")
}
co.RouteViewCounter.Bump(160)
co.RouteViewCounter.Bump(161)
req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this?
r.UploadHandler(w,req) // TODO: Count these views
@ -2561,7 +2577,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
// TODO: Add support for favicons and robots.txt files
switch(extraData) {
case "robots.txt":
co.RouteViewCounter.Bump(162)
co.RouteViewCounter.Bump(163)
return routes.RobotsTxt(w,req)
case "favicon.ico":
gzw, ok := w.(c.GzipResponseWriter)
@ -2575,10 +2591,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
routes.StaticFile(w,req)
return nil
case "opensearch.xml":
co.RouteViewCounter.Bump(164)
co.RouteViewCounter.Bump(165)
return routes.OpenSearchXml(w,req)
/*case "sitemap.xml":
co.RouteViewCounter.Bump(163)
co.RouteViewCounter.Bump(164)
return routes.SitemapXml(w,req)*/
}
return c.NotFound(w,req,nil)
@ -2589,7 +2605,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
r.RUnlock()
if ok {
co.RouteViewCounter.Bump(159) // TODO: Be more specific about *which* dynamic route it is
co.RouteViewCounter.Bump(160) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData
return handle(w,req,user)
}
@ -2600,7 +2616,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
} else {
r.DumpRequest(req,"Bad Route")
}
co.RouteViewCounter.Bump(165)
co.RouteViewCounter.Bump(166)
return c.NotFound(w,req,nil)
}
return err

View File

@ -720,33 +720,38 @@
"paginator.next_page":"",
"paginator.next_page_aria":"Go to the next page",
"profile_login_for_options":"Login for options",
"profile_send_message":"Send Message",
"profile_add_friend":"Add Friend",
"profile_unban":"Unban",
"profile_ban":"Ban",
"profile_block":"Block",
"profile_unblock":"Unblock",
"profile_report_user_tooltip":"Report User",
"profile_report_user_aria":"Report User",
"profile_ban_user_head":"Ban User",
"profile_ban_user_notice":"If all the fields are left blank, the ban will be permanent.",
"profile_ban_user_days":"Days",
"profile_ban_user_weeks":"Weeks",
"profile_ban_user_months":"Months",
"profile_ban_user_reason":"Reason",
"profile_ban_user_button":"Ban User",
"profile_comments_head":"Comments",
"profile_comments_edit_tooltip":"Edit Item",
"profile_comments_edit_aria":"Edit Item",
"profile_comments_delete_tooltip":"Delete Item",
"profile_comments_delete_aria":"Delete Item",
"profile_comments_report_tooltip":"Report Item",
"profile_comments_report_aria":"Report Item",
"profile_comments_form_content":"Insert comment here",
"profile_comments_form_button":"Create Reply",
"profile_comments_form_guest":"You need to login to comment on this profile.",
"profile_owner_tag":"Profile Owner",
"profile.login_for_options":"Login for options",
"profile.send_message":"Send Message",
"profile.add_friend":"Add Friend",
"profile.unban":"Unban",
"profile.ban":"Ban",
"profile.delete_posts":"Delete Posts",
"profile.block":"Block",
"profile.unblock":"Unblock",
"profile.report_user_tooltip":"Report User",
"profile.report_user_aria":"Report User",
"profile.ban_user_head":"Ban User",
"profile.ban_user_notice":"If all the fields are left blank, the ban will be permanent.",
"profile.ban_user_days":"Days",
"profile.ban_user_weeks":"Weeks",
"profile.ban_user_months":"Months",
"profile.ban_user_reason":"Reason",
"profile.ban_user_button":"Ban User",
"profile.ban_delete_posts":"Delete Posts",
"profile.delete_posts_head":"Delete Posts",
"profile.delete_posts_notice":"Would you like to delete %d posts?",
"profile.delete_posts_button":"Delete Posts",
"profile.comments_head":"Comments",
"profile.comments_edit_tooltip":"Edit Item",
"profile.comments_edit_aria":"Edit Item",
"profile.comments_delete_tooltip":"Delete Item",
"profile.comments_delete_aria":"Delete Item",
"profile.comments_report_tooltip":"Report Item",
"profile.comments_report_aria":"Report Item",
"profile.comments_form_content":"Insert comment here",
"profile.comments_form_button":"Create Reply",
"profile.comments_form_guest":"You need to login to comment on this profile.",
"profile.owner_tag":"Profile Owner",
"ip_search_head":"IP Search",
"ip_search_search_button":"Search",

View File

@ -4,6 +4,9 @@ function handle_profile_hashbit() {
case "ban_user":
hash_class = "ban_user_hash";
break;
case "delete_posts":
hash_class = "delete_posts_hash";
break;
default:
console.log("Unknown hashbit");
return;

View File

@ -505,7 +505,7 @@ func skipFunctionCall(data string, index int) int {
return index
}
func writeFile(name string, content string) (err error) {
func writeFile(name, content string) (err error) {
f, err := os.Create(name)
if err != nil {
return err

View File

@ -111,6 +111,7 @@ func usersRoutes() *RouteGroup {
Action("routes.UnbanUser", "/users/unban/", "extraData"),
Action("routes.ActivateUser", "/users/activate/", "extraData"),
MView("routes.IPSearch", "/users/ips/"), // TODO: .Perms("ViewIPs")?
Action("routes.DeletePostsSubmit", "/users/delete-posts/submit/", "extraData"),
)
}

View File

@ -88,7 +88,7 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user c.User, header *c.
if group.Tag != "" {
ru.Tag = group.Tag
} else if puser.ID == ru.CreatedBy {
ru.Tag = phrases.GetTmplPhrase("profile_owner_tag")
ru.Tag = phrases.GetTmplPhrase("profile.owner_tag")
}
// TODO: Add a hook here

View File

@ -13,7 +13,6 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid str
if !user.Perms.BanUsers {
return c.NoPermissions(w, r, user)
}
uid, err := strconv.Atoi(suid)
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
@ -28,7 +27,6 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid str
} else if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one?
if targetUser.IsMod {
return c.LocalError("You may not ban another staff member.", w, r, user)
@ -40,44 +38,59 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid str
return c.LocalError("The user you're trying to unban is already banned.", w, r, user)
}
durationDays, err := strconv.Atoi(r.FormValue("ban-duration-days"))
durDays, err := strconv.Atoi(r.FormValue("dur-days"))
if err != nil {
return c.LocalError("You can only use whole numbers for the number of days", w, r, user)
}
durationWeeks, err := strconv.Atoi(r.FormValue("ban-duration-weeks"))
durWeeks, err := strconv.Atoi(r.FormValue("dur-weeks"))
if err != nil {
return c.LocalError("You can only use whole numbers for the number of weeks", w, r, user)
}
durationMonths, err := strconv.Atoi(r.FormValue("ban-duration-months"))
durMonths, err := strconv.Atoi(r.FormValue("dur-months"))
if err != nil {
return c.LocalError("You can only use whole numbers for the number of months", w, r, user)
}
var duration time.Duration
if durationDays > 1 && durationWeeks > 1 && durationMonths > 1 {
duration, _ = time.ParseDuration("0")
} else {
var seconds int
seconds += durationDays * int(c.Day)
seconds += durationWeeks * int(c.Week)
seconds += durationMonths * int(c.Month)
duration, _ = time.ParseDuration(strconv.Itoa(seconds) + "s")
deletePosts := false
switch r.FormValue("delete-posts") {
case "1":
deletePosts = true
}
err = targetUser.Ban(duration, user.ID)
var dur time.Duration
if durDays > 1 && durWeeks > 1 && durMonths > 1 {
dur, _ = time.ParseDuration("0")
} else {
var secs int
secs += durDays * int(c.Day)
secs += durWeeks * int(c.Week)
secs += durMonths * int(c.Month)
dur, _ = time.ParseDuration(strconv.Itoa(secs) + "s")
}
err = targetUser.Ban(dur, user.ID)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to ban no longer exists.", w, r, user)
} else if err != nil {
return c.InternalError(err, w, r)
}
err = c.ModLogs.Create("ban", uid, "user", user.GetIP(), user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
if deletePosts {
err = targetUser.DeletePosts()
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to ban no longer exists.", w, r, user)
} else if err != nil {
return c.InternalError(err, w, r)
}
err = c.ModLogs.Create("delete-posts", uid, "user", user.GetIP(), user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
}
// TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable()
skip, rerr := hTbl.VhookSkippable("action_end_ban_user", targetUser.ID, &user)
@ -93,7 +106,6 @@ func UnbanUser(w http.ResponseWriter, r *http.Request, user c.User, suid string)
if !user.Perms.BanUsers {
return c.NoPermissions(w, r, user)
}
uid, err := strconv.Atoi(suid)
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
@ -139,7 +151,6 @@ func ActivateUser(w http.ResponseWriter, r *http.Request, user c.User, suid stri
if !user.Perms.ActivateUsers {
return c.NoPermissions(w, r, user)
}
uid, err := strconv.Atoi(suid)
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
@ -175,3 +186,45 @@ func ActivateUser(w http.ResponseWriter, r *http.Request, user c.User, suid stri
http.Redirect(w, r, "/user/"+strconv.Itoa(targetUser.ID), http.StatusSeeOther)
return nil
}
func DeletePostsSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid string) c.RouteError {
if !user.Perms.BanUsers {
return c.NoPermissions(w, r, user)
}
uid, err := strconv.Atoi(suid)
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
}
targetUser, err := c.Users.Get(uid)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to purge posts of no longer exists.", w, r, user)
} else if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one?
if targetUser.IsMod {
return c.LocalError("You may not purge the posts of another staff member.", w, r, user)
}
err = targetUser.DeletePosts()
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to purge posts of no longer exists.", w, r, user)
} else if err != nil {
return c.InternalError(err, w, r)
}
err = c.ModLogs.Create("delete-posts", uid, "user", user.GetIP(), user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable()
skip, rerr := hTbl.VhookSkippable("action_end_delete_posts", targetUser.ID, &user)
if skip || rerr != nil {
return rerr
}
http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther)
return nil
}

View File

@ -14,10 +14,10 @@
<form action="/user/convo/create/submit/{{.Convo.ID}}?s={{.CurrentUser.Session}}" method="post">
<div class="colstack_item topic_reply_form" style="border-top:none;">
<div class="formrow">
<div class="formitem"><textarea class="input_content" name="content" placeholder="{{lang "profile_comments_form_content"}}"></textarea></div>
<div class="formitem"><textarea class="input_content" name="content" placeholder="{{lang "profile.comments_form_content"}}"></textarea></div>
</div>
<div class="formrow quick_button_row">
<div class="formitem"><button name="reply-button" class="formbutton">{{lang "profile_comments_form_button"}}</button></div>
<div class="formitem"><button name="reply-button" class="formbutton">{{lang "profile.comments_form_button"}}</button></div>
</div>
</div>
</form>

View File

@ -6,11 +6,11 @@
<span class="controls">
<a href="{{.User.Link}}" class="real_username username">{{.User.Name}}</a>&nbsp;&nbsp;
{{if .CanModify}}<a href="/user/convo/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
{{if .CanModify}}<a href="/user/convo/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile.comments_edit_tooltip"}}" aria-label="{{lang "profile.comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/user/convo/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>{{end}}
<a href="/user/convo/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile.comments_delete_tooltip"}}" aria-label="{{lang "profile.comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=convo-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=convo-reply"><button class="username report_item flag_label" title="{{lang "profile.comments_report_tooltip"}}" aria-label="{{lang "profile.comments_report_aria"}}"></button></a>
{{if .User.Tag}}<a class="username hide_on_mobile user_tag" style="float:right;">{{.User.Tag}}</a>{{end}}
</span>

View File

@ -10,12 +10,12 @@
</div>
<span class="controls">
{{if .CanModify}}
<a href="/user/convo/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/user/convo/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile.comments_edit_tooltip"}}" aria-label="{{lang "profile.comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/user/convo/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>
<a href="/user/convo/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile.comments_delete_tooltip"}}" aria-label="{{lang "profile.comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>
{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=convo-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=convo-reply"><button class="username report_item flag_label" title="{{lang "profile.comments_report_tooltip"}}" aria-label="{{lang "profile.comments_report_aria"}}"></button></a>
</span>
</div>
<div class="content_column">

View File

@ -5,7 +5,7 @@
<div id="profile_left_pane" class="rowmenu">
<div class="topBlock">
<div class="rowitem avatarRow">
<a href="{{.ProfileOwner.Avatar}}"><img src="{{.ProfileOwner.Avatar}}" class="avatar" alt="Avatar" title="{{.ProfileOwner.Name}}'s Avatar" aria-hidden="true" /></a>
<a href="{{.ProfileOwner.Avatar}}"><img src="{{.ProfileOwner.Avatar}}" class="avatar" alt="Avatar" title="{{.ProfileOwner.Name}}'s Avatar" aria-hidden="true"/></a>
</div>
<div class="rowitem nameRow">
<span class="profileName" title="{{.ProfileOwner.Name}}">{{.ProfileOwner.Name}}</span>{{if .ProfileOwner.Tag}}<span class="username" title="{{.ProfileOwner.Tag}}">{{.ProfileOwner.Tag}}</span>{{end}}
@ -25,23 +25,29 @@
</div>
<div class="passiveBlock">
{{if not .CurrentUser.Loggedin}}<div class="rowitem passive">
<a class="profile_menu_item">{{lang "profile_login_for_options"}}</a>
<a class="profile_menu_item">{{lang "profile.login_for_options"}}</a>
</div>{{else}}
{{if .CanMessage}}<div class="rowitem passive">
<a href="/user/convos/create/" class="profile_menu_item">{{lang "profile_send_message"}}</a>
<a href="/user/convos/create/" class="profile_menu_item">{{lang "profile.send_message"}}</a>
</div>{{end}}
<!--<div class="rowitem passive">
<a class="profile_menu_item">{{lang "profile_add_friend"}}</a>
<a class="profile_menu_item">{{lang "profile.add_friend"}}</a>
</div>-->
{{if (.CurrentUser.IsSuperMod) and not (.ProfileOwner.IsSuperMod)}}<div class="rowitem passive">
{{if .ProfileOwner.IsBanned}}<a href="/users/unban/{{.ProfileOwner.ID}}?s={{.CurrentUser.Session}}" class="profile_menu_item">{{lang "profile_unban"}}</a>
{{else}}<a href="#ban_user" class="profile_menu_item">{{lang "profile_ban"}}</a>{{end}}
</div>{{end}}
<div class="rowitem passive">
{{if .Blocked}}<a href="/user/block/remove/{{.ProfileOwner.ID}}" class="profile_menu_item">{{lang "profile_unblock"}}</a>{{else}}<a href="/user/block/create/{{.ProfileOwner.ID}}" class="profile_menu_item">{{lang "profile_block"}}</a>{{end}}
{{if .ProfileOwner.IsBanned}}<a href="/users/unban/{{.ProfileOwner.ID}}?s={{.CurrentUser.Session}}" class="profile_menu_item">{{lang "profile.unban"}}</a>
{{else}}<a href="#ban_user" class="profile_menu_item">{{lang "profile.ban"}}</a>{{end}}
</div>
<div class="rowitem passive">
<a href="/report/submit/{{.ProfileOwner.ID}}?s={{.CurrentUser.Session}}&type=user" class="profile_menu_item report_item" aria-label="{{lang "profile_report_user_aria"}}" title="{{lang "profile_report_user_tooltip"}}"></a>
<a href="#delete_posts" class="profile_menu_item">{{lang "profile.delete_posts"}}</a>
</div>
{{end}}
<div class="rowitem passive">
{{if .Blocked}}<a href="/user/block/remove/{{.ProfileOwner.ID}}" class="profile_menu_item">{{lang "profile.unblock"}}</a>{{else}}<a href="/user/block/create/{{.ProfileOwner.ID}}" class="profile_menu_item">{{lang "profile.block"}}</a>{{end}}
</div>
<div class="rowitem passive">
<a href="/report/submit/{{.ProfileOwner.ID}}?s={{.CurrentUser.Session}}&type=user" class="profile_menu_item report_item" aria-label="{{lang "profile.report_user_aria"}}" title="{{lang "profile.report_user_tooltip"}}"></a>
</div>
{{end}}
</div>
@ -53,37 +59,58 @@
{{if .CurrentUser.Perms.BanUsers}}
<!-- TODO: Inline the display:none; CSS -->
<div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display:none;">
<div class="rowitem"><h1><a>{{lang "profile_ban_user_head"}}</a></h1></div>
<div class="rowitem"><h1><a>{{lang "profile.ban_user_head"}}</a></h1></div>
</div>
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/{{.ProfileOwner.ID}}?s={{.CurrentUser.Session}}" method="post" style="display:none;">
<div class="the_form">
{{/** TODO: Put a JS duration calculator here instead of this text? **/}}
<div class="colline">{{lang "profile_ban_user_notice"}}</div>
<div class="colline">{{lang "profile.ban_user_notice"}}</div>
<div class="colstack_item">
<div class="formrow real_first_child">
<div class="formitem formlabel"><a>{{lang "profile_ban_user_days"}}</a></div>
<div class="formitem formlabel"><a>{{lang "profile.ban_user_days"}}</a></div>
<div class="formitem">
<input name="ban-duration-days" type="number" value=0 min=0 />
<input name="dur-days" type="number" value=0 min=0/>
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "profile_ban_user_weeks"}}</a></div>
<div class="formitem formlabel"><a>{{lang "profile.ban_user_weeks"}}</a></div>
<div class="formitem">
<input name="ban-duration-weeks" type="number" value=0 min=0 />
<input name="dur-weeks" type="number" value=0 min=0/>
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "profile_ban_user_months"}}</a></div>
<div class="formitem formlabel"><a>{{lang "profile.ban_user_months"}}</a></div>
<div class="formitem">
<input name="ban-duration-months" type="number" value=0 min=0 />
<input name="dur-months" type="number" value=0 min=0/>
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "profile.ban_delete_posts"}}</a></div>
<div class="formitem"><select name="delete-posts">
<option value=1>{{lang "option_yes"}}</option>
<option selected value=0>{{lang "option_no"}}</option>
</select></div>
</div>
{{/**<!--<div class="formrow">
<div class="formitem formlabel"><a>{{lang "profile_ban_user_reason"}}</a></div>
<div class="formitem formlabel"><a>{{lang "profile.ban_user_reason"}}</a></div>
<div class="formitem"><textarea name="ban-reason" placeholder="A really horrible person" required></textarea></div>
</div>-->**/}}
<div class="formrow">
<div class="formitem"><button name="ban-button" class="formbutton form_middle_button">{{lang "profile_ban_user_button"}}</button></div>
<div class="formitem"><button name="ban-button" class="formbutton form_middle_button">{{lang "profile.ban_user_button"}}</button></div>
</div>
</div>
</div>
</form>
<div id="delete_posts_head" class="colstack_item colstack_head hash_hide delete_posts_hash" style="display:none;">
<div class="rowitem"><h1><a>{{lang "profile.delete_posts_head"}}</a></h1></div>
</div>
<form id="delete_posts_form" class="hash_hide delete_posts_hash" action="/users/delete-posts/submit/{{.ProfileOwner.ID}}?s={{.CurrentUser.Session}}" method="post" style="display:none;">
<div class="the_form">
<div class="colline">{{langf "profile.delete_posts_notice" .ProfileOwner.Posts}}</div>
<div class="colstack_item">
<div class="formrow real_first_child">
<div class="formitem"><button name="delete-posts-button" class="formbutton form_middle_button">{{lang "profile.delete_posts_button"}}</button></div>
</div>
</div>
</div>
@ -92,7 +119,7 @@
{{end}}
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1><a>{{lang "profile_comments_head"}}</a></h1></div>
<div class="rowitem"><h1><a>{{lang "profile.comments_head"}}</a></h1></div>
</div>
<div id="profile_comments" class="colstack_item hash_hide">{{template "profile_comments_row.html" . }}</div>
@ -102,17 +129,17 @@
<input name="uid" value='{{.ProfileOwner.ID}}' type="hidden" />
<div class="colstack_item topic_reply_form" style="border-top:none;">
<div class="formrow">
<div class="formitem"><textarea class="input_content" name="content" placeholder="{{lang "profile_comments_form_content"}}"></textarea></div>
<div class="formitem"><textarea class="input_content" name="content" placeholder="{{lang "profile.comments_form_content"}}"></textarea></div>
</div>
<div class="formrow quick_button_row">
<div class="formitem"><button name="reply-button" class="formbutton">{{lang "profile_comments_form_button"}}</button></div>
<div class="formitem"><button name="reply-button" class="formbutton">{{lang "profile.comments_form_button"}}</button></div>
</div>
</div>
</form>
{{end}}
{{else}}
<div class="colstack_item" style="border-top:none;">
<div class="rowitem passive">{{lang "profile_comments_form_guest"}}</div>
<div class="rowitem passive">{{lang "profile.comments_form_guest"}}</div>
</div>
{{end}}
</div>

View File

@ -6,11 +6,11 @@
<span class="controls">
<a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>&nbsp;&nbsp;
{{if $.CurrentUser.IsMod}}<a href="/profile/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
{{if $.CurrentUser.IsMod}}<a href="/profile/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile.comments_edit_tooltip"}}" aria-label="{{lang "profile.comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>{{end}}
<a href="/profile/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile.comments_delete_tooltip"}}" aria-label="{{lang "profile.comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label" title="{{lang "profile.comments_report_tooltip"}}" aria-label="{{lang "profile.comments_report_aria"}}"></button></a>
{{if .Tag}}<a class="username hide_on_mobile user_tag" style="float:right;">{{.Tag}}</a>{{end}}
</span>

View File

@ -10,10 +10,10 @@
</div>
<span class="controls">
{{if $.CurrentUser.IsMod}}
<a href="/profile/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>
<a href="/profile/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile.comments_edit_tooltip"}}" aria-label="{{lang "profile.comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile.comments_delete_tooltip"}}" aria-label="{{lang "profile.comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>
{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label" title="{{lang "profile.comments_report_tooltip"}}" aria-label="{{lang "profile.comments_report_aria"}}"></button></a>
</span>
</div>
<div class="content_column">