1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-18 11:46:23 -07:00

Add move thread page

Separate post move, edit, and deletion from server for better organization
This commit is contained in:
Eggbertx 2022-09-23 15:50:18 -07:00
parent 6c4b5a0155
commit 2b12801c60
9 changed files with 614 additions and 279 deletions

193
cmd/gochan/deleteposts.go Normal file
View file

@ -0,0 +1,193 @@
package main
import (
"database/sql"
"fmt"
"net/http"
"os"
"path"
"strconv"
"github.com/gochan-org/gochan/pkg/building"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/manage"
"github.com/gochan-org/gochan/pkg/serverutil"
)
func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.Request) {
// Delete a post or thread
password := request.FormValue("password")
passwordMD5 := gcutil.Md5Sum(password)
rank := manage.GetStaffRank(request)
fileOnly := request.FormValue("fileonly") == "on"
wantsJSON := serverutil.IsRequestingJSON(request)
if wantsJSON {
writer.Header().Set("Content-Type", "application/json")
} else {
writer.Header().Set("Content-Type", "text/plain")
}
boardid, err := strconv.Atoi(request.FormValue("boardid"))
if err != nil {
gcutil.LogError(err).
Str("IP", gcutil.GetRealIP(request)).
Str("boardid", request.FormValue("boardid")).
Msg("Invalid form data (boardid)")
writer.WriteHeader(http.StatusBadRequest)
serverutil.ServeError(writer, err.Error(), wantsJSON, nil)
return
}
board, err := gcsql.GetBoardFromID(boardid)
if err != nil {
serverutil.ServeErrorPage(writer, "Invalid form data: "+err.Error())
gcutil.LogError(err).
Str("IP", gcutil.GetRealIP(request)).
Int("boardid", boardid).
Msg("Invalid form data (error populating data")
return
}
if password == "" && rank == 0 {
serverutil.ServeErrorPage(writer, "Password required for post deletion")
return
}
for _, checkedPostID := range checkedPosts {
var post gcsql.Post
var err error
post.ID = checkedPostID
post.BoardID = boardid
post, err = gcsql.GetSpecificPost(post.ID, true)
if err == sql.ErrNoRows {
serverutil.ServeError(writer, "Post does not exist", wantsJSON, map[string]interface{}{
"postid": post.ID,
"boardid": post.BoardID,
})
return
} else if err != nil {
gcutil.Logger().Error().
Str("requestType", "deletePost").
Err(err).
Int("postid", post.ID).
Int("boardid", post.BoardID).
Msg("Error deleting post")
serverutil.ServeError(writer, "Error deleting post: "+err.Error(), wantsJSON, map[string]interface{}{
"postid": post.ID,
"boardid": post.BoardID,
})
return
}
if passwordMD5 != post.Password && rank == 0 {
serverutil.ServeError(writer, fmt.Sprintf("Incorrect password for #%d", post.ID), wantsJSON, map[string]interface{}{
"postid": post.ID,
"boardid": post.BoardID,
})
return
}
if fileOnly {
fileName := post.Filename
if fileName != "" && fileName != "deleted" {
var files []string
if files, err = post.GetFilePaths(); err != nil {
gcutil.Logger().Error().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Err(err).
Msg("Error getting file upload info")
serverutil.ServeError(writer, "Error getting file upload info: "+err.Error(), wantsJSON, map[string]interface{}{
"postid": post.ID,
})
return
}
if err = post.UnlinkUploads(true); err != nil {
gcutil.Logger().Error().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Err(err).
Msg("Error unlinking post uploads")
serverutil.ServeError(writer, err.Error(), wantsJSON, map[string]interface{}{
"postid": post.ID,
})
return
}
for _, filePath := range files {
if err = os.Remove(filePath); err != nil {
fileBase := path.Base(filePath)
gcutil.Logger().Error().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Str("file", filePath).
Err(err).
Msg("Error unlinking post uploads")
serverutil.ServeError(writer, fmt.Sprintf("Error deleting %s: %s", fileBase, err.Error()), wantsJSON, map[string]interface{}{
"postid": post.ID,
"file": fileBase,
})
return
}
}
}
_board, _ := gcsql.GetBoardFromID(post.BoardID)
building.BuildBoardPages(&_board)
var opPost gcsql.Post
if post.ParentID > 0 {
// post is a reply, get the OP
opPost, _ = gcsql.GetSpecificPost(post.ParentID, true)
} else {
opPost = post
}
building.BuildThreadPages(&opPost)
} else {
// delete the post
if err = gcsql.DeletePost(post.ID, true); err != nil {
gcutil.Logger().Error().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Err(err).
Msg("Error deleting post")
serverutil.ServeError(writer, "Error deleting post: "+err.Error(), wantsJSON, map[string]interface{}{
"postid": post.ID,
})
return
}
if post.ParentID == 0 {
threadIndexPath := path.Join(config.GetSystemCriticalConfig().DocumentRoot, board.WebPath(strconv.Itoa(post.ID), "threadPage"))
os.Remove(threadIndexPath + ".html")
os.Remove(threadIndexPath + ".json")
} else {
_board, _ := gcsql.GetBoardFromID(post.BoardID)
building.BuildBoardPages(&_board)
}
building.BuildBoards(false, post.BoardID)
}
gcutil.LogAccess(request).
Str("requestType", "deletePost").
Int("boardid", post.BoardID).
Int("postid", post.ID).
Bool("fileOnly", fileOnly).
Msg("Post deleted")
if wantsJSON {
serverutil.ServeJSON(writer, map[string]interface{}{
"success": "post deleted",
"postid": post.ID,
"boardid": post.BoardID,
"fileOnly": fileOnly,
})
} else {
if post.ParentID == 0 {
// deleted thread
http.Redirect(writer, request, board.WebPath("/", "boardPage"), http.StatusFound)
} else {
// deleted a post in the thread
http.Redirect(writer, request, post.GetURL(false), http.StatusFound)
}
}
}
}

