1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-01 22:26:24 -07:00

Fix thread upload deletion

This commit is contained in:
Eggbertx 2022-12-24 20:53:56 -08:00
parent 8b29c4b0e0
commit 820f5b993b
9 changed files with 169 additions and 78 deletions

View file

@ -46,6 +46,7 @@ See [`frontend/README.md`](frontend/README.md) for information on working with S
## Style guide
* For Go source, follow the standard Go [style guide](https://github.com/golang/go/wiki/CodeReviewComments).
* variables and functions exposed to Go templates should be in camelCase, like Go variables
* All exported functions and variables should have a documentation comment explaining their functionality, as per go style guides.
* Unexported functions are preferred to have a documentation comment explaining it, unless it is sufficiently self explanatory or simple.
* Git commits should be descriptive. Put further explanation in the comment of the commit.
@ -55,18 +56,19 @@ See [`frontend/README.md`](frontend/README.md) for information on working with S
## Roadmap
### Near future
All features that are to be realised for the near future are found in the issues tab with the milestone "Next Release"
* Make the plugin support actually useful
* Improve moderation tools, using path routing instead of the action variable
* Add support for sticky and locked threads
* Add support for more filetypes (zip,)
* Improve API support for existing chan browing phone apps
### Lower priority
* Improve moderation tools heavily
* Better image fingerpringing and banning system (as opposed to a hash)
* Rework any legacy structs that uses comma separated fields to use a slice instead.
* Readd sqlite support
* RSS feeds from boards/specific threads/specific usernames+tripcodes (such as newsanon)
* Pinning a post within a thread even if its not the OP, to prevent its deletion in a cyclical thread.
### Later down the line
* Improve API support for existing chan browing phone apps
* Better image fingerpringing and banning system (as opposed to a hash)
### Possible experimental features:
* Allow users to be mini-moderators within threads they posted themselves, to prevent spammers/derailers.

View file

@ -2,7 +2,9 @@ package main
import (
"database/sql"
"errors"
"fmt"
"io/fs"
"net/http"
"os"
"path"
@ -14,8 +16,15 @@ import (
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/manage"
"github.com/gochan-org/gochan/pkg/serverutil"
"github.com/rs/zerolog"
)
type upload struct {
postID int
filename string
boardDir string
}
func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.Request) {
// Delete a post or thread
errEv := gcutil.LogError(nil).
@ -85,61 +94,9 @@ func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.R
}
if fileOnly {
upload, err := post.GetUpload()
if err != nil {
errEv.Err(err).Caller().
Int("postid", post.ID).
Msg("Unable to get file upload info")
serverutil.ServeError(writer, "Error getting file uplaod info: "+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
if deletePostUpload(post, board, writer, request, errEv) {
return
}
documentRoot := config.GetSystemCriticalConfig().DocumentRoot
if upload != nil && upload.Filename != "deleted" {
filePath := path.Join(documentRoot, board.Dir, "src", upload.Filename)
if err = os.Remove(filePath); err != nil {
errEv.Err(err).Caller().
Int("postid", post.ID).
Str("filename", upload.Filename).
Msg("Unable to delete file")
serverutil.ServeError(writer, "Unable to delete file: "+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return
}
// delete the file's thumbnail
thumbPath := path.Join(documentRoot, board.Dir, "thumb", upload.ThumbnailPath("thumb"))
if err = os.Remove(thumbPath); err != nil {
errEv.Err(err).Caller().
Int("postid", post.ID).
Str("thumbnail", upload.ThumbnailPath("thumb")).
Msg("Unable to delete thumbnail")
serverutil.ServeError(writer, "Unable to delete thumbnail: "+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return
}
// delete the catalog thumbnail
if post.IsTopPost {
thumbPath := path.Join(documentRoot, board.Dir, "thumb", upload.ThumbnailPath("catalog"))
if err = os.Remove(thumbPath); err != nil {
errEv.Err(err).Caller().
Int("postid", post.ID).
Str("catalogThumb", upload.ThumbnailPath("catalog")).
Msg("Unable to delete catalog thumbnail")
serverutil.ServeError(writer, "Unable to delete catalog thumbnail: "+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return
}
}
if err = post.UnlinkUploads(true); err != nil {
errEv.Err(err).Caller().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Msg("Error unlinking post uploads")
serverutil.ServeError(writer, "Unable to unlink post uploads"+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return
}
}
if err = building.BuildBoardPages(board); err != nil {
errEv.Err(err).Caller().Send()
serverutil.ServeError(writer, "Unable to build board pages for /"+board.Dir+"/: "+err.Error(), wantsJSON, map[string]interface{}{
@ -172,6 +129,53 @@ func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.R
return
}
} else {
if post.IsTopPost {
rows, err := gcsql.QuerySQL(
`SELECT filename FROM DBPREFIXfiles
LEFT JOIN (
SELECT id FROM DBPREFIXposts WHERE thread_id = ?
) p
ON p.id = post_id
WHERE post_id = p.id AND filename != 'deleted'`,
post.ThreadID)
if err != nil {
errEv.Err(err).Caller().
Str("requestType", "deleteThread").
Int("postid", post.ID).
Int("threadID", post.ThreadID).
Msg("Unable to get list of filenames in thread")
serverutil.ServeError(writer, "Unable to get list of filenames in thread", wantsJSON, map[string]interface{}{
"postid": post.ID,
})
return
}
defer rows.Close()
var uploads []upload
for rows.Next() {
var filename string
if err = rows.Scan(&filename); err != nil {
errEv.Err(err).Caller().
Str("requestType", "deleteThread").
Int("postid", post.ID).
Int("threadID", post.ThreadID).
Msg("Unable to get list of filenames in thread")
serverutil.ServeError(writer, "Unable to get list of filenames in thread", wantsJSON, map[string]interface{}{
"postid": post.ID,
})
return
}
uploads = append(uploads, upload{
filename: filename,
boardDir: board.Dir,
})
}
// done as a goroutine to avoid delays if the thread has a lot of files
// the downside is of course that if something goes wrong, deletion errors
// won't be seen in the browser
go deleteUploads(uploads)
} else if deletePostUpload(post, board, writer, request, errEv) {
return
}
// delete the post
if err = post.Delete(); err != nil {
errEv.Err(err).Caller().
@ -189,8 +193,6 @@ func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.R
os.Remove(threadIndexPath + ".json")
} else {
building.BuildBoardPages(board)
// _board, _ := gcsql.GetBoardFromID(post.BoardID)
// building.BuildBoardPages(&_board)
}
building.BuildBoards(false, boardid)
}
@ -218,3 +220,90 @@ func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.R
}
}
}
func deleteUploads(uploads []upload) {
documentRoot := config.GetSystemCriticalConfig().DocumentRoot
var filePath, thumbPath, catalogThumbPath string
var err error
for _, upload := range uploads {
filePath = path.Join(documentRoot, upload.boardDir, "src", upload.filename)
if err = os.Remove(filePath); err != nil && !errors.Is(err, os.ErrNotExist) {
gcutil.LogError(err).Caller().
Str("filePath", filePath).
Int("postid", upload.postID).Send()
}
thumbPath = path.Join(documentRoot, upload.boardDir, "thumb", gcutil.GetThumbnailPath("reply", upload.filename))
if err = os.Remove(thumbPath); err != nil && !errors.Is(err, os.ErrNotExist) {
gcutil.LogError(err).Caller().
Str("thumbPath", thumbPath).
Int("postid", upload.postID).Send()
}
catalogThumbPath = path.Join(documentRoot, upload.boardDir, "thumb", gcutil.GetThumbnailPath("catalog", upload.filename))
if err = os.Remove(catalogThumbPath); err != nil && !errors.Is(err, os.ErrNotExist) {
gcutil.LogError(err).Caller().
Str("catalogThumbPath", catalogThumbPath).
Int("postid", upload.postID).Send()
}
}
}
func deletePostUpload(post *gcsql.Post, board *gcsql.Board, writer http.ResponseWriter, request *http.Request, errEv *zerolog.Event) bool {
documentRoot := config.GetSystemCriticalConfig().DocumentRoot
upload, err := post.GetUpload()
wantsJSON := serverutil.IsRequestingJSON(request)
if err != nil {
errEv.Err(err).Caller().
Int("postid", post.ID).
Msg("Unable to get file upload info")
serverutil.ServeError(writer, "Error getting file uplaod info: "+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return true
}
if upload != nil && upload.Filename != "deleted" {
filePath := path.Join(documentRoot, board.Dir, "src", upload.Filename)
if err = os.Remove(filePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
errEv.Err(err).Caller().
Int("postid", post.ID).
Str("filename", upload.Filename).
Msg("Unable to delete file")
serverutil.ServeError(writer, "Unable to delete file: "+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return true
}
// delete the file's thumbnail
thumbPath := path.Join(documentRoot, board.Dir, "thumb", upload.ThumbnailPath("thumb"))
if err = os.Remove(thumbPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
errEv.Err(err).Caller().
Int("postid", post.ID).
Str("thumbnail", upload.ThumbnailPath("thumb")).
Msg("Unable to delete thumbnail")
serverutil.ServeError(writer, "Unable to delete thumbnail: "+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return true
}
// delete the catalog thumbnail
if post.IsTopPost {
thumbPath := path.Join(documentRoot, board.Dir, "thumb", upload.ThumbnailPath("catalog"))
if err = os.Remove(thumbPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
errEv.Err(err).Caller().
Int("postid", post.ID).
Str("catalogThumb", upload.ThumbnailPath("catalog")).
Msg("Unable to delete catalog thumbnail")
serverutil.ServeError(writer, "Unable to delete catalog thumbnail: "+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return true
}
}
// remove the upload from the database
if err = post.UnlinkUploads(true); err != nil {
errEv.Err(err).Caller().
Str("requestType", "deleteFile").
Int("postid", post.ID).
Msg("Error unlinking post uploads")
serverutil.ServeError(writer, "Unable to unlink post uploads"+err.Error(),
wantsJSON, map[string]interface{}{"postid": post.ID})
return true
}
}
return false
}

View file

@ -1,12 +1,12 @@
{
"name": "gochan.js",
"version": "3.3.0",
"version": "3.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "gochan.js",
"version": "3.3.0",
"version": "3.4.0",
"license": "BSD-2-Clause",
"dependencies": {
"jquery": "^3.5.1",
@ -4922,9 +4922,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001367",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001367.tgz",
"integrity": "sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==",
"version": "1.0.30001441",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz",
"integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==",
"dev": true,
"funding": [
{
@ -13729,9 +13729,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001367",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001367.tgz",
"integrity": "sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==",
"version": "1.0.30001441",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz",
"integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==",
"dev": true
},
"chalk": {

View file

@ -1,6 +1,6 @@
{
"name": "gochan.js",
"version": "3.3.0",
"version": "3.4.0",
"description": "",
"type": "module",
"source": "js/gochan.js",

View file

@ -7,6 +7,6 @@
<h1>404: File not found</h1>
<img src="./lol 404.gif" border="0" alt="">
<p>The requested file could not be found on this server.</p>
<hr/>Site powered by <a href="https://github.com/gochan-org/gochan" target="_blank">Gochan</a> v3.3.0
<hr/>Site powered by <a href="https://github.com/gochan-org/gochan" target="_blank">Gochan</a> v3.4.0
</body>
</html>

View file

@ -7,6 +7,6 @@
<h1>Error 500: Internal Server error</h1>
<img src="./server500.gif" border="0" alt="">
<p>The server encountered an error while trying to serve the page, and we apologize for the inconvenience. The <a href="https://en.wikipedia.org/wiki/Idiot">system administrator</a> will try to fix things as soon they get around to it, whenever that is. Hopefully soon.</p>
<hr/>Site powered by <a href="https://github.com/gochan-org/gochan" target="_blank">Gochan</a> v3.3.0
<hr/>Site powered by <a href="https://github.com/gochan-org/gochan" target="_blank">Gochan</a> v3.4.0
</body>
</html>

View file

@ -7,6 +7,6 @@
<h1>Error 502: Bad gateway</h1>
<img src="./server500.gif" border="0" alt="">
<p>The server encountered an error while trying to serve the page, and we apologize for the inconvenience. The <a href="https://en.wikipedia.org/wiki/Idiot">system administrator</a> will try to fix things as soon they get around to it, whenever that is. Hopefully soon.</p>
<hr/>Site powered by <a href="https://github.com/gochan-org/gochan" target="_blank">Gochan</a> v3.3.0
<hr/>Site powered by <a href="https://github.com/gochan-org/gochan" target="_blank">Gochan</a> v3.4.0
</body>
</html>

View file

@ -102,7 +102,7 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
infoEv.Str("post", "withVideo").
Str("filename", handler.Filename).
Str("referer", request.Referer()).Send()
if post.IsTopPost {
if post.ThreadID == 0 {
if err := createVideoThumbnail(filePath, thumbPath, boardConfig.ThumbWidth); err != nil {
errEv.Err(err).Caller().
Str("filePath", filePath).
@ -158,7 +158,7 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
}
}
thumbType := "reply"
if post.IsTopPost {
if post.ThreadID == 0 {
thumbType = "op"
}
upload.ThumbnailWidth, upload.ThumbnailHeight = getThumbnailSize(
@ -188,7 +188,7 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
upload.Width = img.Bounds().Max.X
upload.Height = img.Bounds().Max.Y
thumbType := "reply"
if post.IsTopPost {
if post.ThreadID == 0 {
thumbType = "op"
}
upload.ThumbnailWidth, upload.ThumbnailHeight = getThumbnailSize(
@ -219,7 +219,7 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
if shouldThumb {
var thumbnail image.Image
var catalogThumbnail image.Image
if post.IsTopPost {
if post.ThreadID == 0 {
// If this is a new thread, generate thumbnail and catalog thumbnail
thumbnail = createImageThumbnail(img, postBoard.Dir, "op")
catalogThumbnail = createImageThumbnail(img, postBoard.Dir, "catalog")
@ -251,7 +251,7 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
serverutil.ServeErrorPage(writer, "Couldn't create thumbnail: "+err.Error())
return nil, true
}
if post.IsTopPost {
if post.ThreadID == 0 {
// Generate catalog thumbnail
catalogThumbnail := createImageThumbnail(img, postBoard.Dir, "catalog")
if err = imaging.Save(catalogThumbnail, catalogThumbPath); err != nil {

View file

@ -1 +1 @@
3.3.0
3.4.0