Documentation ¶
Index ¶
- Variables
- func A(handler func(w http.ResponseWriter, r *http.Request, sess Session)) func(w http.ResponseWriter, r *http.Request)
- func Authenticate() error
- func ClearSession(w http.ResponseWriter, r *http.Request)
- func ErrForbiddenHandler(w http.ResponseWriter, r *http.Request)
- func ErrNotFoundHandler(w http.ResponseWriter, r *http.Request)
- func ErrServerHandler(w http.ResponseWriter, r *http.Request)
- func FaviconHandler(w http.ResponseWriter, r *http.Request)
- func ImageHandler(w http.ResponseWriter, r *http.Request)
- func LogoutHandler(w http.ResponseWriter, r *http.Request)
- func ScriptHandler(w http.ResponseWriter, r *http.Request)
- func StyleHandler(w http.ResponseWriter, r *http.Request)
- func TestHandler(w http.ResponseWriter, r *http.Request)
- func UA(handler func(w http.ResponseWriter, r *http.Request, sess Session)) func(w http.ResponseWriter, r *http.Request)
- type CommonData
- type ExtraNote
- type Session
Constants ¶
This section is empty.
Variables ¶
View Source
var AdminIndexHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { if !sess.IsUserSuperAdmin() { ErrForbiddenHandler(w, r) return } linkID := r.PostFormValue("linkid") if r.Method == "POST" && linkID == "" { forumName := strings.TrimSpace(r.PostFormValue("forum_name")) headerMsg := strings.TrimSpace(r.PostFormValue("header_msg")) censoredWords := r.PostFormValue("censored_words") loginMsg := strings.TrimSpace(r.PostFormValue("login_msg")) signupMsg := strings.TrimSpace(r.PostFormValue("signup_msg")) signupDisabled := "0" groupCreationDisabled := "0" imageUploadEnabled := "0" allowGroupSubscription := "0" allowTopicSubscription := "0" readOnlyMode := "0" dataDir := r.PostFormValue("data_dir") bodyAppendage := r.PostFormValue("body_appendage") defaultFromEmail := r.PostFormValue("default_from_mail") smtpHost := r.PostFormValue("smtp_host") smtpPort := r.PostFormValue("smtp_port") smtpUser := r.PostFormValue("smtp_user") smtpPass := r.PostFormValue("smtp_pass") if r.PostFormValue("signup_disabled") != "" { signupDisabled = "1" } if r.PostFormValue("group_creation_disabled") != "" { groupCreationDisabled = "1" } if r.PostFormValue("image_upload_enabled") != "" { imageUploadEnabled = "1" } if r.PostFormValue("allow_group_subscription") != "" { allowGroupSubscription = "1" } if r.PostFormValue("allow_topic_subscription") != "" { allowTopicSubscription = "1" } if r.PostFormValue(models.ReadOnlyMode) != "" { readOnlyMode = "1" } if dataDir != "" { if dataDir[len(dataDir)-1] != '/' { dataDir = dataDir + "/" } } errMsg := "" if forumName == "" { errMsg = "Forum name is empty." } if errMsg == "" { models.WriteConfig(models.ForumName, forumName) models.WriteConfig(models.HeaderMsg, headerMsg) models.WriteConfig(models.LoginMsg, loginMsg) models.WriteConfig(models.SignupMsg, signupMsg) models.WriteConfig(models.SignupDisabled, signupDisabled) models.WriteConfig(models.CensoredWords, censoredWords) models.WriteConfig(models.GroupCreationDisabled, groupCreationDisabled) models.WriteConfig(models.ImageUploadEnabled, imageUploadEnabled) models.WriteConfig(models.AllowGroupSubscription, allowGroupSubscription) models.WriteConfig(models.AllowTopicSubscription, allowTopicSubscription) models.WriteConfig(models.ReadOnlyMode, readOnlyMode) models.WriteConfig(models.DataDir, dataDir) models.WriteConfig(models.BodyAppendage, bodyAppendage) models.WriteConfig(models.DefaultFromMail, defaultFromEmail) models.WriteConfig(models.SMTPHost, smtpHost) models.WriteConfig(models.SMTPPort, smtpPort) models.WriteConfig(models.SMTPUser, smtpUser) models.WriteConfig(models.SMTPPass, smtpPass) sess.SetFlashMsg("Update successful.") } else { sess.SetFlashMsg(errMsg) } http.Redirect(w, r, "/admin", http.StatusSeeOther) return } if r.Method == "POST" && linkID != "" { name := r.PostFormValue("name") URL := r.PostFormValue("url") content := r.PostFormValue("content") if linkID == "new" { if name != "" && (URL != "" || content != "") { db.Exec(`INSERT INTO extranotes(name, URL, content, created_date, updated_date) VALUES(?, ?, ?, ?, ?);`, name, URL, content, time.Now().Unix(), time.Now().Unix()) } else { sess.SetFlashMsg("Enter an external URL or type some content for the footer link.") } } else { if r.PostFormValue("submit") == "Delete" { db.Exec(`DELETE FROM extranotes WHERE id=?;`, linkID) } else { db.Exec(`UPDATE extranotes SET name=?, URL=?, content=?, updated_date=? WHERE id=?;`, name, URL, content, int64(time.Now().Unix()), linkID) } } http.Redirect(w, r, "/admin", http.StatusSeeOther) return } rows := db.Query(`SELECT id, name, URL, content FROM extranotes;`) var extraNotes []ExtraNote for rows.Next() { var extraNote ExtraNote rows.Scan(&extraNote.ID, &extraNote.Name, &extraNote.URL, &extraNote.Content) extraNotes = append(extraNotes, extraNote) } templates.Render(w, "adminindex.html", map[string]interface{}{ "Common": readCommonData(r, sess), "Config": models.ConfigAllVals(), "ExtraNotes": extraNotes, "NumUsers": models.NumUsers(), "NumGroups": models.NumGroups(), "NumTopics": models.NumTopics(), "NumComments": models.NumComments(), }) })
View Source
var ChangePasswdHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { userName := r.FormValue("u") commonData := readCommonData(r, sess) if !sess.IsUserValid() { ErrForbiddenHandler(w, r) return } if userName != commonData.UserName && !commonData.IsSuperAdmin { ErrForbiddenHandler(w, r) return } if r.Method == "POST" { if !commonData.IsSuperAdmin { passwd := r.PostFormValue("passwd") if sess.Authenticate(userName, passwd) != nil { sess.SetFlashMsg("Current password incorrect.") http.Redirect(w, r, "/changepass?u="+userName, http.StatusSeeOther) return } } newPasswd := r.PostFormValue("newpass") newPasswdConfirm := r.PostFormValue("confirm") if err := validatePasswd(newPasswd, newPasswdConfirm); err != nil { sess.SetFlashMsg(err.Error()) http.Redirect(w, r, "/changepass?u="+userName, http.StatusSeeOther) return } if err := models.UpdateUserPasswd(userName, newPasswd); err != nil { log.Panicf("[ERROR] Error changing password: %s\n", err) } if commonData.IsSuperAdmin { var userID string db.QueryRow(`SELECT id FROM users WHERE username=?;`, userName).Scan(&userID) db.Exec(`DELETE FROM sessions WHERE userid=?;`, userID) } sess.SetFlashMsg("Password change successful.") http.Redirect(w, r, "/changepass?u="+userName, http.StatusSeeOther) return } templates.Render(w, "changepass.html", map[string]interface{}{ "Common": commonData, "UserName": userName, }) })
View Source
var CommentCreateHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { topicID := r.FormValue("tid") quoteID := r.FormValue("quote") content := strings.TrimSpace(r.PostFormValue("content")) isSticky := r.PostFormValue("is_sticky") != "" isImageUploadEnabled := models.Config(models.ImageUploadEnabled) != "0" var groupID, groupName, topicName, parentComment, topicOwnerID, topicOwnerName string var topicCreatedDate int64 if db.QueryRow(`SELECT userid, groupid, title, content, created_date FROM topics WHERE id=?;`, topicID).Scan( &topicOwnerID, &groupID, &topicName, &parentComment, &topicCreatedDate) != nil { ErrNotFoundHandler(w, r) return } isClosed := true db.QueryRow(`SELECT is_closed FROM groups WHERE id=?;`, groupID).Scan(&isClosed) if isClosed { ErrForbiddenHandler(w, r) return } db.QueryRow(`SELECT username FROM users WHERE id=?;`, topicOwnerID).Scan(&topicOwnerName) var tmp string db.QueryRow(`SELECT name FROM groups WHERE id=?;`, groupID).Scan(&groupName) isMod := db.QueryRow(`SELECT id FROM mods WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isAdmin := db.QueryRow(`SELECT id FROM admins WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isSuperAdmin := false db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?;`, sess.UserID).Scan(&isSuperAdmin) quoteContent := "" if quoteID != "" { var quotedUser string var isDeleted bool db.QueryRow(`SELECT comments.content, comments.is_deleted, users.username FROM comments INNER JOIN users ON comments.userid=users.id WHERE comments.id=?;`, quoteID).Scan("eContent, &isDeleted, "edUser) if !isDeleted { quoteContent = formatReply(quotedUser, quoteContent) } else { quoteContent = "" } } if r.Method == "POST" { if !isMod && !isAdmin && !isSuperAdmin { isSticky = false } imageName := "" if isImageUploadEnabled { imageName = saveImage(r) } if (len(content) < 2 && imageName == "") || len(content) > 5000 { sess.SetFlashMsg("Comment should have 2-5000 characters.") http.Redirect(w, r, "/comments/new?tid="+topicID, http.StatusSeeOther) return } var lastPos int db.QueryRow(`SELECT pos FROM comments WHERE topicid=? ORDER BY pos DESC LIMIT 1;`, topicID).Scan(&lastPos) newPos := lastPos + 1 if isSticky { newPos = -newPos } db.Exec(`INSERT INTO comments(content, image, topicid, userid, parentid, pos, created_date, updated_date) VALUES(?, ?, ?, ?, ?, ?, ?, ?);`, content, imageName, topicID, sess.UserID, sql.NullInt64{Valid: false}, newPos, int64(time.Now().Unix()), int64(time.Now().Unix())) db.Exec(`UPDATE topics SET num_comments=num_comments+1, activity_date=? WHERE id=?;`, int(time.Now().Unix()), topicID) if models.Config(models.AllowTopicSubscription) != "0" { var userName string db.QueryRow(`SELECT username FROM users WHERE id=?;`, sess.UserID).Scan(&userName) topicURL := "http://" + r.Host + "/topics?id=" + topicID rows := db.Query(`SELECT users.email, topicsubscriptions.token FROM users INNER JOIN topicsubscriptions ON users.id=topicsubscriptions.userid AND topicsubscriptions.topicid=?;`, topicID) for rows.Next() { var email, token string rows.Scan(&email, &token) if email != "" { unSubURL := "http://" + r.Host + "/topics/unsubscribe?token=" + token utils.SendMail(email, `New comment in "`+topicName+`"`, "A new comment has been posted by "+userName+" in \""+topicName+"\".\r\nSee the comment at "+topicURL+"\r\n\r\nIf you do not want these emails, unsubscribe by following this link: "+unSubURL) } } } page := newPos / numCommentsPerPage if page < 0 { page = 0 } http.Redirect(w, r, "/topics?id="+topicID+"&p="+strconv.Itoa(page)+"#comment-last", http.StatusSeeOther) return } templates.Render(w, "commentedit.html", map[string]interface{}{ "Common": readCommonData(r, sess), "TopicID": topicID, "TopicOwnerName": topicOwnerName, "TopicCreatedDate": timeAgoFromNow(time.Unix(topicCreatedDate, 0)), "CommentID": "", "TopicName": topicName, "GroupName": groupName, "ParentComment": parentComment, "Content": quoteContent, "IsSticky": false, "IsMod": isMod, "IsAdmin": isAdmin, "IsSuperAdmin": isSuperAdmin, "IsImageUploadEnabled": isImageUploadEnabled, }) })
View Source
var CommentIndexHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { commentID := r.FormValue("id") var groupID, topicID, topicName, groupName, ownerID, ownerName, content, imgSrc string var cDate int64 var isDeleted bool if db.QueryRow(`SELECT userid, topicid, content, image, is_deleted, created_date FROM comments WHERE id=?;`, commentID).Scan( &ownerID, &topicID, &content, &imgSrc, &isDeleted, &cDate) != nil { ErrNotFoundHandler(w, r) return } db.QueryRow(`SELECT groupid, title FROM topics WHERE id=?;`, topicID).Scan(&groupID, &topicName) db.QueryRow(`SELECT username FROM users WHERE id=?;`, ownerID).Scan(&ownerName) db.QueryRow(`SELECT name FROM groups WHERE id=?;`, groupID).Scan(&groupName) var tmp string db.QueryRow(`SELECT name FROM groups WHERE id=?;`, groupID).Scan(&groupName) isMod := sess.UserID.Valid && db.QueryRow(`SELECT id FROM mods WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isAdmin := sess.UserID.Valid && db.QueryRow(`SELECT id FROM admins WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isSuperAdmin := false if sess.UserID.Valid { db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?`, sess.UserID).Scan(&isSuperAdmin) } isOwner := sess.UserID.Valid && db.QueryRow(`SELECT userid FROM comments WHERE id=?;`, commentID).Scan(&tmp) == nil templates.Render(w, "commentindex.html", map[string]interface{}{ "Common": readCommonData(r, sess), "ID": commentID, "TopicID": topicID, "TopicName": topicName, "GroupName": groupName, "OwnerName": ownerName, "Content": formatComment(content), "ImgSrc": imgSrc, "IsMod": isMod, "IsAdmin": isAdmin, "IsSuperAdmin": isSuperAdmin, "IsOwner": isOwner, "IsDeleted": isDeleted, "CreatedDate": timeAgoFromNow(time.Unix(cDate, 0)), }) })
View Source
var CommentUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { commentID := r.FormValue("id") content := strings.TrimSpace(r.PostFormValue("content")) isSticky := r.PostFormValue("is_sticky") != "" var groupID, topicID, groupName, topicName, parentComment, topicOwnerName, topicOwnerID string var topicCreatedDate int64 var pos int if db.QueryRow(`SELECT topicid, pos FROM comments WHERE id=?;`, commentID).Scan(&topicID, &pos) != nil { ErrNotFoundHandler(w, r) return } if db.QueryRow(`SELECT userid, groupid, title, content, created_date FROM topics WHERE id=?;`, topicID).Scan( &topicOwnerID, &groupID, &topicName, &parentComment, &topicCreatedDate) != nil { ErrNotFoundHandler(w, r) return } isClosed := true db.QueryRow(`SELECT is_closed FROM groups WHERE id=?;`, groupID).Scan(&isClosed) if !isClosed { db.QueryRow(`SELECT is_closed FROM topics WHERE id=?;`, topicID).Scan(&isClosed) } if isClosed { ErrForbiddenHandler(w, r) return } db.QueryRow(`SELECT username FROM users WHERE id=?;`, topicOwnerID).Scan(&topicOwnerName) var tmp string db.QueryRow(`SELECT name FROM groups WHERE id=?;`, groupID).Scan(&groupName) isMod := db.QueryRow(`SELECT id FROM mods WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isAdmin := db.QueryRow(`SELECT id FROM admins WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isSuperAdmin := false db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?`, sess.UserID).Scan(&isSuperAdmin) isOwner := db.QueryRow(`SELECT userid FROM comments WHERE id=?;`, commentID).Scan(&tmp) == nil if !isOwner && !isMod && !isAdmin && !isSuperAdmin { ErrForbiddenHandler(w, r) return } if r.Method == "POST" { action := r.PostFormValue("action") if action == "Update" { if len(content) < 2 || len(content) > 5000 { sess.SetFlashMsg("Comment should have 2-5000 characters.") http.Redirect(w, r, "/comments/edit?id="+commentID, http.StatusSeeOther) return } if content == "" { http.Redirect(w, r, "/comments/edit?id="+commentID, http.StatusSeeOther) return } if !isMod && !isAdmin && !isSuperAdmin { isSticky = (pos < 0) } if isSticky { if pos > 0 { pos = -pos } } else { if pos < 0 { pos = -pos } } db.Exec(`UPDATE comments SET content=?, pos=?, updated_date=? WHERE id=?;`, content, pos, int64(time.Now().Unix()), commentID) page := pos / numCommentsPerPage if page < 0 { page = 0 } http.Redirect(w, r, "/topics?id="+topicID+"&p="+strconv.Itoa(page)+"#comment-"+commentID, http.StatusSeeOther) } if action == "Delete" { db.Exec(`UPDATE comments SET is_deleted=1 WHERE id=?;`, commentID) http.Redirect(w, r, "/comments/edit?id="+commentID, http.StatusSeeOther) } if action == "Undelete" { db.Exec(`UPDATE comments SET is_deleted=0 WHERE id=?;`, commentID) http.Redirect(w, r, "/comments/edit?id="+commentID, http.StatusSeeOther) } return } isDeleted := false db.QueryRow(`SELECT content, is_deleted FROM comments WHERE id=?;`, commentID).Scan(&content, &isDeleted) isSticky = (pos < 0) templates.Render(w, "commentedit.html", map[string]interface{}{ "Common": readCommonData(r, sess), "TopicID": topicID, "TopicOwnerName": topicOwnerName, "TopicCreatedDate": timeAgoFromNow(time.Unix(topicCreatedDate, 0)), "CommentID": commentID, "TopicName": topicName, "GroupName": groupName, "ParentComment": parentComment, "Content": content, "IsSticky": isSticky, "IsMod": isMod, "IsAdmin": isAdmin, "IsSuperAdmin": isSuperAdmin, "IsDeleted": isDeleted, "IsImageUploadEnabled": false, }) })
View Source
var ErrAuthFail = errors.New("username / password incorrect")
View Source
var ErrNoFlashMsg = errors.New("No flash message")
View Source
var ForgotPasswdHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { if r.Method == "POST" { userName := r.PostFormValue("username") if userName == "" || len(userName) > 200 || !models.ProbeUser(userName) { sess.SetFlashMsg("Username doesn't exist.") http.Redirect(w, r, "/forgotpass", http.StatusSeeOther) return } email := models.ReadUserEmail(userName) if !strings.ContainsRune(email, '@') { sess.SetFlashMsg("E-mail address not set. Contact site admin to reset the password.") http.Redirect(w, r, "/forgotpass", http.StatusSeeOther) return } forumName := models.Config(models.ForumName) resetToken := randSeq(40) db.Exec(`UPDATE users SET reset_token=?, reset_token_date=? WHERE username=?;`, resetToken, int64(time.Now().Unix()), userName) resetLink := "https://" + r.Host + "/resetpass?r=" + resetToken sub := forumName + " Password Recovery" msg := "Someone (hopefully you) requested we reset your password at " + forumName + ".\r\n" + "If you want to change it, visit " + resetLink + "\r\n\r\nIf not, just ignore this message." utils.SendMail(email, sub, msg) sess.SetFlashMsg("Password reset link sent to your e-mail.") http.Redirect(w, r, "/login", http.StatusSeeOther) return } templates.Render(w, "forgotpass.html", map[string]interface{}{ "Common": readCommonData(r, sess), }) })
View Source
var GroupEditHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { if models.Config(models.GroupCreationDisabled) == "1" { ErrForbiddenHandler(w, r) return } commonData := readCommonData(r, sess) userName := commonData.UserName groupID := r.FormValue("id") name := strings.TrimSpace(r.FormValue("name")) desc := strings.TrimSpace(r.FormValue("desc")) headerMsg := strings.TrimSpace(r.FormValue("header_msg")) isSticky := r.FormValue("is_sticky") != "" isPrivate := r.FormValue("is_private") != "" isDeleted := false mods := strings.Split(r.FormValue("mods"), ",") for i, mod := range mods { mods[i] = strings.TrimSpace(mod) } admins := strings.Split(r.FormValue("admins"), ",") for i, admin := range admins { admins[i] = strings.TrimSpace(admin) } if len(admins) == 1 && admins[0] == "" { admins[0] = userName } action := r.FormValue("action") if groupID != "" { if !models.IsUserGroupAdmin(strconv.Itoa(int(sess.UserID.Int64)), groupID) && !commonData.IsSuperAdmin { ErrForbiddenHandler(w, r) return } } if r.Method == "POST" { if action == "Create" { if len(name) < 3 || len(name) > 40 { sess.SetFlashMsg("Group name should have 3-40 characters.") http.Redirect(w, r, "/groups/edit", http.StatusSeeOther) return } if censored := censor(name); censored != name { sess.SetFlashMsg("Fix group name: " + censored) http.Redirect(w, r, "/groups/edit", http.StatusSeeOther) return } if len(desc) > 160 { sess.SetFlashMsg("Group description should have less than 160 characters.") http.Redirect(w, r, "/groups/edit", http.StatusSeeOther) return } if len(headerMsg) > 160 { sess.SetFlashMsg("Announcement should have less than 160 characters.") http.Redirect(w, r, "/groups/edit", http.StatusSeeOther) return } if err := validateName(name); err != nil { sess.SetFlashMsg(err.Error()) http.Redirect(w, r, "/groups/edit", http.StatusSeeOther) return } if len(admins) > 32 || len(mods) > 32 { sess.SetFlashMsg("Number of admins/mods should no more than 32.") http.Redirect(w, r, "/groups/edit", http.StatusSeeOther) return } db.Exec(`INSERT INTO groups(name, description, header_msg, is_sticky, is_private, created_date, updated_date) VALUES(?, ?, ?, ?, ?, ?, ?);`, name, desc, headerMsg, isSticky, isPrivate, time.Now().Unix(), time.Now().Unix()) groupID := models.ReadGroupIDByName(name) for _, mod := range mods { if mod != "" { models.CreateGroupMod(mod, groupID) } } for _, admin := range admins { if admin != "" { models.CreateGroupAdmin(admin, groupID) } } http.Redirect(w, r, "/groups?name="+name, http.StatusSeeOther) } else if action == "Update" { if len(name) < 3 || len(name) > 40 { sess.SetFlashMsg("Group name should have 3-40 characters.") http.Redirect(w, r, "/groups/edit?id="+groupID, http.StatusSeeOther) return } if censored := censor(name); censored != name { sess.SetFlashMsg("Fix group name: " + censored) http.Redirect(w, r, "/groups/edit?id="+groupID, http.StatusSeeOther) return } if len(desc) > 160 { sess.SetFlashMsg("Group description should have less than 160 characters.") http.Redirect(w, r, "/groups/edit?id="+groupID, http.StatusSeeOther) return } if len(headerMsg) > 160 { sess.SetFlashMsg("Announcement should have less than 160 characters.") http.Redirect(w, r, "/groups/edit?id="+groupID, http.StatusSeeOther) return } if err := validateName(name); err != nil { sess.SetFlashMsg(err.Error()) http.Redirect(w, r, "/groups/edit?id="+groupID, http.StatusSeeOther) return } if len(admins) > 32 || len(mods) > 32 { sess.SetFlashMsg("Number of admins/mods should no more than 32.") http.Redirect(w, r, "/groups/edit?id="+groupID, http.StatusSeeOther) return } isUserSuperAdmin := false db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?;`, sess.UserID).Scan(&isUserSuperAdmin) if !isUserSuperAdmin { db.QueryRow(`SELECT is_sticky FROM groups WHERE id=?;`, groupID).Scan(&isSticky) } db.Exec(`UPDATE groups SET name=?, description=?, header_msg=?, is_sticky=?, is_private=?, updated_date=? WHERE id=?;`, name, desc, headerMsg, isSticky, isPrivate, time.Now().Unix(), groupID) db.Exec(`DELETE FROM mods WHERE groupid=?;`, groupID) db.Exec(`DELETE FROM admins WHERE groupid=?;`, groupID) for _, mod := range mods { if mod != "" { models.CreateGroupMod(mod, groupID) } } for _, admin := range admins { if admin != "" { models.CreateGroupAdmin(admin, groupID) } } http.Redirect(w, r, "/groups?name="+name, http.StatusSeeOther) } else if action == "Delete" { db.Exec(`UPDATE groups SET is_closed=1 WHERE id=?;`, groupID) http.Redirect(w, r, "/groups/edit?id="+groupID, http.StatusSeeOther) } else if action == "Undelete" { db.Exec(`UPDATE groups SET is_closed=0 WHERE id=?;`, groupID) http.Redirect(w, r, "/groups/edit?id="+groupID, http.StatusSeeOther) } return } if groupID != "" { db.QueryRow(`SELECT name, description, header_msg, is_sticky, is_private, is_closed FROM groups WHERE id=?;`, groupID).Scan( &name, &desc, &headerMsg, &isSticky, &isPrivate, &isDeleted, ) mods = models.ReadMods(groupID) admins = models.ReadAdmins(groupID) } templates.Render(w, "groupedit.html", map[string]interface{}{ "Common": readCommonData(r, sess), "ID": groupID, "GroupName": name, "Desc": desc, "HeaderMsg": headerMsg, "IsSticky": isSticky, "IsPrivate": isPrivate, "IsDeleted": isDeleted, "Mods": strings.Join(mods, ", "), "Admins": strings.Join(admins, ", "), }) })
View Source
var GroupIndexHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { name := r.FormValue("name") var groupID, groupDesc, headerMsg string if db.QueryRow(`SELECT id, description, header_msg FROM groups WHERE name=?;`, name).Scan(&groupID, &groupDesc, &headerMsg) != nil { ErrNotFoundHandler(w, r) return } subToken := "" if sess.UserID.Valid { db.QueryRow(`SELECT token FROM groupsubscriptions WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&subToken) } numTopicsPerPage := 30 lastTopicDate, err := strconv.ParseInt(r.FormValue("ltd"), 10, 64) if err != nil { lastTopicDate = 0 } type Topic struct { ID int Title string IsDeleted bool IsClosed bool Owner string NumComments int CreatedDate string cDateUnix int64 } var topics []Topic var rows *db.Rows if lastTopicDate == 0 { rows = db.Query(`SELECT topics.id, topics.title, topics.is_deleted, topics.is_closed, topics.num_comments, topics.created_date, users.username FROM topics INNER JOIN users ON topics.userid = users.id AND topics.groupid=? ORDER BY topics.is_sticky DESC, topics.activity_date DESC LIMIT ?;`, groupID, numTopicsPerPage) } else { rows = db.Query(`SELECT topics.id, topics.title, topics.is_deleted, topics.is_closed, topics.num_comments, topics.created_date, users.username FROM topics INNER JOIN users ON topics.userid = users.id AND topics.groupid=? AND topics.is_sticky=0 AND topics.created_date < ? ORDER BY topics.activity_date DESC LIMIT ?;`, groupID, lastTopicDate, numTopicsPerPage) } for rows.Next() { t := Topic{} rows.Scan(&t.ID, &t.Title, &t.IsDeleted, &t.IsClosed, &t.NumComments, &t.cDateUnix, &t.Owner) t.CreatedDate = timeAgoFromNow(time.Unix(t.cDateUnix, 0)) t.Title = censor(t.Title) topics = append(topics, t) } isSuperAdmin := false isAdmin := false isMod := false if sess.IsUserValid() { db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?;`, sess.UserID).Scan(&isSuperAdmin) var tmp string isAdmin = db.QueryRow(`SELECT id FROM admins WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isMod = db.QueryRow(`SELECT id FROM mods WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil } if len(topics) >= numTopicsPerPage { lastTopicDate = topics[len(topics)-1].cDateUnix } else { lastTopicDate = 0 } commonData := readCommonData(r, sess) commonData.PageTitle = name templates.Render(w, "groupindex.html", map[string]interface{}{ "Common": commonData, "GroupName": name, "GroupDesc": censor(groupDesc), "GroupID": groupID, "HeaderMsg": censor(headerMsg), "SubToken": subToken, "Topics": topics, "IsMod": isMod, "IsAdmin": isAdmin, "IsSuperAdmin": isSuperAdmin, "LastTopicDate": lastTopicDate, }) })
View Source
var GroupSubscribeHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { groupID := r.FormValue("id") if models.Config(models.AllowGroupSubscription) == "0" { ErrForbiddenHandler(w, r) return } var groupName string if db.QueryRow(`SELECT name FROM groups WHERE id=?;`, groupID).Scan(&groupName) != nil { ErrNotFoundHandler(w, r) return } if r.Method == "POST" { var tmp string if db.QueryRow(`SELECT id FROM groupsubscriptions WHERE userid=? AND groupid=?;`, sess.UserID, groupID).Scan(&tmp) != nil { db.Exec(`INSERT INTO groupsubscriptions(userid, groupid, token, created_date) VALUES(?, ?, ?, ?);`, sess.UserID, groupID, randSeq(64), time.Now().Unix()) } } http.Redirect(w, r, "/groups?name="+groupName, http.StatusSeeOther) })
View Source
var GroupUnsubscribeHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { token := r.FormValue("token") var groupID, groupName string if db.QueryRow(`SELECT groupid FROM groupsubscriptions WHERE token=?;`, token).Scan(&groupID) != nil { ErrNotFoundHandler(w, r) return } db.QueryRow(`SELECT name FROM groups WHERE id=?;`, groupID).Scan(&groupName) if r.Method == "POST" { db.Exec(`DELETE FROM groupsubscriptions WHERE token=?;`, token) if r.PostFormValue("noredirect") != "" { w.Write([]byte("Unsubscribed.")) } else { http.Redirect(w, r, "/groups?name="+groupName, http.StatusSeeOther) } return } w.Write([]byte(`<!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"></head> <body><form action="/groups/unsubscribe" method="POST"> Unsubscribe from ` + groupName + `? <input type="hidden" name="token" value=` + token + `> <input type="hidden" name="csrf" value="` + sess.CSRFToken + `"> <input type="hidden" name="noredirect" value="1"> <input type="submit" value="Unsubscribe"> </form></body></html>`)) })
View Source
var IndexHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { if r.URL.Path != "/" { ErrNotFoundHandler(w, r) return } type Group struct { Name string Desc string IsSticky int } groups := []Group{} rows := db.Query(`SELECT name, description, is_sticky FROM groups WHERE is_closed=0 ORDER BY is_sticky DESC, RANDOM() LIMIT 25;`) for rows.Next() { groups = append(groups, Group{}) g := &groups[len(groups)-1] rows.Scan(&g.Name, &g.Desc, &g.IsSticky) g.Desc = censor(g.Desc) } sort.Slice(groups, func(i, j int) bool { return groups[i].Name < groups[j].Name }) sort.Slice(groups, func(i, j int) bool { return groups[i].IsSticky > groups[j].IsSticky }) type Topic struct { ID string Title string GroupName string OwnerName string CreatedDate string NumComments int } topics := []Topic{} trows := db.Query(`SELECT topics.id, topics.title, topics.num_comments, topics.created_date, topics.is_deleted, topics.is_closed, groups.name, groups.is_closed, users.username FROM topics INNER JOIN groups ON topics.groupid=groups.id INNER JOIN users ON topics.userid=users.id ORDER BY topics.created_date DESC LIMIT 20;`) for trows.Next() { t := Topic{} var cDate int64 var isTopicDeleted, isTopicClosed, isGroupClosed bool trows.Scan(&t.ID, &t.Title, &t.NumComments, &cDate, &isTopicDeleted, &isTopicClosed, &t.GroupName, &isGroupClosed, &t.OwnerName) t.CreatedDate = timeAgoFromNow(time.Unix(cDate, 0)) t.Title = censor(t.Title) if !isTopicDeleted && !isTopicClosed && !isGroupClosed { topics = append(topics, t) } } templates.Render(w, "index.html", map[string]interface{}{ "Common": readCommonData(r, sess), "GroupCreationDisabled": models.Config(models.GroupCreationDisabled) == "1", "HeaderMsg": models.Config(models.HeaderMsg), "Groups": groups, "Topics": topics, }) })
View Source
var LoginHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { redirectURL, err := url.QueryUnescape(r.FormValue("next")) if redirectURL == "" || err != nil { redirectURL = "/" } if sess.IsUserValid() { http.Redirect(w, r, redirectURL, http.StatusSeeOther) return } if r.Method == "POST" { userName := r.PostFormValue("username") passwd := r.PostFormValue("passwd") if len(userName) > 200 || len(passwd) > 200 { fmt.Fprint(w, "username / password too long.") return } if err = sess.Authenticate(userName, passwd); err == nil { http.Redirect(w, r, redirectURL, http.StatusSeeOther) return } else { sess.SetFlashMsg(err.Error()) http.Redirect(w, r, "/login?next="+redirectURL, http.StatusSeeOther) return } } templates.Render(w, "login.html", map[string]interface{}{ "Common": readCommonData(r, sess), "next": template.URL(url.QueryEscape(redirectURL)), "LoginMsg": models.Config(models.LoginMsg), }) })
View Source
var NoteHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { id := r.FormValue("id") row := db.QueryRow(`SELECT name, URL, content, created_date, updated_date FROM extranotes WHERE id=?;`, id) var e ExtraNote var cDate int64 var uDate int64 if err := row.Scan(&e.Name, &e.URL, &e.Content, &cDate, &uDate); err == nil { e.CreatedDate = time.Unix(cDate, 0) e.UpdatedDate = time.Unix(uDate, 0) if e.URL == "" { templates.Render(w, "extranote.html", map[string]interface{}{ "Common": readCommonData(r, sess), "Name": e.Name, "UpdatedDate": e.UpdatedDate, "Content": template.HTML(e.Content), }) return } else { http.Redirect(w, r, e.URL, http.StatusSeeOther) return } } ErrNotFoundHandler(w, r) })
View Source
var PrivateMessageCreateHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { if r.Method == "POST" { tousers := strings.TrimSpace(r.PostFormValue("to")) content := strings.TrimSpace(r.PostFormValue("content")) if tousers == "" { sess.SetFlashMsg("No users to send the message to.") http.Redirect(w, r, "/pm", http.StatusSeeOther) return } if content == "" { sess.SetFlashMsg("Content is empty.") http.Redirect(w, r, "/pm", http.StatusSeeOther) return } tousernames := strings.Split(tousers, ",") touserids := []string{} for _, tousername := range tousernames { username := strings.TrimSpace(tousername) var userid string if err := db.QueryRow(`SELECT id FROM users WHERE username=?;`, username).Scan(&userid); err == nil { touserids = append(touserids, userid) } else { sess.SetFlashMsg("Username not found: " + username) http.Redirect(w, r, "/pm#end", http.StatusSeeOther) return } } for _, userid := range touserids { db.Exec(`INSERT INTO messages(fromid, toid, content, created_date) VALUES(?, ?, ?, ?);`, sess.UserID, userid, content, int(time.Now().Unix())) } sess.SetFlashMsg("Message sent.") http.Redirect(w, r, "/pm#end", http.StatusSeeOther) return } })
View Source
var PrivateMessageDeleteHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { if r.Method == "POST" { id := r.PostFormValue("id") db.Exec(`DELETE FROM messages WHERE id=? AND toid=?;`, id, sess.UserID) http.Redirect(w, r, "/pm?lmd="+r.PostFormValue("lmd"), http.StatusSeeOther) return } })
View Source
var PrivateMessageHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { startDate := time.Now().Unix() lmd := r.FormValue("lmd") if lmd != "" { if d, err := strconv.Atoi(lmd); err == nil { startDate = int64(d) } } type Message struct { ID string From string To string IsRead bool CreatedDate string Content template.HTML } var lastMessageDate int64 var msgs []Message var cDate int64 var content string var rows *db.Rows rows = db.Query(`SELECT messages.id, fromusers.username, tousers.username, messages.content, messages.is_read, messages.created_date FROM messages INNER JOIN users fromusers ON fromusers.id=messages.fromid INNER JOIN users tousers ON tousers.id=messages.toid WHERE messages.toid=? AND messages.created_date <= ? ORDER BY messages.created_date DESC LIMIT ?;`, sess.UserID, startDate, messagesPerPage+1) for rows.Next() { msg := Message{} rows.Scan(&msg.ID, &msg.From, &msg.To, &content, &msg.IsRead, &cDate) msg.CreatedDate = timeAgoFromNow(time.Unix(cDate, 0)) msg.Content = formatComment(content) if len(msgs) < messagesPerPage { msgs = append(msgs, msg) } else { lastMessageDate = cDate } } to, cont := "", "" if pmid := r.FormValue("quote"); pmid != "" { db.QueryRow(`SELECT users.username, messages.content FROM messages INNER JOIN users ON messages.fromid=users.id WHERE messages.id=?;`, pmid).Scan(&to, &cont) cont = formatReply(to, cont) } if flag := r.FormValue("flag"); flag != "" { rows := db.Query(`SELECT users.username FROM mods INNER JOIN users ON users.id=mods.userid INNER JOIN topics ON topics.groupid=mods.groupid INNER JOIN comments ON comments.topicid=topics.id WHERE comments.id=?;`, flag) for rows.Next() { var mod string rows.Scan(&mod) if to != "" { to = to + ", " } to = to + mod } cont = "Flagging " + "http://" + r.Host + "/comments?id=" + flag } if lmd != "" && len(msgs) == 0 { http.Redirect(w, r, "/pm", http.StatusSeeOther) return } db.Exec(`UPDATE messages SET is_read=? WHERE toid=?;`, true, sess.UserID) templates.Render(w, "pm.html", map[string]interface{}{ "Common": readCommonData(r, sess), "Messages": msgs, "LastMessageDate": lastMessageDate, "FirstMessageDate": startDate, "To": to, "Content": cont, }) })
View Source
var ResetPasswdHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { resetToken := r.FormValue("r") userName, err := models.ReadUserNameByToken(resetToken) if err != nil { ErrForbiddenHandler(w, r) return } if r.Method == "POST" { passwd := r.PostFormValue("passwd") passwdConfirm := r.PostFormValue("confirm") if err := validatePasswd(passwd, passwdConfirm); err != nil { sess.SetFlashMsg(err.Error()) http.Redirect(w, r, "/resetpass?r="+resetToken, http.StatusSeeOther) return } models.UpdateUserPasswd(userName, passwd) sess.SetFlashMsg("Password change successful.") http.Redirect(w, r, "/login", http.StatusSeeOther) return } templates.Render(w, "resetpass.html", map[string]interface{}{ "ResetToken": resetToken, "Common": readCommonData(r, sess), }) })
View Source
var SignupHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { redirectURL, err := url.QueryUnescape(r.FormValue("next")) if redirectURL == "" || err != nil { redirectURL = "/" } if sess.IsUserValid() && !sess.IsUserSuperAdmin() { http.Redirect(w, r, redirectURL, http.StatusSeeOther) return } isSignupDisabled := models.Config(models.SignupDisabled) != "0" if r.Method == "POST" { userName := strings.TrimSpace(r.PostFormValue("username")) passwd := r.PostFormValue("passwd") passwdConfirm := r.PostFormValue("confirm") email := strings.TrimSpace(r.PostFormValue("email")) if len(userName) < 2 || len(userName) > 32 { sess.SetFlashMsg("Username should have 2-32 characters.") http.Redirect(w, r, "/signup", http.StatusSeeOther) return } if censored := censor(userName); censored != userName { sess.SetFlashMsg("Fix username: " + censored) http.Redirect(w, r, "/signup", http.StatusSeeOther) return } hasSpecial := false for _, ch := range userName { if (ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && ch != '_' && (ch < '0' || ch > '9') { hasSpecial = true } } if hasSpecial { sess.SetFlashMsg("Username can contain only alphabets, numbers, and underscore.") http.Redirect(w, r, "/signup", http.StatusSeeOther) return } if models.ProbeUser(userName) { sess.SetFlashMsg("Username already registered.") http.Redirect(w, r, "/signup", http.StatusSeeOther) return } if err := validatePasswd(passwd, passwdConfirm); err != nil { sess.SetFlashMsg(err.Error()) http.Redirect(w, r, "/signup", http.StatusSeeOther) return } if len(email) > 64 { sess.SetFlashMsg("Email should have fewer than 64 characters.") http.Redirect(w, r, "/signup", http.StatusSeeOther) return } if isSignupDisabled && !sess.IsUserSuperAdmin() { ErrForbiddenHandler(w, r) return } models.CreateUser(userName, passwd, email) if sess.IsUserSuperAdmin() { sess.SetFlashMsg("User " + userName + " created") http.Redirect(w, r, "/signup", http.StatusSeeOther) return } sess.Authenticate(userName, passwd) http.Redirect(w, r, redirectURL, http.StatusSeeOther) } templates.Render(w, "signup.html", map[string]interface{}{ "Common": readCommonData(r, sess), "next": template.URL(url.QueryEscape(redirectURL)), "IsDisabled": isSignupDisabled && !sess.IsUserSuperAdmin(), "SignupMsg": models.Config(models.SignupMsg), }) })
View Source
var TopicCreateHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { groupID := r.FormValue("gid") var groupName string isGroupClosed := 1 db.QueryRow(`SELECT name, is_closed FROM groups WHERE id=?;`, groupID).Scan(&groupName, &isGroupClosed) if isGroupClosed == 1 { ErrForbiddenHandler(w, r) return } var tmp int isMod := db.QueryRow(`SELECT id FROM mods WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isAdmin := db.QueryRow(`SELECT id FROM admins WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isSuperAdmin := false db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?`, sess.UserID).Scan(&isSuperAdmin) if r.Method == "POST" { title := strings.TrimSpace(r.PostFormValue("title")) content := strings.TrimSpace(r.PostFormValue("content")) isSticky := r.PostFormValue("is_sticky") != "" if len(title) < 8 || len(title) > 80 { sess.SetFlashMsg("Title should have 8-80 characters.") http.Redirect(w, r, "/topics/new?gid="+groupID, http.StatusSeeOther) return } if len(content) > 5000 { sess.SetFlashMsg("Content should have less than 5000 characters.") http.Redirect(w, r, "/topics/new?gid="+groupID, http.StatusSeeOther) return } db.Exec(`INSERT INTO topics(title, content, userid, groupid, is_sticky, created_date, updated_date, activity_date) VALUES(?, ?, ?, ?, ?, ?, ?, ?);`, title, content, sess.UserID, groupID, isSticky, int(time.Now().Unix()), int(time.Now().Unix()), int(time.Now().Unix())) if models.Config(models.AllowGroupSubscription) != "0" { groupURL := "http://" + r.Host + "/groups?name=" + groupName rows := db.Query(`SELECT users.email, groupsubscriptions.token FROM users INNER JOIN groupsubscriptions ON users.id=groupsubscriptions.userid AND groupsubscriptions.groupid=?;`, groupID) for rows.Next() { var email, token string rows.Scan(&email, &token) if email != "" { unSubURL := "http://" + r.Host + "/groups/unsubscribe?token=" + token utils.SendMail(email, `New topic in `+groupName, "A new topic titled \""+title+"\" has been posted to "+groupName+".\r\nSee topics posted to the group at "+groupURL+"\r\n\r\nIf you do not want these emails, unsubscribe by following this link: "+unSubURL) } } } http.Redirect(w, r, "/groups?name="+groupName, http.StatusSeeOther) return } templates.Render(w, "topicedit.html", map[string]interface{}{ "Common": readCommonData(r, sess), "GroupID": groupID, "GroupName": groupName, "TopicID": "", "Title": "", "Content": "", "IsSticky": false, "IsClosed": false, "IsDeleted": false, "IsMod": isMod, "IsAdmin": isAdmin, "IsSuperAdmin": isSuperAdmin, }) })
View Source
var TopicIndexHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { topicID := r.FormValue("id") page64, err := strconv.ParseInt(r.FormValue("p"), 10, 64) if err != nil { page64 = 0 } page := int(page64) if page < 0 { page = 0 } var title, content, groupID, groupName string var isDeleted, isClosed bool var ownerID, createdDate int64 if db.QueryRow(`SELECT title, content, userid, groupid, is_deleted, is_closed, created_date FROM topics WHERE id=?;`, topicID).Scan( &title, &content, &ownerID, &groupID, &isDeleted, &isClosed, &createdDate) != nil { ErrNotFoundHandler(w, r) return } if isDeleted { ErrNotFoundHandler(w, r) return } var ownerName string db.QueryRow(`SELECT username FROM users WHERE id=?;`, ownerID).Scan(&ownerName) subToken := "" if sess.UserID.Valid { db.QueryRow(`SELECT token FROM topicsubscriptions WHERE topicid=? AND userid=?;`, topicID, sess.UserID).Scan(&subToken) } var lastPos int db.QueryRow(`SELECT pos FROM comments WHERE topicid=? ORDER BY pos DESC LIMIT 1;`, topicID).Scan(&lastPos) isLastPage := (lastPos < (page+1)*numCommentsPerPage) numPages := 0 if lastPos > 0 { numPages = 1 + lastPos/numCommentsPerPage } type Comment struct { ID string Content template.HTML ImgSrc string CreatedDate string UserName string IsOwner bool IsDeleted bool } var comments []Comment var cDate int64 var rows *db.Rows if page == 0 { rows = db.Query(`SELECT users.id, users.username, comments.id, comments.content, comments.image, comments.is_deleted, comments.created_date FROM comments INNER JOIN users ON comments.userid=users.id AND comments.topicid=? AND comments.pos < ? ORDER BY comments.pos;`, topicID, numCommentsPerPage) } else { rows = db.Query(`SELECT users.id, users.username, comments.id, comments.content, comments.image, comments.is_deleted, comments.created_date FROM comments INNER JOIN users ON comments.userid=users.id AND comments.topicid=? AND comments.pos >= ? AND comments.pos < ? ORDER BY comments.pos;`, topicID, page*numCommentsPerPage, (page+1)*numCommentsPerPage) } for rows.Next() { comments = append(comments, Comment{}) c := &comments[len(comments)-1] var ownerID int64 var content string rows.Scan(&ownerID, &c.UserName, &c.ID, &content, &c.ImgSrc, &c.IsDeleted, &cDate) c.CreatedDate = timeAgoFromNow(time.Unix(cDate, 0)) c.IsOwner = sess.UserID.Valid && (ownerID == sess.UserID.Int64) c.Content = formatComment(content) } var tmp string db.QueryRow(`SELECT name FROM groups WHERE id=?;`, groupID).Scan(&groupName) isMod := db.QueryRow(`SELECT id FROM mods WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isAdmin := db.QueryRow(`SELECT id FROM admins WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isSuperAdmin := false db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?`, sess.UserID).Scan(&isSuperAdmin) isOwner := sess.UserID.Valid && ownerID == sess.UserID.Int64 commonData := readCommonData(r, sess) commonData.PageTitle = censor(title) templates.Render(w, "topicindex.html", map[string]interface{}{ "Common": commonData, "GroupID": groupID, "TopicID": topicID, "GroupName": groupName, "TopicName": censor(title), "OwnerName": ownerName, "CreatedDate": timeAgoFromNow(time.Unix(createdDate, 0)), "SubToken": subToken, "Title": title, "Content": formatComment(content), "IsClosed": isClosed, "IsOwner": isOwner, "IsMod": isMod, "IsAdmin": isAdmin, "IsSuperAdmin": isSuperAdmin, "IsImageUploadEnabled": models.Config(models.ImageUploadEnabled) != "0", "Comments": comments, "IsLastPage": isLastPage, "NextPage": page + 1, "CurrentPage": page, "Pages": make([]int, numPages), "NumPages": numPages, }) })
View Source
var TopicSubscribeHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { topicID := r.FormValue("id") if models.Config(models.AllowTopicSubscription) == "0" { ErrForbiddenHandler(w, r) return } var tmp string if db.QueryRow(`SELECT id FROM topics WHERE id=?;`, topicID).Scan(&tmp) != nil { ErrNotFoundHandler(w, r) return } if r.Method == "POST" { var tmp string if db.QueryRow(`SELECT id FROM topicsubscriptions WHERE userid=? AND topicid=?;`, sess.UserID, topicID).Scan(&tmp) != nil { db.Exec(`INSERT INTO topicsubscriptions(userid, topicid, token, created_date) VALUES(?, ?, ?, ?);`, sess.UserID, topicID, randSeq(64), time.Now().Unix()) } } http.Redirect(w, r, "/topics?id="+topicID, http.StatusSeeOther) })
View Source
var TopicUnsubscribeHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { token := r.FormValue("token") var topicID, topicName string if db.QueryRow(`SELECT topicid FROM topicsubscriptions WHERE token=?;`, token).Scan(&topicID) != nil { ErrNotFoundHandler(w, r) return } db.QueryRow(`SELECT title FROM topics WHERE id=?;`, topicID).Scan(&topicName) if r.Method == "POST" { db.Exec(`DELETE FROM topicsubscriptions WHERE token=?;`, token) if r.PostFormValue("noredirect") != "" { w.Write([]byte("Unsubscribed.")) } else { http.Redirect(w, r, "/topics?id="+topicID, http.StatusSeeOther) } return } w.Write([]byte(`<!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"></head> <body><form action="/topics/unsubscribe" method="POST"> Unsubscribe from ` + topicName + `? <input type="hidden" name="token" value="` + token + `"> <input type="hidden" name="csrf" value="` + sess.CSRFToken + `"> <input type="hidden" name="noredirect" value="1"> <input type="submit" value="Unsubscribe"> </form></body></html>`)) })
View Source
var TopicUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { topicID := r.FormValue("id") groupID := "" title := strings.TrimSpace(r.PostFormValue("title")) content := strings.TrimSpace(r.PostFormValue("content")) action := r.PostFormValue("action") isSticky := r.PostFormValue("is_sticky") != "" isClosed := true isDeleted := true if db.QueryRow(`SELECT groupid FROM topics WHERE id=?;`, topicID).Scan(&groupID) != nil { ErrNotFoundHandler(w, r) return } isGroupClosed := 1 var groupName string db.QueryRow(`SELECT name, is_closed FROM groups WHERE id=?;`, groupID).Scan(&groupName, &isGroupClosed) if isGroupClosed == 1 { ErrForbiddenHandler(w, r) return } var tmp int var uID int64 db.QueryRow(`SELECT userid FROM topics WHERE id=?;`, topicID).Scan(&uID) isOwner := (uID == sess.UserID.Int64) isMod := db.QueryRow(`SELECT id FROM mods WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isAdmin := db.QueryRow(`SELECT id FROM admins WHERE groupid=? AND userid=?;`, groupID, sess.UserID).Scan(&tmp) == nil isSuperAdmin := false db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?`, sess.UserID).Scan(&isSuperAdmin) if !isMod && !isAdmin && !isSuperAdmin { db.QueryRow(`SELECT is_sticky FROM topics WHERE id=?;`, topicID).Scan(&isSticky) if !isOwner { ErrForbiddenHandler(w, r) return } } if r.Method == "POST" { if len(title) < 8 || len(title) > 80 { sess.SetFlashMsg("Title should have 8-80 characters.") http.Redirect(w, r, "/topics/edit?id="+topicID, http.StatusSeeOther) return } if len(content) > 5000 { sess.SetFlashMsg("Content should have less than 5000 characters.") http.Redirect(w, r, "/topics/edit?id="+topicID, http.StatusSeeOther) return } if action == "Update" { db.Exec(`UPDATE topics SET title=?, content=?, is_sticky=?, updated_date=? WHERE id=?;`, title, content, isSticky, int(time.Now().Unix()), topicID) } else if action == "Close" && (isMod || isAdmin || isSuperAdmin) { db.Exec(`UPDATE topics SET is_closed=1 WHERE id=?;`, topicID) } else if action == "Reopen" && (isMod || isAdmin || isSuperAdmin) { db.Exec(`UPDATE topics SET is_closed=0 WHERE id=?;`, topicID) } else if action == "Delete" { db.Exec(`UPDATE topics SET is_deleted=1 WHERE id=?;`, topicID) http.Redirect(w, r, "/topics/edit?id="+topicID, http.StatusSeeOther) return } else if action == "Undelete" { db.Exec(`UPDATE topics SET is_deleted=0 WHERE id=?;`, topicID) } http.Redirect(w, r, "/topics?id="+topicID, http.StatusSeeOther) return } if db.QueryRow(`SELECT title, content, is_sticky, is_deleted, is_closed FROM topics WHERE id=?;`, topicID).Scan(&title, &content, &isSticky, &isDeleted, &isClosed) != nil { ErrNotFoundHandler(w, r) return } templates.Render(w, "topicedit.html", map[string]interface{}{ "Common": readCommonData(r, sess), "GroupID": groupID, "GroupName": groupName, "TopicID": topicID, "Title": title, "Content": content, "IsSticky": isSticky, "IsClosed": isClosed, "IsDeleted": isDeleted, "IsMod": isMod, "IsAdmin": isAdmin, "IsSuperAdmin": isSuperAdmin, }) })
View Source
var UserCommentsHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { ownerName := r.FormValue("u") lastCommentDate, err := strconv.ParseInt(r.FormValue("lcd"), 10, 64) if err != nil { lastCommentDate = 0 } var ownerID string if db.QueryRow(`SELECT id FROM users WHERE username=?;`, ownerName).Scan(&ownerID) != nil { ErrNotFoundHandler(w, r) return } type Comment struct { ID string Content template.HTML TopicID string TopicName string CreatedDate string ImgSrc string IsDeleted bool } commentsPerPage := 50 var comments []Comment var rows *db.Rows if lastCommentDate == 0 { rows = db.Query(`SELECT topics.title, comments.topicid, comments.id, comments.content, comments.image, comments.created_date, comments.is_deleted FROM comments INNER JOIN topics ON topics.id = comments.topicid AND comments.userid=? ORDER BY comments.created_date DESC LIMIT ?;`, ownerID, commentsPerPage) } else { rows = db.Query(`SELECT topics.title, comments.topicid, comments.id, comments.content, comments.image, comments.created_date, comments.is_deleted FROM comments INNER JOIN topics ON topics.id = comments.topicid AND comments.userid=? AND comments.created_date < ? ORDER BY comments.created_date DESC LIMIT ?;`, ownerID, lastCommentDate, commentsPerPage) } var cDate int64 for rows.Next() { comments = append(comments, Comment{}) c := &comments[len(comments)-1] var content string rows.Scan(&c.TopicName, &c.TopicID, &c.ID, &content, &c.ImgSrc, &cDate, &c.IsDeleted) c.CreatedDate = timeAgoFromNow(time.Unix(cDate, 0)) c.Content = formatComment(content) } if len(comments) >= commentsPerPage { lastCommentDate = cDate } else { lastCommentDate = 0 } templates.Render(w, "profilecomments.html", map[string]interface{}{ "Common": readCommonData(r, sess), "OwnerName": ownerName, "Comments": comments, "LastCommentDate": lastCommentDate, }) })
View Source
var UserGroupsHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { ownerID := sess.UserID.Int64 var ownerName string type Group struct { ID string Name string IsClosed bool CreatedDate string } var adminInGroups []Group rows := db.Query(`SELECT groups.id, groups.name, groups.is_closed, groups.created_date FROM groups INNER JOIN admins ON admins.groupid=groups.id AND admins.userid=?;`, ownerID) for rows.Next() { adminInGroups = append(adminInGroups, Group{}) g := &adminInGroups[len(adminInGroups)-1] var cDate int64 rows.Scan(&g.ID, &g.Name, &g.IsClosed, &cDate) g.CreatedDate = timeAgoFromNow(time.Unix(cDate, 0)) } var modInGroups []Group rows = db.Query(`SELECT groups.id, groups.name, groups.is_closed, groups.created_date FROM groups INNER JOIN mods ON mods.groupid=groups.id AND mods.userid=?;`, ownerID) for rows.Next() { modInGroups = append(modInGroups, Group{}) g := &adminInGroups[len(adminInGroups)-1] var cDate int64 rows.Scan(&g.ID, &g.Name, &g.IsClosed, &cDate) g.CreatedDate = timeAgoFromNow(time.Unix(cDate, 0)) } templates.Render(w, "profilegroups.html", map[string]interface{}{ "Common": readCommonData(r, sess), "OwnerName": ownerName, "AdminInGroups": adminInGroups, "ModInGroups": modInGroups, }) })
View Source
var UserProfileHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { userName := r.FormValue("u") var about, email string var isBanned bool var userID int64 if db.QueryRow(`SELECT id, about, email, is_banned FROM users WHERE username=?;`, userName).Scan(&userID, &about, &email, &isBanned) != nil { ErrNotFoundHandler(w, r) return } templates.Render(w, "profile.html", map[string]interface{}{ "Common": readCommonData(r, sess), "UserName": userName, "About": about, "Email": email, "IsSelf": sess.UserID.Valid && (userID == sess.UserID.Int64), "IsBanned": isBanned, }) })
View Source
var UserProfileUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, sess Session) { userName := r.FormValue("u") var about, email string var isBanned bool var userID int64 if db.QueryRow(`SELECT id, about, email, is_banned FROM users WHERE username=?;`, userName).Scan(&userID, &about, &email, &isBanned) != nil { ErrNotFoundHandler(w, r) return } if r.Method == "POST" { if !sess.UserID.Valid { ErrForbiddenHandler(w, r) return } action := r.PostFormValue("action") var isSuperAdmin bool db.QueryRow(`SELECT is_superadmin FROM users WHERE id=?;`, sess.UserID).Scan(&isSuperAdmin) if action == "Update" { if isSuperAdmin || userID == sess.UserID.Int64 { email := strings.TrimSpace(r.FormValue("email")) about := r.FormValue("about") if len(email) > 64 { sess.SetFlashMsg("Email should have fewer than 64 characters.") http.Redirect(w, r, "/users?u="+userName, http.StatusSeeOther) return } if len(about) > 1024 { sess.SetFlashMsg("About should have fewer than 1024 characters.") http.Redirect(w, r, "/users?u="+userName, http.StatusSeeOther) return } db.Exec(`UPDATE users SET email=?, about=? WHERE id=?;`, email, about, userID) } else { ErrForbiddenHandler(w, r) return } } else if action == "Ban" { if isSuperAdmin { db.Exec(`UPDATE users SET is_banned=1 WHERE id=?;`, userID) db.Exec(`DELETE FROM sessions WHERE userid=?;`, userID) } else { ErrForbiddenHandler(w, r) return } } else if action == "Unban" { if isSuperAdmin { db.Exec(`UPDATE users SET is_banned=0 WHERE id=?;`, userID) } else { ErrForbiddenHandler(w, r) return } } } sess.SetFlashMsg("Update successful.") http.Redirect(w, r, "/users?u="+userName, http.StatusSeeOther) })
View Source
var UserTopicsHandler = UA(func(w http.ResponseWriter, r *http.Request, sess Session) { ownerName := r.FormValue("u") var ownerID string if db.QueryRow(`SELECT id FROM users WHERE username=?;`, ownerName).Scan(&ownerID) != nil { ErrNotFoundHandler(w, r) return } lastTopicDate, err := strconv.ParseInt(r.FormValue("ltd"), 10, 64) if err != nil { lastTopicDate = 0 } numTopicsPerPage := 50 type Topic struct { ID string Title string IsClosed bool IsDeleted bool CreatedDate string } var topics []Topic var rows *db.Rows var cDate int64 if lastTopicDate == 0 { rows = db.Query(`SELECT id, title, is_deleted, is_closed, created_date FROM topics WHERE userid=? ORDER BY created_date DESC LIMIT ?;`, ownerID, numTopicsPerPage) } else { rows = db.Query(`SELECT id, title, is_deleted, is_closed, created_date FROM topics WHERE userid=? AND created_date < ? ORDER BY created_date DESC LIMIT ?;`, ownerID, lastTopicDate, numTopicsPerPage) } for rows.Next() { topics = append(topics, Topic{}) t := &topics[len(topics)-1] rows.Scan(&t.ID, &t.Title, &t.IsDeleted, &t.IsClosed, &cDate) t.CreatedDate = timeAgoFromNow(time.Unix(cDate, 0)) t.Title = censor(t.Title) } if len(topics) >= numTopicsPerPage { lastTopicDate = cDate } else { lastTopicDate = 0 } templates.Render(w, "profiletopics.html", map[string]interface{}{ "Common": readCommonData(r, sess), "OwnerName": ownerName, "Topics": topics, "LastTopicDate": lastTopicDate, }) })
Functions ¶
func A ¶
func A(handler func(w http.ResponseWriter, r *http.Request, sess Session)) func(w http.ResponseWriter, r *http.Request)
func Authenticate ¶ added in v1.1.0
func Authenticate() error
func ClearSession ¶ added in v1.1.0
func ClearSession(w http.ResponseWriter, r *http.Request)
func ErrForbiddenHandler ¶
func ErrForbiddenHandler(w http.ResponseWriter, r *http.Request)
func ErrNotFoundHandler ¶
func ErrNotFoundHandler(w http.ResponseWriter, r *http.Request)
func ErrServerHandler ¶
func ErrServerHandler(w http.ResponseWriter, r *http.Request)
func FaviconHandler ¶
func FaviconHandler(w http.ResponseWriter, r *http.Request)
func ImageHandler ¶
func ImageHandler(w http.ResponseWriter, r *http.Request)
func LogoutHandler ¶
func LogoutHandler(w http.ResponseWriter, r *http.Request)
func ScriptHandler ¶ added in v1.1.0
func ScriptHandler(w http.ResponseWriter, r *http.Request)
func StyleHandler ¶ added in v1.1.0
func StyleHandler(w http.ResponseWriter, r *http.Request)
func TestHandler ¶
func TestHandler(w http.ResponseWriter, r *http.Request)
Types ¶
type CommonData ¶ added in v1.1.0
type Session ¶ added in v1.1.0
type Session struct { SessionID string UserID sql.NullInt64 CSRFToken string Msg string CreatedDate time.Time UpdatedDate time.Time }
func OpenSession ¶ added in v1.1.0
func OpenSession(w http.ResponseWriter, r *http.Request) Session
func (*Session) Authenticate ¶ added in v1.1.0
func (*Session) IsUserSuperAdmin ¶ added in v1.1.0
func (*Session) IsUserValid ¶ added in v1.1.0
func (*Session) SetFlashMsg ¶ added in v1.1.0
Click to show internal directories.
Click to hide internal directories.