132
cmd/gochan/editpost.go Normal file
View file

@ -0,0 +1,132 @@
package main
import (
"net/http"
"strconv"
"github.com/gochan-org/gochan/pkg/building"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gctemplates"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/manage"
"github.com/gochan-org/gochan/pkg/posting"
"github.com/gochan-org/gochan/pkg/serverutil"
)
func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.ResponseWriter, request *http.Request) {
password := request.FormValue("password")
wantsJSON := serverutil.IsRequestingJSON(request)
if editBtn == "Edit post" {
var err error
if len(checkedPosts) == 0 {
serverutil.ServeErrorPage(writer, "You need to select one post to edit.")
return
} else if len(checkedPosts) > 1 {
serverutil.ServeErrorPage(writer, "You can only edit one post at a time.")
return
}
rank := manage.GetStaffRank(request)
if password == "" && rank == 0 {
serverutil.ServeErrorPage(writer, "Password required for post editing")
return
}
passwordMD5 := gcutil.Md5Sum(password)
var post gcsql.Post
post, err = gcsql.GetSpecificPost(checkedPosts[0], true)
if err != nil {
gcutil.Logger().Error().
Err(err).
Msg("Error getting post information")
return
}
if post.Password != passwordMD5 && rank == 0 {
serverutil.ServeErrorPage(writer, "Wrong password")
return
}
if err = gctemplates.PostEdit.Execute(writer, map[string]interface{}{
"systemCritical": config.GetSystemCriticalConfig(),
"siteConfig": config.GetSiteConfig(),
"boardConfig": config.GetBoardConfig(""),
"post": post,
"referrer": request.Referer(),
}); err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Error executing edit post template")
serverutil.ServeError(writer, "Error executing edit post template: "+err.Error(), wantsJSON, nil)
return
}
}
if doEdit == "1" {
var password string
postid, err := strconv.Atoi(request.FormValue("postid"))
if err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Invalid form data")
serverutil.ServeErrorPage(writer, "Invalid form data: "+err.Error())
return
}
boardid, err := strconv.Atoi(request.FormValue("boardid"))
if err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Invalid form data")
serverutil.ServeErrorPage(writer, "Invalid form data: "+err.Error())
return
}
password, err = gcsql.GetPostPassword(postid)
if err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Invalid form data")
return
}
rank := manage.GetStaffRank(request)
if request.FormValue("password") != password && rank == 0 {
serverutil.ServeErrorPage(writer, "Wrong password")
return
}
var board gcsql.Board
if err = board.PopulateData(boardid); err != nil {
serverutil.ServeErrorPage(writer, "Invalid form data: "+err.Error())
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Invalid form data")
return
}
if err = gcsql.UpdatePost(postid, request.FormValue("editemail"), request.FormValue("editsubject"),
posting.FormatMessage(request.FormValue("editmsg"), board.Dir), request.FormValue("editmsg")); err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Unable to edit post")
serverutil.ServeErrorPage(writer, "Unable to edit post: "+err.Error())
return
}
building.BuildBoards(false, boardid)
building.BuildFrontPage()
if request.FormValue("parentid") == "0" {
http.Redirect(writer, request, "/"+board.Dir+"/res/"+strconv.Itoa(postid)+".html", http.StatusFound)
} else {
http.Redirect(writer, request, "/"+board.Dir+"/res/"+request.FormValue("parentid")+".html#"+strconv.Itoa(postid), http.StatusFound)
}
return
}
}

