IP Search now searches profile replies.

Added the subscription and attachment stores.

Refactored the routes to use the new subscription and attachment stores.
Fixed up the buttons on the profile comments for Cosora.
Made various enhancements to Cosora.
Renamed routePanel to routePanelDashboard.
Changed routeCreateTopicSubmit into an upload action and moved it into /routes/topic.go and refactored some of the redundant bits away.
This commit is contained in:
Azareal 2018-01-22 08:15:45 +00:00
parent 8252c481df
commit a7fec11170
15 changed files with 266 additions and 193 deletions

29
common/attachments.go Normal file
View File

@ -0,0 +1,29 @@
package common
import (
"database/sql"
"../query_gen/lib"
)
var Attachments AttachmentStore
type AttachmentStore interface {
Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) error
}
type DefaultAttachmentStore struct {
add *sql.Stmt
}
func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) {
acc := qgen.Builder.Accumulator()
return &DefaultAttachmentStore{
add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(),
}, acc.FirstError()
}
func (store *DefaultAttachmentStore) Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) error {
_, err := store.add.Exec(sectionID, sectionTable, originID, originTable, uploadedBy, path)
return err
}

View File

@ -13,18 +13,20 @@ type IPSearcher interface {
} }
type DefaultIPSearcher struct { type DefaultIPSearcher struct {
searchUsers *sql.Stmt searchUsers *sql.Stmt
searchReplies *sql.Stmt searchTopics *sql.Stmt
searchTopics *sql.Stmt searchReplies *sql.Stmt
searchUsersReplies *sql.Stmt
} }
// NewDefaultIPSearcher gives you a new instance of DefaultIPSearcher // NewDefaultIPSearcher gives you a new instance of DefaultIPSearcher
func NewDefaultIPSearcher() (*DefaultIPSearcher, error) { func NewDefaultIPSearcher() (*DefaultIPSearcher, error) {
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
return &DefaultIPSearcher{ return &DefaultIPSearcher{
searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(), searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(),
searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(), searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(),
searchReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(), searchReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(),
searchUsersReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("users_replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
@ -53,16 +55,18 @@ func (searcher *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) {
if err != nil { if err != nil {
return uids, err return uids, err
} }
err = runQuery(searcher.searchTopics) err = runQuery(searcher.searchTopics)
if err != nil { if err != nil {
return uids, err return uids, err
} }
err = runQuery(searcher.searchReplies) err = runQuery(searcher.searchReplies)
if err != nil { if err != nil {
return uids, err return uids, err
} }
err = runQuery(searcher.searchUsersReplies)
if err != nil {
return uids, err
}
// Convert the user ID map to a slice, then bulk load the users // Convert the user ID map to a slice, then bulk load the users
uids = make([]int, len(reqUserList)) uids = make([]int, len(reqUserList))

27
common/subscription.go Normal file
View File

@ -0,0 +1,27 @@
package common
import "database/sql"
import "../query_gen/lib"
var Subscriptions SubscriptionStore
// ? Should we have a subscription store for each zone? topic, forum, etc?
type SubscriptionStore interface {
Add(uid int, elementID int, elementType string) error
}
type DefaultSubscriptionStore struct {
add *sql.Stmt
}
func NewDefaultSubscriptionStore() (*DefaultSubscriptionStore, error) {
acc := qgen.Builder.Accumulator()
return &DefaultSubscriptionStore{
add: acc.Insert("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Prepare(),
}, acc.FirstError()
}
func (store *DefaultSubscriptionStore) Add(uid int, elementID int, elementType string) error {
_, err := store.add.Exec(uid, elementID, elementType)
return err
}

View File

@ -35,11 +35,9 @@ type Stmts struct {
addActivity *sql.Stmt addActivity *sql.Stmt
notifyOne *sql.Stmt notifyOne *sql.Stmt
addEmail *sql.Stmt addEmail *sql.Stmt
addSubscription *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt addPlugin *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
addAttachment *sql.Stmt
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
editReply *sql.Stmt editReply *sql.Stmt
updatePlugin *sql.Stmt updatePlugin *sql.Stmt
@ -256,13 +254,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing addSubscription statement.")
stmts.addSubscription, err = db.Prepare("INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)")
if err != nil {
log.Print("Bad Query: ","INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)")
return err
}
log.Print("Preparing addForumPermsToForum statement.") log.Print("Preparing addForumPermsToForum statement.")
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) VALUES (?,?,?,?)") stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) VALUES (?,?,?,?)")
if err != nil { if err != nil {
@ -284,13 +275,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing addAttachment statement.")
stmts.addAttachment, err = db.Prepare("INSERT INTO [attachments] ([sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path]) VALUES (?,?,?,?,?,?)")
if err != nil {
log.Print("Bad Query: ","INSERT INTO [attachments] ([sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path]) VALUES (?,?,?,?,?,?)")
return err
}
log.Print("Preparing createWordFilter statement.") log.Print("Preparing createWordFilter statement.")
stmts.createWordFilter, err = db.Prepare("INSERT INTO [word_filters] ([find],[replacement]) VALUES (?,?)") stmts.createWordFilter, err = db.Prepare("INSERT INTO [word_filters] ([find],[replacement]) VALUES (?,?)")
if err != nil { if err != nil {

View File

@ -37,11 +37,9 @@ type Stmts struct {
addActivity *sql.Stmt addActivity *sql.Stmt
notifyOne *sql.Stmt notifyOne *sql.Stmt
addEmail *sql.Stmt addEmail *sql.Stmt
addSubscription *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt addPlugin *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
addAttachment *sql.Stmt
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
editReply *sql.Stmt editReply *sql.Stmt
updatePlugin *sql.Stmt updatePlugin *sql.Stmt
@ -232,12 +230,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing addSubscription statement.")
stmts.addSubscription, err = db.Prepare("INSERT INTO `activity_subscriptions`(`user`,`targetID`,`targetType`,`level`) VALUES (?,?,?,2)")
if err != nil {
return err
}
log.Print("Preparing addForumPermsToForum statement.") log.Print("Preparing addForumPermsToForum statement.")
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)") stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)")
if err != nil { if err != nil {
@ -256,12 +248,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing addAttachment statement.")
stmts.addAttachment, err = db.Prepare("INSERT INTO `attachments`(`sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path`) VALUES (?,?,?,?,?,?)")
if err != nil {
return err
}
log.Print("Preparing createWordFilter statement.") log.Print("Preparing createWordFilter statement.")
stmts.createWordFilter, err = db.Prepare("INSERT INTO `word_filters`(`find`,`replacement`) VALUES (?,?)") stmts.createWordFilter, err = db.Prepare("INSERT INTO `word_filters`(`find`,`replacement`) VALUES (?,?)")
if err != nil { if err != nil {

View File

@ -69,7 +69,7 @@ var RouteMap = map[string]interface{}{
"routePanelBackups": routePanelBackups, "routePanelBackups": routePanelBackups,
"routePanelLogsMod": routePanelLogsMod, "routePanelLogsMod": routePanelLogsMod,
"routePanelDebug": routePanelDebug, "routePanelDebug": routePanelDebug,
"routePanel": routePanel, "routePanelDashboard": routePanelDashboard,
"routeAccountEditCritical": routeAccountEditCritical, "routeAccountEditCritical": routeAccountEditCritical,
"routeAccountEditCriticalSubmit": routeAccountEditCriticalSubmit, "routeAccountEditCriticalSubmit": routeAccountEditCriticalSubmit,
"routeAccountEditAvatar": routeAccountEditAvatar, "routeAccountEditAvatar": routeAccountEditAvatar,
@ -83,7 +83,7 @@ var RouteMap = map[string]interface{}{
"routes.UnbanUser": routes.UnbanUser, "routes.UnbanUser": routes.UnbanUser,
"routes.ActivateUser": routes.ActivateUser, "routes.ActivateUser": routes.ActivateUser,
"routes.IPSearch": routes.IPSearch, "routes.IPSearch": routes.IPSearch,
"routeCreateTopicSubmit": routeCreateTopicSubmit, "routes.CreateTopicSubmit": routes.CreateTopicSubmit,
"routes.EditTopicSubmit": routes.EditTopicSubmit, "routes.EditTopicSubmit": routes.EditTopicSubmit,
"routes.DeleteTopicSubmit": routes.DeleteTopicSubmit, "routes.DeleteTopicSubmit": routes.DeleteTopicSubmit,
"routes.StickTopicSubmit": routes.StickTopicSubmit, "routes.StickTopicSubmit": routes.StickTopicSubmit,
@ -164,7 +164,7 @@ var routeMapEnum = map[string]int{
"routePanelBackups": 50, "routePanelBackups": 50,
"routePanelLogsMod": 51, "routePanelLogsMod": 51,
"routePanelDebug": 52, "routePanelDebug": 52,
"routePanel": 53, "routePanelDashboard": 53,
"routeAccountEditCritical": 54, "routeAccountEditCritical": 54,
"routeAccountEditCriticalSubmit": 55, "routeAccountEditCriticalSubmit": 55,
"routeAccountEditAvatar": 56, "routeAccountEditAvatar": 56,
@ -178,7 +178,7 @@ var routeMapEnum = map[string]int{
"routes.UnbanUser": 64, "routes.UnbanUser": 64,
"routes.ActivateUser": 65, "routes.ActivateUser": 65,
"routes.IPSearch": 66, "routes.IPSearch": 66,
"routeCreateTopicSubmit": 67, "routes.CreateTopicSubmit": 67,
"routes.EditTopicSubmit": 68, "routes.EditTopicSubmit": 68,
"routes.DeleteTopicSubmit": 69, "routes.DeleteTopicSubmit": 69,
"routes.StickTopicSubmit": 70, "routes.StickTopicSubmit": 70,
@ -257,7 +257,7 @@ var reverseRouteMapEnum = map[int]string{
50: "routePanelBackups", 50: "routePanelBackups",
51: "routePanelLogsMod", 51: "routePanelLogsMod",
52: "routePanelDebug", 52: "routePanelDebug",
53: "routePanel", 53: "routePanelDashboard",
54: "routeAccountEditCritical", 54: "routeAccountEditCritical",
55: "routeAccountEditCriticalSubmit", 55: "routeAccountEditCriticalSubmit",
56: "routeAccountEditAvatar", 56: "routeAccountEditAvatar",
@ -271,7 +271,7 @@ var reverseRouteMapEnum = map[int]string{
64: "routes.UnbanUser", 64: "routes.UnbanUser",
65: "routes.ActivateUser", 65: "routes.ActivateUser",
66: "routes.IPSearch", 66: "routes.IPSearch",
67: "routeCreateTopicSubmit", 67: "routes.CreateTopicSubmit",
68: "routes.EditTopicSubmit", 68: "routes.EditTopicSubmit",
69: "routes.DeleteTopicSubmit", 69: "routes.DeleteTopicSubmit",
70: "routes.StickTopicSubmit", 70: "routes.StickTopicSubmit",
@ -951,7 +951,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
err = routePanelDebug(w,req,user) err = routePanelDebug(w,req,user)
default: default:
common.RouteViewCounter.Bump(53) common.RouteViewCounter.Bump(53)
err = routePanel(w,req,user) err = routePanelDashboard(w,req,user)
} }
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
@ -1130,20 +1130,25 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/topic": case "/topic":
switch(req.URL.Path) { switch(req.URL.Path) {
case "/topic/create/submit/": case "/topic/create/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
return return
} }
err = common.HandleUploadRoute(w,req,user,common.Config.MaxRequestSize)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = common.NoUploadSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
common.RouteViewCounter.Bump(67) common.RouteViewCounter.Bump(67)
err = routeCreateTopicSubmit(w,req,user) err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/": case "/topic/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {

View File

@ -84,6 +84,14 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
common.Subscriptions, err = common.NewDefaultSubscriptionStore()
if err != nil {
return err
}
common.Attachments, err = common.NewDefaultAttachmentStore()
if err != nil {
return err
}
common.GlobalViewCounter, err = common.NewGlobalViewCounter() common.GlobalViewCounter, err = common.NewGlobalViewCounter()
if err != nil { if err != nil {

View File

@ -16,135 +16,6 @@ import (
"./common" "./common"
) )
func routeCreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Reduce this to 1MB for attachments for each file?
if r.ContentLength > int64(common.Config.MaxRequestSize) {
size, unit := common.ConvertByteUnit(float64(common.Config.MaxRequestSize))
return common.CustomError("Your attachments are too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user)
}
r.Body = http.MaxBytesReader(w, r.Body, int64(common.Config.MaxRequestSize))
err := r.ParseMultipartForm(int64(common.Megabyte))
if err != nil {
return common.LocalError("Unable to parse the form", w, r, user)
}
fid, err := strconv.Atoi(r.PostFormValue("topic-board"))
if err != nil {
return common.LocalError("The provided ForumID is not a valid number.", w, r, user)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateTopic {
return common.NoPermissions(w, r, user)
}
topicName := html.EscapeString(strings.Replace(r.PostFormValue("topic-name"), "\n", "", -1))
content := common.PreparseMessage(r.PostFormValue("topic-content"))
// TODO: Fully parse the post and store it in the parsed column
tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP)
if err != nil {
switch err {
case common.ErrNoRows:
return common.LocalError("Something went wrong, perhaps the forum got deleted?", w, r, user)
case common.ErrNoTitle:
return common.LocalError("This topic doesn't have a title", w, r, user)
case common.ErrNoBody:
return common.LocalError("This topic doesn't have a body", w, r, user)
default:
return common.InternalError(err, w, r)
}
}
_, err = stmts.addSubscription.Exec(user.ID, tid, "topic")
if err != nil {
return common.InternalError(err, w, r)
}
err = user.IncreasePostStats(common.WordCount(content), true)
if err != nil {
return common.InternalError(err, w, r)
}
// Handle the file attachments
// TODO: Stop duplicating this code
if user.Perms.UploadFiles {
files, ok := r.MultipartForm.File["upload_files"]
if ok {
if len(files) > 5 {
return common.LocalError("You can't attach more than five files", w, r, user)
}
for _, file := range files {
if common.Dev.DebugMode {
log.Print("file.Filename ", file.Filename)
}
extarr := strings.Split(file.Filename, ".")
if len(extarr) < 2 {
return common.LocalError("Bad file", w, r, user)
}
ext := extarr[len(extarr)-1]
// TODO: Can we do this without a regex?
reg, err := regexp.Compile("[^A-Za-z0-9]+")
if err != nil {
return common.LocalError("Bad file extension", w, r, user)
}
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
if !common.AllowedFileExts.Contains(ext) {
return common.LocalError("You're not allowed to upload files with this extension", w, r, user)
}
infile, err := file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, infile)
if err != nil {
return common.LocalError("Upload failed [Hashing Failed]", w, r, user)
}
infile.Close()
checksum := hex.EncodeToString(hasher.Sum(nil))
filename := checksum + "." + ext
outfile, err := os.Create("." + "/attachs/" + filename)
if err != nil {
return common.LocalError("Upload failed [File Creation Failed]", w, r, user)
}
defer outfile.Close()
infile, err = file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
_, err = io.Copy(outfile, infile)
if err != nil {
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
}
_, err = stmts.addAttachment.Exec(fid, "forums", tid, "topics", user.ID, filename)
if err != nil {
return common.InternalError(err, w, r)
}
}
}
}
common.PostCounter.Bump()
common.TopicCounter.Bump()
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
return nil
}
func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
tid, err := strconv.Atoi(r.PostFormValue("tid")) tid, err := strconv.Atoi(r.PostFormValue("tid"))
if err != nil { if err != nil {
@ -226,7 +97,7 @@ func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.
return common.LocalError("Upload failed [Copy Failed]", w, r, user) return common.LocalError("Upload failed [Copy Failed]", w, r, user)
} }
_, err = stmts.addAttachment.Exec(topic.ParentID, "forums", tid, "replies", user.ID, filename) err = common.Attachments.Add(topic.ParentID, "forums", tid, "replies", user.ID, filename)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

View File

@ -43,7 +43,7 @@ func panelRenderTemplate(tmplName string, w http.ResponseWriter, r *http.Request
return nil return nil
} }
func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelDashboard(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr

View File

@ -288,16 +288,12 @@ func writeInserts(adapter qgen.Adapter) error {
build.Insert("addEmail").Table("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Parse() build.Insert("addEmail").Table("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Parse()
build.Insert("addSubscription").Table("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Parse()
build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse() build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse()
build.Insert("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse() build.Insert("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse()
build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse() build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse()
build.Insert("addAttachment").Table("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Parse()
build.Insert("createWordFilter").Table("word_filters").Columns("find, replacement").Fields("?,?").Parse() build.Insert("createWordFilter").Table("word_filters").Columns("find, replacement").Fields("?,?").Parse()
return nil return nil

View File

@ -65,7 +65,7 @@ func buildTopicRoutes() {
topicGroup := newRouteGroup("/topic/") topicGroup := newRouteGroup("/topic/")
topicGroup.Routes( topicGroup.Routes(
View("routeTopicID", "/topic/", "extraData"), View("routeTopicID", "/topic/", "extraData"),
Action("routeCreateTopicSubmit", "/topic/create/submit/"), UploadAction("routes.CreateTopicSubmit", "/topic/create/submit/").MaxSizeVar("common.Config.MaxRequestSize"),
Action("routes.EditTopicSubmit", "/topic/edit/submit/", "extraData"), Action("routes.EditTopicSubmit", "/topic/edit/submit/", "extraData"),
Action("routes.DeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"), Action("routes.DeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"),
Action("routes.StickTopicSubmit", "/topic/stick/submit/", "extraData"), Action("routes.StickTopicSubmit", "/topic/stick/submit/", "extraData"),
@ -120,7 +120,7 @@ func buildAccountRoutes() {
func buildPanelRoutes() { func buildPanelRoutes() {
panelGroup := newRouteGroup("/panel/").Before("SuperModOnly") panelGroup := newRouteGroup("/panel/").Before("SuperModOnly")
panelGroup.Routes( panelGroup.Routes(
View("routePanel", "/panel/"), View("routePanelDashboard", "/panel/"),
View("routePanelForums", "/panel/forums/"), View("routePanelForums", "/panel/forums/"),
Action("routePanelForumsCreateSubmit", "/panel/forums/create/"), Action("routePanelForumsCreateSubmit", "/panel/forums/create/"),
Action("routePanelForumsDelete", "/panel/forums/delete/", "extraData"), Action("routePanelForumsDelete", "/panel/forums/delete/", "extraData"),

View File

@ -1,11 +1,18 @@
package routes package routes
import ( import (
"crypto/sha256"
"database/sql" "database/sql"
"encoding/hex"
"encoding/json" "encoding/json"
"html"
"io"
"log" "log"
"net/http" "net/http"
"os"
"regexp"
"strconv" "strconv"
"strings"
"../common" "../common"
) )
@ -99,6 +106,123 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid
return nil return nil
} }
func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
fid, err := strconv.Atoi(r.PostFormValue("topic-board"))
if err != nil {
return common.LocalError("The provided ForumID is not a valid number.", w, r, user)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateTopic {
return common.NoPermissions(w, r, user)
}
topicName := html.EscapeString(strings.Replace(r.PostFormValue("topic-name"), "\n", "", -1))
content := common.PreparseMessage(r.PostFormValue("topic-content"))
// TODO: Fully parse the post and store it in the parsed column
tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP)
if err != nil {
switch err {
case common.ErrNoRows:
return common.LocalError("Something went wrong, perhaps the forum got deleted?", w, r, user)
case common.ErrNoTitle:
return common.LocalError("This topic doesn't have a title", w, r, user)
case common.ErrNoBody:
return common.LocalError("This topic doesn't have a body", w, r, user)
default:
return common.InternalError(err, w, r)
}
}
err = common.Subscriptions.Add(user.ID, tid, "topic")
if err != nil {
return common.InternalError(err, w, r)
}
err = user.IncreasePostStats(common.WordCount(content), true)
if err != nil {
return common.InternalError(err, w, r)
}
// Handle the file attachments
// TODO: Stop duplicating this code
if user.Perms.UploadFiles {
files, ok := r.MultipartForm.File["upload_files"]
if ok {
if len(files) > 5 {
return common.LocalError("You can't attach more than five files", w, r, user)
}
for _, file := range files {
if common.Dev.DebugMode {
log.Print("file.Filename ", file.Filename)
}
extarr := strings.Split(file.Filename, ".")
if len(extarr) < 2 {
return common.LocalError("Bad file", w, r, user)
}
ext := extarr[len(extarr)-1]
// TODO: Can we do this without a regex?
reg, err := regexp.Compile("[^A-Za-z0-9]+")
if err != nil {
return common.LocalError("Bad file extension", w, r, user)
}
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
if !common.AllowedFileExts.Contains(ext) {
return common.LocalError("You're not allowed to upload files with this extension", w, r, user)
}
infile, err := file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, infile)
if err != nil {
return common.LocalError("Upload failed [Hashing Failed]", w, r, user)
}
infile.Close()
checksum := hex.EncodeToString(hasher.Sum(nil))
filename := checksum + "." + ext
outfile, err := os.Create("." + "/attachs/" + filename)
if err != nil {
return common.LocalError("Upload failed [File Creation Failed]", w, r, user)
}
defer outfile.Close()
infile, err = file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
_, err = io.Copy(outfile, infile)
if err != nil {
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
}
err = common.Attachments.Add(fid, "forums", tid, "topics", user.ID, filename)
if err != nil {
return common.InternalError(err, w, r)
}
}
}
}
common.PostCounter.Bump()
common.TopicCounter.Bump()
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
return nil
}
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes // TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
// TODO: Disable stat updates in posts handled by plugin_guilds // TODO: Disable stat updates in posts handled by plugin_guilds
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {

View File

@ -10,8 +10,8 @@
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem editable_parent" style="background-image: url('{{.Avatar}}');"> <div class="rowitem editable_parent" style="background-image: url('{{.Avatar}}');">
<img class="bgsub" src="{{.Avatar}}" alt="{{.Name}}'s Avatar" /> <img class="bgsub" src="{{.Avatar}}" alt="{{.Name}}'s Avatar" />
<a class="rowTitle" {{if $.CurrentUser.Perms.EditUser}}href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}" {{end}}class="editable_block">{{.Name}}</a> <a class="rowTitle editable_block"{{if $.CurrentUser.Perms.EditUser}} href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}"{{end}}>{{.Name}}</a>
<a href="/user/{{.ID}}" class="tag-mini">Profile</a> <a href="/user/{{.ID}}" class="tag-mini profile_url">Profile</a>
{{if (.Tag) and (.IsSuperMod)}}<span style="float: right;"><span class="panel_tag" style="margin-left 4px;">{{.Tag}}</span></span>{{end}} {{if (.Tag) and (.IsSuperMod)}}<span style="float: right;"><span class="panel_tag" style="margin-left 4px;">{{.Tag}}</span></span>{{end}}
<span class="panel_floater"> <span class="panel_floater">

View File

@ -642,6 +642,10 @@ textarea {
.rowlist.bgavatars .rowitem { .rowlist.bgavatars .rowitem {
flex-direction: column; flex-direction: column;
} }
.rowlist.bgavatars .rowitem {
padding-top: 16px;
padding-bottom: 10px;
}
.bgavatars .bgsub { .bgavatars .bgsub {
border-radius: 30px; border-radius: 30px;
height: 48px; height: 48px;
@ -1060,6 +1064,28 @@ textarea {
#profile_comments .content_column { #profile_comments .content_column {
margin-bottom: 16px; margin-bottom: 16px;
} }
#profile_comments button {
background: inherit;
color: var(--lighter-text-color);
padding-left: 8px;
padding-right: 8px;
cursor: pointer;
}
#profile_comments button:hover {
color: var(--light-text-color);
}
#profile_comments button.edit_item:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\f040";
}
#profile_comments button.delete_item:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\f1f8";
}
#profile_comments button.report_item:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\f024";
}
#profile_comments_head { #profile_comments_head {
margin-top: 6px; margin-top: 6px;
} }
@ -1167,7 +1193,7 @@ textarea {
/* TODO: Highlight the one we're currently on? */ /* TODO: Highlight the one we're currently on? */
.pageset { .pageset {
display: flex; display: flex;
margin-left: 16px; margin-left: 14px;
} }
.pageitem { .pageitem {
padding: 8px; padding: 8px;

View File

@ -91,6 +91,9 @@
#panel_users .panel_tag:not(.panel_right_button) { #panel_users .panel_tag:not(.panel_right_button) {
display: none; display: none;
} }
.panel_right_button + .panel_right_button {
margin-left: 3px;
}
.panel_group_perms .formitem a { .panel_group_perms .formitem a {
margin-top: 5px; margin-top: 5px;
@ -128,6 +131,12 @@
display: block; display: block;
} }
#panel_users .rowitem .rowTitle {
border-bottom: 1px solid var(--lighter-text-color);
padding-bottom: 4px;
margin-bottom: 4px;
}
#forum_quick_perms .formitem { #forum_quick_perms .formitem {
display: flex; display: flex;
} }
@ -190,3 +199,7 @@
font-size: 16px; font-size: 16px;
} }
*/ */
.pageset {
margin-left: 16px;
}