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:
parent
8b29c4b0e0
commit
820f5b993b
9 changed files with 169 additions and 78 deletions
12
README.md
12
README.md
|
@ -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.
|
|
@ -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
|
||||
}
|
||||
|
|
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
|
@ -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": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gochan.js",
|
||||
"version": "3.3.0",
|
||||
"version": "3.4.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"source": "js/gochan.js",
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 {
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
3.3.0
|
||||
3.4.0
|
Loading…
Add table
Add a link
Reference in a new issue