205
cmd/gochan/movethread.go Normal file
View file

@ -0,0 +1,205 @@
package main
import (
"fmt"
"net/http"
"strconv"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gctemplates"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/manage"
"github.com/gochan-org/gochan/pkg/serverutil"
)
func moveThread(checkedPosts []int, moveBtn string, doMove string, writer http.ResponseWriter, request *http.Request) {
password := request.FormValue("password")
wantsJSON := serverutil.IsRequestingJSON(request)
if moveBtn == "Move thread" {
// user clicked on move thread button on board or thread page
if len(checkedPosts) == 0 {
serverutil.ServeError(writer, "You need to select one thread to move.", wantsJSON, nil)
return
} else if len(checkedPosts) > 1 {
serverutil.ServeError(writer, "You can only move one thread at a time.", wantsJSON, nil)
return
}
post, err := gcsql.GetPostFromID(checkedPosts[0], true)
if err != nil {
serverutil.ServeError(writer, err.Error(), wantsJSON, nil)
gcutil.LogError(err).
Str("IP", gcutil.GetRealIP(request)).
Int("postid", checkedPosts[0]).
Msg("Error getting post from ID")
return
}
if post.ParentID != post.ID {
serverutil.ServeError(writer, "You appear to be trying to move a post that is not the top post in the thread", wantsJSON, map[string]interface{}{
"postid": checkedPosts[0],
"parentid": post.ParentID,
})
return
}
srcBoardID, err := strconv.Atoi(request.PostForm.Get("boardid"))
if err != nil {
serverutil.ServeError(writer, fmt.Sprintf("Invalid or missing boarid: %q", request.PostForm.Get("boardid")), wantsJSON, map[string]interface{}{
"boardid": srcBoardID,
})
}
var destBoards []gcsql.Board
var srcBoard gcsql.Board
for _, board := range gcsql.AllBoards {
if board.ID != srcBoardID {
destBoards = append(destBoards, board)
} else {
srcBoard = board
}
}
if err = serverutil.MinifyTemplate(gctemplates.MoveThreadPage, map[string]interface{}{
"postid": post.ID,
"webroot": config.GetSystemCriticalConfig().WebRoot,
"destBoards": destBoards,
"srcBoard": srcBoard,
}, writer, "text/html"); err != nil {
gcutil.LogError(err).
Str("IP", gcutil.GetRealIP(request)).
Int("postid", post.ID).Send()
serverutil.ServeError(writer, err.Error(), wantsJSON, nil)
return
}
} else if doMove == "1" {
// user got here from the move thread page
rank := manage.GetStaffRank(request)
if password == "" && rank == 0 {
writer.WriteHeader(http.StatusBadRequest)
serverutil.ServeError(writer, "Password required for post editing", wantsJSON, nil)
return
}
postIDstr := request.PostForm.Get("postid")
postID, err := strconv.Atoi(postIDstr)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
serverutil.ServeError(writer, fmt.Sprintf("Error parsing postid value: %q: %s", postIDstr, err.Error()), wantsJSON, map[string]interface{}{
"postid": postIDstr,
})
return
}
srcBoardIDstr := request.PostForm.Get("srcboardid")
srcBoardID, err := strconv.Atoi(srcBoardIDstr)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
serverutil.ServeError(writer, fmt.Sprintf("Error parsing srcboardid value: %q: %s", srcBoardIDstr, err.Error()), wantsJSON, map[string]interface{}{
"srcboardid": srcBoardIDstr,
})
return
}
srcBoard, err := gcsql.GetBoardFromID(srcBoardID)
if err != nil {
gcutil.LogError(err).
Int("srcboardid", srcBoardID).Send()
writer.WriteHeader(http.StatusInternalServerError)
serverutil.ServeError(writer, err.Error(), wantsJSON, map[string]interface{}{
"srcboardid": srcBoardID,
})
return
}
destBoardIDstr := request.PostForm.Get("destboardid")
destBoardID, err := strconv.Atoi(destBoardIDstr)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
serverutil.ServeError(writer, fmt.Sprintf("Error parsing destboardid value: %q: %s", destBoardIDstr, err.Error()), wantsJSON, map[string]interface{}{
"destboardid": destBoardIDstr,
})
return
}
destBoard, err := gcsql.GetBoardFromID(destBoardID)
if err != nil {
gcutil.LogError(err).
Int("destboardid", destBoardID).Send()
writer.WriteHeader(http.StatusInternalServerError)
serverutil.ServeError(writer, err.Error(), wantsJSON, map[string]interface{}{
"destboardid": destBoardID,
})
return
}
post, err := gcsql.GetPostFromID(postID, true)
if err != nil {
gcutil.LogError(err).Int("postid", postID).Send()
writer.WriteHeader(http.StatusInternalServerError)
serverutil.ServeError(writer, err.Error(), wantsJSON, map[string]interface{}{
"postid": postID,
})
return
}
passwordMD5 := gcutil.Md5Sum(password)
if passwordMD5 != post.Password && rank == 0 {
serverutil.ServeError(writer, "Wrong password", wantsJSON, nil)
return
}
threadUploads, err := getThreadFiles(post)
if err != nil {
gcutil.LogError(err).Int("postid", post.ID).Send()
writer.WriteHeader(http.StatusInternalServerError)
serverutil.ServeError(writer, "Error getting list of files in thread", wantsJSON, map[string]interface{}{
"postid": post.ID,
})
}
for _, upload := range threadUploads {
fmt.Println("Upload post ID:", upload.postID)
fmt.Println("Upload filename:", upload.filename)
fmt.Println("Upload thumbnail:", gcutil.GetThumbnailPath("thumb", upload.filename))
fmt.Println("Upload catalog thumbnail:", gcutil.GetThumbnailPath("catalog", upload.filename))
fmt.Println()
// threadUploads[f] = path.Join(config.GetSystemCriticalConfig().DocumentRoot, srcBoard.Dir, filename)
}
serverutil.ServeJSON(writer, map[string]interface{}{
"srcBoard": srcBoard.Dir,
"destBoard": destBoard.Dir,
"post": post,
})
}
}
type postUpload struct {
filename string
thumbnail string
catalogThumbnail string
postID int
}
func getThreadFiles(post *gcsql.Post) ([]postUpload, error) {
query := `SELECT filename,post_id FROM DBPREFIXfiles WHERE post_id IN (
SELECT id FROM DBPREFIXposts WHERE thread_id = (
SELECT thread_id FROM DBPREFIXposts WHERE id = ?)) AND filename != 'deleted'`
rows, err := gcsql.QuerySQL(query, post.ID)
if err != nil {
return nil, err
}
var uploads []postUpload
for rows.Next() {
var upload postUpload
if err = rows.Scan(&upload.filename, &upload.postID); err != nil {
return uploads, err
}
upload.thumbnail = gcutil.GetThumbnailPath("thumb", upload.filename)
var parentID int
if parentID, err = gcsql.GetThreadIDZeroIfTopPost(upload.postID); err != nil {
return uploads, err
}
if parentID == 0 {
upload.catalogThumbnail = gcutil.GetThumbnailPath("catalog", upload.filename)
}
uploads = append(uploads, upload)
}
return uploads, nil
}

