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:
parent
6c4b5a0155
commit
2b12801c60
9 changed files with 614 additions and 279 deletions
193
cmd/gochan/deleteposts.go
Normal file
193
cmd/gochan/deleteposts.go
Normal 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
132
cmd/gochan/editpost.go
Normal 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
205
cmd/gochan/movethread.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" />
|
||||
<input type="submit" name="move_btn" value="Move thread" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
20
templates/movethreadpage.html
Normal file
20
templates/movethreadpage.html
Normal 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" .}}
|
|
@ -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" />
|
||||
<input type="submit" name="move_btn" value="Move thread" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue