diff --git a/cmd/gochan/deleteposts.go b/cmd/gochan/deleteposts.go index f00bda95..5aad2733 100644 --- a/cmd/gochan/deleteposts.go +++ b/cmd/gochan/deleteposts.go @@ -118,12 +118,12 @@ func getAllPostsToDelete(postIDs []any, fileOnly bool) ([]delPost, []any, error) params := postIDs if fileOnly { // only deleting this post's file, not subfiles if it's an OP - query = "SELECT * FROM DBPREFIXv_posts_to_delete_file_only WHERE postid IN " + setPart + query = "SELECT post_id, thread_id, op_id, is_top_post, filename, dir FROM DBPREFIXv_posts_to_delete_file_only WHERE post_id IN " + setPart } else { // deleting everything, including subfiles params = append(params, postIDs...) - query = "SELECT * FROM DBPREFIXv_posts_to_delete WHERE postid IN " + setPart + - ` OR thread_id IN (SELECT thread_id from DBPREFIXposts op WHERE opid IN ` + setPart + ` AND is_top_post)` + query = "SELECT post_id, thread_id, op_id, is_top_post, filename, dir FROM DBPREFIXv_posts_to_delete WHERE post_id IN " + + setPart + " OR thread_id IN (SELECT thread_id from DBPREFIXposts op WHERE op_id IN " + setPart + " AND is_top_post)" } rows, err := gcsql.QuerySQL(query, params...) if err != nil { diff --git a/pkg/building/building.go b/pkg/building/building.go index 5475ce6b..178264b0 100644 --- a/pkg/building/building.go +++ b/pkg/building/building.go @@ -35,9 +35,9 @@ func getFrontPagePosts() ([]frontPagePost, error) { if siteCfg.RecentPostsWithNoFile { // get recent posts, including those with no file - query = "SELECT * FROM DBPREFIXv_front_page_posts" + query = "SELECT id, message_raw, dir, filename, op_id FROM DBPREFIXv_front_page_posts" } else { - query = "SELECT * FROM DBPREFIXv_front_page_posts_with_file" + query = "SELECT id, message_raw, dir, filename, op_id FROM DBPREFIXv_front_page_posts_with_file" } query += " ORDER BY id DESC LIMIT " + strconv.Itoa(siteCfg.MaxRecentPosts) diff --git a/pkg/building/catalog.go b/pkg/building/catalog.go index 6a7db0a4..e5001e72 100644 --- a/pkg/building/catalog.go +++ b/pkg/building/catalog.go @@ -63,7 +63,10 @@ func (catalog *boardCatalog) fillPages(threadsPerPage int, threads []catalogThre } func getBoardTopPosts(board string) ([]*Post, error) { - const query = "SELECT * FROM DBPREFIXv_building_posts WHERE id = parent_id AND dir = ?" + const query = `SELECT id, thread_id, ip, name, tripcode, email, subject, created_on, last_modified, parent_id, + last_bump, message, message_raw, board_id, dir, original_filename, filename, checksum, filesize, tw, th, + width, height, locked, stickied, cyclical, flag, country, is_deleted + FROM DBPREFIXv_building_posts WHERE id = parent_id AND dir = ?` var posts []*Post err := QueryPosts(query, []any{board}, func(p *Post) error { diff --git a/pkg/building/posts.go b/pkg/building/posts.go index 95038b2c..08a620f9 100644 --- a/pkg/building/posts.go +++ b/pkg/building/posts.go @@ -155,7 +155,10 @@ func QueryPosts(query string, params []any, cb func(*Post) error) error { } func GetBuildablePostsByIP(ip string, limit int) ([]*Post, error) { - query := "SELECT * FROM DBPREFIXv_building_posts WHERE ip = PARAM_ATON ORDER BY id DESC" + query := `SELECT id, thread_id, ip, name, tripcode, email, subject, created_on, last_modified, parent_id, + last_bump, message, message_raw, board_id, dir, original_filename, filename, checksum, filesize, tw, th, + width, height, locked, stickied, cyclical, flag, country, is_deleted + FROM DBPREFIXv_building_posts WHERE ip = PARAM_ATON ORDER BY id DESC` if limit > 0 { query += " LIMIT " + strconv.Itoa(limit) } @@ -169,7 +172,10 @@ func GetBuildablePostsByIP(ip string, limit int) ([]*Post, error) { } func getThreadPosts(thread *gcsql.Thread) ([]*Post, error) { - const query = "SELECT * FROM DBPREFIXv_building_posts WHERE thread_id = ? ORDER BY id ASC" + const query = `SELECT id, thread_id, ip, name, tripcode, email, subject, created_on, last_modified, parent_id, + last_bump, message, message_raw, board_id, dir, original_filename, filename, checksum, filesize, tw, th, + width, height, locked, stickied, cyclical, flag, country, is_deleted + FROM DBPREFIXv_building_posts WHERE thread_id = ? ORDER BY id ASC` var posts []*Post err := QueryPosts(query, []any{thread.ID}, func(p *Post) error { posts = append(posts, p) @@ -179,7 +185,10 @@ func getThreadPosts(thread *gcsql.Thread) ([]*Post, error) { } func GetRecentPosts(boardid int, limit int) ([]*Post, error) { - query := `SELECT * FROM DBPREFIXv_building_posts` + query := `SELECT id, thread_id, ip, name, tripcode, email, subject, created_on, last_modified, parent_id, + last_bump, message, message_raw, board_id, dir, original_filename, filename, checksum, filesize, tw, th, + width, height, locked, stickied, cyclical, flag, country, is_deleted + FROM DBPREFIXv_building_posts` var args []any if boardid > 0 { diff --git a/pkg/gcsql/posts.go b/pkg/gcsql/posts.go index 3fa1bc5d..2a1ebdd0 100644 --- a/pkg/gcsql/posts.go +++ b/pkg/gcsql/posts.go @@ -337,6 +337,16 @@ func (p *Post) UnlinkUploads(leaveDeletedBox bool) error { return err } +// InCyclicalThread returns true if the post is in a cyclical thread +func (p *Post) InCyclicalThread() (bool, error) { + var cyclical bool + err := QueryRowTimeoutSQL(nil, "SELECT cyclical FROM DBPREFIXthreads WHERE id = ?", []any{p.ThreadID}, []any{&cyclical}) + if errors.Is(err, sql.ErrNoRows) { + return false, ErrThreadDoesNotExist + } + return cyclical, err +} + // Delete sets the post as deleted and sets the deleted_at timestamp to the current time func (p *Post) Delete() error { if p.IsTopPost { @@ -413,6 +423,31 @@ func (p *Post) Insert(bumpThread bool, boardID int, locked bool, stickied bool, return tx.Commit() } +type cyclicalThreadPost struct { + PostID int // sql: post_id + +} + +// returns post IDs that should be deleted in a cyclical thread +// func (p *Post) postsToBeBumpedOff() ([]int, error) { +// ctx, cancel := context.WithTimeout(context.Background(), gcdb.defaultTimeout) +// defer cancel() +// tx, err := BeginContextTx(ctx) +// if err != nil { +// return nil, err +// } +// var cyclical bool +// if err = QueryRowContextSQL(ctx, tx, "SELECT cyclical FROM DBPREFIXthreads WHERE id = ?", []any{p.ThreadID}, []any{&cyclical}); err != nil { +// return nil, err +// } + +// if !cyclical { +// return nil, nil +// } + +// QueryContextSQL(ctx, tx, "SELECT post_id, thread_id, op_id, is_top_post, filename, dir FROM DBPREFIXv_posts_to_delete WHERE thread_id = ?") +// } + func (p *Post) WebPath() string { if p.opID > 0 && p.boardDir != "" { return config.WebPath(p.boardDir, "res/", strconv.Itoa(p.opID)+".html#"+strconv.Itoa(p.ID)) diff --git a/pkg/manage/actionsAdminPerm.go b/pkg/manage/actionsAdminPerm.go index 121380df..d2d0672c 100644 --- a/pkg/manage/actionsAdminPerm.go +++ b/pkg/manage/actionsAdminPerm.go @@ -356,7 +356,8 @@ func fixThumbnailsCallback(_ http.ResponseWriter, request *http.Request, _ *gcsq board := request.FormValue("board") var uploads []uploadInfo if board != "" { - const query = "SELECT * FROM DBPREFIXv_upload_info WHERE dir = ? ORDER BY created_on DESC" + const query = `SELECT id, op, filename, is_spoilered, width, height, thumbnail_width, thumbnail_height + FROM DBPREFIXv_upload_info WHERE dir = ? ORDER BY created_on DESC` rows, err := gcsql.QuerySQL(query, board) if err != nil { return "", err diff --git a/pkg/posting/post.go b/pkg/posting/post.go index 6d4d2dc8..6c21e674 100644 --- a/pkg/posting/post.go +++ b/pkg/posting/post.go @@ -335,6 +335,7 @@ func MakePost(writer http.ResponseWriter, request *http.Request) { isCyclical := request.PostFormValue("cyclical") == "on" if isCyclical && boardConfig.CyclicalThreadNumPosts == 0 { + writer.WriteHeader(http.StatusBadRequest) server.ServeError(writer, "Board does not support cyclical threads", wantsJSON, nil) return } @@ -460,17 +461,43 @@ func MakePost(writer http.ResponseWriter, request *http.Request) { if err = config.TakeOwnership(filePath); err != nil { errEv.Err(err).Caller(). Str("file", filePath).Send() + os.Remove(filePath) + os.Remove(thumbPath) + os.Remove(catalogThumbPath) + post.Delete() + server.ServeError(writer, err.Error(), wantsJSON, nil) } if err = config.TakeOwnership(thumbPath); err != nil { errEv.Err(err).Caller(). Str("thumbnail", thumbPath).Send() + os.Remove(filePath) + os.Remove(thumbPath) + os.Remove(catalogThumbPath) + post.Delete() + server.ServeError(writer, err.Error(), wantsJSON, nil) } if err = config.TakeOwnership(catalogThumbPath); err != nil && !os.IsNotExist(err) { errEv.Err(err).Caller(). Str("catalogThumbnail", catalogThumbPath).Send() + os.Remove(filePath) + os.Remove(thumbPath) + os.Remove(catalogThumbPath) + post.Delete() + server.ServeError(writer, err.Error(), wantsJSON, nil) } } + inCyclicalThread, err := post.InCyclicalThread() + if err != nil { + errEv.Err(err).Caller().Send() + server.ServeError(writer, "Unable to get thread info", wantsJSON, nil) + return + } + if inCyclicalThread { + // post is a cyclical thread + errEv.Bool("cyclical", inCyclicalThread) + } + // rebuild the board page if err = building.BuildBoards(false, board.ID); err != nil { server.ServeError(writer, "Unable to build boards", wantsJSON, nil) diff --git a/sql/reset_views.sql b/sql/reset_views.sql index 6495cf3b..31db9922 100644 --- a/sql/reset_views.sql +++ b/sql/reset_views.sql @@ -8,6 +8,7 @@ DROP VIEW IF EXISTS DBPREFIXv_front_page_posts_with_file; DROP VIEW IF EXISTS DBPREFIXv_front_page_posts; DROP VIEW IF EXISTS DBPREFIXv_posts_to_delete_file_only; DROP VIEW IF EXISTS DBPREFIXv_posts_to_delete; +DROP VIEW IF EXISTS DBPREFIXv_posts_cyclical_check; DROP VIEW IF EXISTS DBPREFIXv_recent_posts; DROP VIEW IF EXISTS DBPREFIXv_building_posts; DROP VIEW IF EXISTS DBPREFIXv_top_post_thread_ids; @@ -42,10 +43,10 @@ INNER JOIN DBPREFIXv_top_post_thread_ids op ON op.thread_id = p.thread_id WHERE p.is_deleted = FALSE; CREATE VIEW DBPREFIXv_posts_to_delete AS -SELECT p.id AS postid, thread_id, ( - SELECT op.id AS opid FROM DBPREFIXposts op +SELECT p.id AS post_id, thread_id, ( + SELECT op.id AS op_id FROM DBPREFIXposts op WHERE op.thread_id = p.thread_id AND is_top_post LIMIT 1 -) as opid, is_top_post, COALESCE(filename, '') AS filename, dir +) as op_id, is_top_post, COALESCE(filename, '') AS filename, dir FROM DBPREFIXboards b LEFT JOIN DBPREFIXthreads t ON t.board_id = b.id LEFT JOIN DBPREFIXposts p ON p.thread_id = t.id @@ -55,10 +56,17 @@ CREATE VIEW DBPREFIXv_posts_to_delete_file_only AS SELECT * FROM DBPREFIXv_posts_to_delete WHERE filename IS NOT NULL; +CREATE VIEW DBPREFIXv_posts_cyclical_check AS +SELECT post_id, d.thread_id, op_id, d.is_top_post, filename, dir +FROM DBPREFIXv_posts_to_delete d +INNER JOIN DBPREFIXposts p ON p.id = post_id +INNER JOIN DBPREFIXthreads t ON d.thread_id = t.id +WHERE p.is_deleted = 0 AND d.is_top_post = 0 and t.cyclical = 1; + CREATE VIEW DBPREFIXv_front_page_posts AS SELECT DBPREFIXposts.id, DBPREFIXposts.message_raw, (SELECT dir FROM DBPREFIXboards WHERE id = t.board_id) as dir, -COALESCE(f.filename, '') as filename, op.id as opid +COALESCE(f.filename, '') as filename, op.id as op_id FROM DBPREFIXposts LEFT JOIN DBPREFIXv_thread_board_ids t ON t.id = DBPREFIXposts.thread_id LEFT JOIN (SELECT post_id, filename FROM DBPREFIXfiles) f on f.post_id = DBPREFIXposts.id