View file

@ -1,7 +1,6 @@
package main
import (
"database/sql"
"fmt"
"net"
"net/http"
@ -11,10 +10,7 @@ import (
"strconv"
"strings"
"github.com/gochan-org/gochan/pkg/building"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gctemplates"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/manage"
"github.com/gochan-org/gochan/pkg/posting"
@ -149,7 +145,7 @@ func initServer() {
http.Redirect(writer, request, "https://www.youtube.com/watch?v=dQw4w9WgXcQ", http.StatusFound)
}
}
// Eventually plugins will be able to register new namespaces or they will be restricted to something
// Eventually plugins might be able to register new namespaces or they might be restricted to something
// like /plugin
if systemCritical.UseFastCGI {
@ -169,24 +165,22 @@ func initServer() {
// handles requests to /util
func utilHandler(writer http.ResponseWriter, request *http.Request) {
action := request.FormValue("action")
password := request.FormValue("password")
board := request.FormValue("board")
boardid := request.FormValue("boardid")
fileOnly := request.FormValue("fileonly") == "on"
deleteBtn := request.PostFormValue("delete_btn")
reportBtn := request.PostFormValue("report_btn")
editBtn := request.PostFormValue("edit_btn")
doEdit := request.PostFormValue("doedit")
moveBtn := request.PostFormValue("move_btn")
doMove := request.PostFormValue("domove")
systemCritical := config.GetSystemCriticalConfig()
wantsJSON := request.PostFormValue("json") == "1"
wantsJSON := serverutil.IsRequestingJSON(request)
if wantsJSON {
writer.Header().Set("Content-Type", "application/json")
}
if action == "" && deleteBtn != "Delete" && reportBtn != "Report" && editBtn != "Edit" && doEdit != "1" {
if action == "" && deleteBtn != "Delete" && reportBtn != "Report" && editBtn != "Edit post" && doEdit != "1" && moveBtn != "Move thread" && doMove != "1" {
gcutil.LogAccess(request).Int("status", 400).Msg("received invalid /util request")
if wantsJSON {
writer.WriteHeader(400)
writer.WriteHeader(http.StatusBadRequest)
serverutil.ServeJSON(writer, map[string]interface{}{"error": "Invalid /util request"})
} else {
http.Redirect(writer, request, path.Join(systemCritical.WebRoot, "/"), http.StatusBadRequest)
@ -234,265 +228,18 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) {
return
}
if editBtn == "Edit" {
var err error
if len(checkedPosts) == 0 {
serverutil.ServeErrorPage(writer, "You need to select one post to edit.")
return
} else if len(checkedPosts) > 1 {
serverutil.ServeErrorPage(writer, "You can only edit one post at a time.")
return
} else {
rank := manage.GetStaffRank(request)
if password == "" && rank == 0 {
serverutil.ServeErrorPage(writer, "Password required for post editing")
return
}
passwordMD5 := gcutil.Md5Sum(password)
var post gcsql.Post
post, err = gcsql.GetSpecificPost(checkedPosts[0], true)
if err != nil {
gcutil.Logger().Error().
Err(err).
Msg("Error getting post information")
return
}
if post.Password != passwordMD5 && rank == 0 {
serverutil.ServeErrorPage(writer, "Wrong password")
return
}
if err = gctemplates.PostEdit.Execute(writer, map[string]interface{}{
"systemCritical": config.GetSystemCriticalConfig(),
"siteConfig": config.GetSiteConfig(),
"boardConfig": config.GetBoardConfig(""),
"post": post,
"referrer": request.Referer(),
}); err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Error executing edit post template")
serverutil.ServeError(writer, "Error executing edit post template: "+err.Error(), wantsJSON, nil)
return
}
}
if editBtn != "" || doEdit == "1" {
editPost(checkedPosts, editBtn, doEdit, writer, request)
return
}
if doEdit == "1" {
var password string
postid, err := strconv.Atoi(request.FormValue("postid"))
if err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Invalid form data")
serverutil.ServeErrorPage(writer, "Invalid form data: "+err.Error())
return
}
boardid, err := strconv.Atoi(request.FormValue("boardid"))
if err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Invalid form data")
serverutil.ServeErrorPage(writer, "Invalid form data: "+err.Error())
return
}
password, err = gcsql.GetPostPassword(postid)
if err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Invalid form data")
return
}
rank := manage.GetStaffRank(request)
if request.FormValue("password") != password && rank == 0 {
serverutil.ServeErrorPage(writer, "Wrong password")
return
}
var board gcsql.Board
if err = board.PopulateData(boardid); err != nil {
serverutil.ServeErrorPage(writer, "Invalid form data: "+err.Error())
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Invalid form data")
return
}
if err = gcsql.UpdatePost(postid, request.FormValue("editemail"), request.FormValue("editsubject"),
posting.FormatMessage(request.FormValue("editmsg"), board.Dir), request.FormValue("editmsg")); err != nil {
gcutil.Logger().Error().
Err(err).
Str("IP", gcutil.GetRealIP(request)).
Msg("Unable to edit post")
serverutil.ServeErrorPage(writer, "Unable to edit post: "+err.Error())
return
}
building.BuildBoards(false, boardid)
building.BuildFrontPage()
if request.FormValue("parentid") == "0" {
http.Redirect(writer, request, "/"+board.Dir+"/res/"+strconv.Itoa(postid)+".html", http.StatusFound)
} else {
http.Redirect(writer, request, "/"+board.Dir+"/res/"+request.FormValue("parentid")+".html#"+strconv.Itoa(postid), http.StatusFound)
}
if moveBtn != "" || doMove == "1" {
moveThread(checkedPosts, moveBtn, doMove, writer, request)
return
}
if deleteBtn == "Delete" {
// Delete a post or thread
writer.Header().Add("Content-Type", "text/plain")
passwordMD5 := gcutil.Md5Sum(password)
rank := manage.GetStaffRank(request)
if password == "" && rank == 0 {
serverutil.ServeErrorPage(writer, "Password required for post deletion")
return
}
for _, checkedPostID := range checkedPosts {
var post gcsql.Post
var err error
post.ID = checkedPostID
post.BoardID, err = strconv.Atoi(boardid)
if err != nil {
gcutil.Logger().Error().
Err(err).
Str("requestType", "deletePost").
Str("IP", gcutil.GetRealIP(request)).
Str("boardid", boardid).
Int("postid", checkedPostID).Send()
serverutil.ServeError(writer,
fmt.Sprintf("Invalid boardid '%s' in post deletion request (got error '%s')", boardid, err),
wantsJSON, map[string]interface{}{
"boardid": boardid,
"postid": checkedPostID,
})
return
}
post, err = gcsql.GetSpecificPost(post.ID, true)
if err == sql.ErrNoRows {
serverutil.ServeError(writer, "Post does not exist", wantsJSON, map[string]interface{}{
"postid": post.ID,
"boardid": post.BoardID,
})
} else if err != nil {
gcutil.Logger().Error().
Str("requestType", "deletePost").
Err(err).
Int("postid", post.ID).
Int("boardid", post.BoardID).
Msg("Error deleting post")
serverutil.ServeError(writer, "Error deleting post: "+err.Error(), wantsJSON, map[string]interface{}{
"postid": post.ID,
"boardid": post.BoardID,
})
}
if passwordMD5 != post.Password && rank == 0 {
serverutil.ServeError(writer, fmt.Sprintf("Incorrect password for #%d", post.ID), wantsJSON, map[string]interface{}{
"postid": post.ID,
"boardid": post.BoardID,
})
return
}
if fileOnly {
fileName := post.Filename
if fileName != "" && fileName != "deleted" {
var files []string
if files, err = post.GetFilePaths(); err != nil {
gcutil.Logger().Error().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Err(err).
Msg("Error getting file upload info")
serverutil.ServeError(writer, "Error getting file upload info: "+err.Error(), wantsJSON, map[string]interface{}{
"postid": post.ID,
})
return
}
if err = post.UnlinkUploads(true); err != nil {
gcutil.Logger().Error().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Err(err).
Msg("Error unlinking post uploads")
serverutil.ServeError(writer, err.Error(), wantsJSON, map[string]interface{}{
"postid": post.ID,
})
return
}
for _, filePath := range files {
if err = os.Remove(filePath); err != nil {
fileBase := path.Base(filePath)
gcutil.Logger().Error().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Str("file", filePath).
Err(err).
Msg("Error unlinking post uploads")
serverutil.ServeError(writer, fmt.Sprintf("Error deleting %s: %s", fileBase, err.Error()), wantsJSON, map[string]interface{}{
"postid": post.ID,
"file": fileBase,
})
return
}
}
}
_board, _ := gcsql.GetBoardFromID(post.BoardID)
building.BuildBoardPages(&_board)
var opPost gcsql.Post
if post.ParentID > 0 {
// post is a reply, get the OP
opPost, _ = gcsql.GetSpecificPost(post.ParentID, true)
} else {
opPost = post
}
building.BuildThreadPages(&opPost)
} else {
// delete the post
if err = gcsql.DeletePost(post.ID, true); err != nil {
gcutil.Logger().Error().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Err(err).
Msg("Error deleting post")
serverutil.ServeError(writer, "Error deleting post: "+err.Error(), wantsJSON, map[string]interface{}{
"postid": post.ID,
})
}
if post.ParentID == 0 {
threadIndexPath := path.Join(systemCritical.DocumentRoot, board, "/res/", strconv.Itoa(post.ID))
os.Remove(threadIndexPath + ".html")
os.Remove(threadIndexPath + ".json")
} else {
_board, _ := gcsql.GetBoardFromID(post.BoardID)
building.BuildBoardPages(&_board)
}
building.BuildBoards(false, post.BoardID)
}
gcutil.Logger().Info().
Str("requestType", "deletePost").
Str("IP", post.IP).
Int("boardid", post.BoardID).
Bool("fileOnly", fileOnly).
Msg("Post deleted")
if !wantsJSON {
http.Redirect(writer, request, request.Referer(), http.StatusFound)
}
}
deletePosts(checkedPosts, writer, request)
return
}
}

View file

@ -16,9 +16,11 @@ import (
const GochanVersionKeyConstant = "gochan"
var (
ErrNilBoard = errors.New("board is nil")
ErrBoardExists = errors.New("board already exists")
ErrBoardDoesNotExist = errors.New("board does not exist")
ErrNilBoard = errors.New("board is nil")
ErrBoardExists = errors.New("board already exists")
ErrBoardDoesNotExist = errors.New("board does not exist")
ErrThreadExists = errors.New("thread already exists")
ErrThreadDoesNotExist = errors.New("thread does not exist")
)
// GetAllNondeletedMessageRaw gets all the raw message texts from the database, saved per id
@ -179,6 +181,35 @@ func OptimizeDatabase() error {
return nil
}
// ChangeThreadBoardID updates the given thread's post ID and the destination board ID
func ChangeThreadBoardID(postID int, newBoardID int) error {
if !DoesBoardExistByID(newBoardID) {
return ErrBoardDoesNotExist
}
var threadRowID int
err := QueryRowSQL(`SELECT thread_id FROM DBPREFIXposts WHERE id = ?`,
interfaceSlice(postID),
interfaceSlice(&threadRowID))
if err != nil {
return err
}
if threadRowID < 1 {
return ErrThreadDoesNotExist
}
_, err = ExecSQL(`UPDATE DBPREFIXthreads SET board_id = ? WHERE id = ?`, newBoardID, threadRowID)
return err
}
// ChangeThreadBoardByURI updates a thread's board ID, given the thread's post ID and
// the destination board's uri
func ChangeThreadBoardByURI(postID int, uri string) error {
boardID, err := getBoardIDFromURI(uri)
if err != nil {
return err
}
return ChangeThreadBoardID(postID, boardID)
}
func getBoardIDFromURIOrNil(URI string) *int {
ID, err := getBoardIDFromURI(URI)
if err != nil {
@ -364,16 +395,14 @@ func GetEmbedsAllowed(boardID int) (allowed bool, err error) {
// AddBanAppeal adds a given appeal to a given ban
func AddBanAppeal(banID uint, message string) error {
// copy old to audit
const sql1 = `
/*copy old to audit*/
INSERT INTO DBPREFIXip_ban_appeals_audit (appeal_id, staff_id, appeal_text, staff_response, is_denied)
SELECT id, staff_id, appeal_text, staff_response, is_denied
FROM DBPREFIXip_ban_appeals
WHERE DBPREFIXip_ban_appeals.ip_ban_id = ?`
const sql2 = `
/*update old values to new values*/
UPDATE DBPREFIXip_ban_appeals SET appeal_text = ? WHERE ip_ban_id = ?
`
// update old values to new values
const sql2 = `UPDATE DBPREFIXip_ban_appeals SET appeal_text = ? WHERE ip_ban_id = ?`
_, err := ExecSQL(sql1, banID)
if err != nil {
return err

View file

@ -30,6 +30,7 @@ var (
ManageLogin *template.Template
ManageReports *template.Template
ManageStaff *template.Template
MoveThreadPage *template.Template
PageHeader *template.Template
PageFooter *template.Template
PostEdit *template.Template
@ -201,6 +202,12 @@ func templateLoading(t string, buildAll bool) error {
return templateError("manage_staff.html", err)
}
}
if buildAll || t == "movethreadpage" {
MoveThreadPage, err = loadTemplate("movethreadpage.html", "page_header.html", "page_footer.html")
if err != nil {
return templateError("movethreadpage.html", err)
}
}
if buildAll || t == "pageheader" {
PageHeader, err = loadTemplate("page_header.html")
if err != nil {

View file

@ -10,7 +10,7 @@
<a href="#footer">Scroll to bottom</a>
</div>
{{- template "postbox.html" . -}}<hr />
<form action="/util" method="POST" id="main-form">
<form action="{{.webroot}}util" method="POST" id="main-form">
{{$global := .}}
{{- range $t, $thread := .threads}}{{$op := $thread.OP}}
<div class="thread">
@ -28,8 +28,9 @@
<input type="hidden" name="board" value="{{.board.Dir}}" />
<input type="hidden" name="boardid" value="{{.board.ID}}" />
<label>[<input type="checkbox" name="fileonly"/>File only]</label> <input type="password" size="10" name="password" id="delete-password" /> <input type="submit" name="delete_btn" value="Delete" onclick="return confirm('Are you sure you want to delete these posts?')" /><br />
Reason: <input type="text" size="10" name="reason" id="reason" /> <input type="submit" name="report_btn" value="Report" /><br />
Edit post <input type="submit" name="edit_btn" value="Edit" />
Report reason: <input type="text" size="10" name="reason" id="reason" /> <input type="submit" name="report_btn" value="Report" /><br />
<input type="submit" name="edit_btn" value="Edit post" />&nbsp;
<input type="submit" name="move_btn" value="Move thread" />
</div>
</div>
</form>

View file

@ -0,0 +1,20 @@
{{template "page_header.html" .}}
<header>
<h1 id="board-title">Move thread</h1>
</header><hr />
<form action="{{.webroot}}util" method="POST" id="main-form">
<input name="srcboardid" type="hidden" value="{{.srcBoard.ID}}" />
<input name="postid" type="hidden" value="{{.postid}}" />
<input name="domove" type="hidden" value="1" />
<input type="hidden" name="srcboardid" value="{{.srcBoard.ID}}" />
<table>
<tr><td>Source board:</td><td>/{{.srcBoard.Dir}}/</td></tr>
<tr><td>Desination board:</td><td><select name="destboardid" id="">
{{range $b,$board := .destBoards}}<option value="{{$board.ID}}">/{{$board.Dir}}/ - {{$board.Title}}</option>{{end}}
</select></td></tr>
<tr><td>Password:</td><td><input type="password" name="postpassword"/></td></tr>
</table>
<input type="submit" value="Move thread" />
<input type="button" value="Cancel" onclick="window.location = '{{.webroot}}{{.srcBoard.Dir}}/'">
</form>
{{- template "page_footer.html" .}}

View file

@ -23,8 +23,9 @@
<input type="hidden" name="board" value="{{.board.Dir}}" />
<input type="hidden" name="boardid" value="{{.board.ID}}" />
<label>[<input type="checkbox" name="fileonly"/>File only]</label> <input type="password" size="10" name="password" id="delete-password" /> <input type="submit" name="delete_btn" value="Delete" onclick="return confirm('Are you sure you want to delete these posts?')" /><br />
Reason: <input type="text" size="10" name="reason" id="reason" /> <input type="submit" name="report_btn" value="Report" /><br />
Edit post <input type="submit" name="edit_btn" value="Edit" />
Report reason: <input type="text" size="10" name="reason" id="reason" /> <input type="submit" name="report_btn" value="Report" /><br />
<input type="submit" name="edit_btn" value="Edit post" />&nbsp;
<input type="submit" name="move_btn" value="Move thread" />
</div>
</div>
</form>