package gcsql import ( "database/sql" "errors" "html/template" "strconv" ) var abstractSelectPosts = ` /* Left join singlefiles on recentposts where recentposts.selfid = singlefiles.post_id Coalesce filenames to "" (if filename = null -> "" else filename) */ Select recentposts.selfid AS id, recentposts.toppostid AS parentid, recentposts.boardid, recentposts.name, recentposts.tripcode, recentposts.email, recentposts.subject, recentposts.message, recentposts.message_raw, recentposts.password, COALESCE(singlefiles.filename, '') as filename, COALESCE(singlefiles.original_filename, '') as original_filename, COALESCE(singlefiles.checksum, ''), COALESCE(singlefiles.file_size, 0), COALESCE(singlefiles.width, 0), COALESCE(singlefiles.height, 0), COALESCE(singlefiles.thumbnail_width, 0), COALESCE(singlefiles.thumbnail_height, 0), recentposts.ip, recentposts.created_on, recentposts.anchored, recentposts.last_bump, recentposts.stickied, recentposts.locked FROM (SELECT posts.id AS selfid, COALESCE(NULLIF(topposts.id, posts.id), 0) AS toppostid, boards.id AS boardid, posts.name, posts.ip, posts.tripcode, posts.message, posts.email, posts.subject, posts.message_raw, posts.password, posts.created_on, posts.is_top_post, threads.anchored, threads.last_bump, threads.stickied, threads.locked FROM DBPREFIXposts AS posts JOIN DBPREFIXthreads AS threads ON threads.id = posts.thread_id JOIN DBPREFIXposts AS topposts ON threads.id = topposts.thread_id JOIN DBPREFIXboards AS boards ON threads.board_id = boards.id WHERE topposts.is_top_post = TRUE AND posts.is_deleted = FALSE ) as recentposts LEFT JOIN (SELECT files.* FROM DBPREFIXfiles as files JOIN (SELECT post_id, min(file_order) as file_order FROM DBPREFIXfiles GROUP BY post_id) as topfiles ON files.post_id = topfiles.post_id AND files.file_order = topfiles.file_order ) AS singlefiles ON recentposts.selfid = singlefiles.post_id` // getPostsExcecution excecutes a given variation on abstractSelectPosts with parameters and loads the result into an array of posts func getPostsExcecution(sql string, arguments ...interface{}) ([]Post, error) { rows, err := QuerySQL(sql, arguments...) if err != nil { return nil, err } var posts []Post for rows.Next() { post := new(Post) var messageHTML string if err = rows.Scan(&post.ID, &post.ParentID, &post.BoardID, &post.Name, &post.Tripcode, &post.Email, &post.Subject, &messageHTML, &post.MessageText, &post.Password, &post.Filename, &post.FilenameOriginal, &post.FileChecksum, &post.Filesize, &post.ImageW, &post.ImageH, &post.ThumbW, &post.ThumbH, &post.IP, &post.Timestamp, &post.Autosage, &post.Bumped, &post.Stickied, &post.Locked, ); err != nil { return nil, err } post.MessageHTML = template.HTML(messageHTML) post.FileExt = "placeholder" posts = append(posts, *post) } return posts, nil } var onlyTopPosts = abstractSelectPosts + "\nWHERE recentposts.is_top_post AND recentposts.boardid = ?" var sortedTopPosts = onlyTopPosts + "\nORDER BY recentposts.last_bump DESC" // GetTopPostsNoSort gets the thread ops for a given board. // Results are unsorted // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design func GetTopPostsNoSort(boardID int) (posts []Post, err error) { return getPostsExcecution(onlyTopPosts, boardID) } // GetTopPosts gets the thread ops for a given board. // newestFirst sorts the ops by the newest first if true, by newest last if false // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design func GetTopPosts(boardID int) (posts []Post, err error) { return getPostsExcecution(sortedTopPosts, boardID) } var repliesToX = abstractSelectPosts + "\nWHERE recentposts.toppostid = ?" var oldestRepliesFirst = repliesToX + "\nORDER BY recentposts.created_on ASC" var newestFirstLimited = repliesToX + "\nORDER BY recentposts.created_on DESC\nLIMIT ?" // GetExistingReplies gets all the reply posts to a given thread, ordered by oldest first. // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design func GetExistingReplies(topPost int) (posts []Post, err error) { return getPostsExcecution(oldestRepliesFirst, topPost) } // GetExistingRepliesLimitedRev gets N amount of reply posts to a given thread, ordered by newest first. // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design func GetExistingRepliesLimitedRev(topPost, limit int) (posts []Post, err error) { return getPostsExcecution(newestFirstLimited, topPost, limit) } //Toppost: where a post with a given id has this as their top post // GetSpecificTopPost gets the information for the top post for a given id. // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design func GetSpecificTopPost(ID int) (Post, error) { const topPostIDQuery = `SELECT posts.id from DBPREFIXposts as posts JOIN ( SELECT threads.id from DBPREFIXthreads as threads JOIN DBPREFIXposts as posts ON posts.thread_id = threads.id WHERE posts.id = ? ) as thread ON posts.thread_id = thread.id WHERE posts.is_top_post` //get top post of item with given id var FoundID int err := QueryRowSQL(topPostIDQuery, interfaceSlice(ID), interfaceSlice(&FoundID)) if err != nil { return Post{}, err } return GetSpecificPost(FoundID, false) } // GetSpecificPostByString gets a specific post for a given string id. // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design func GetSpecificPostByString(ID string) (Post, error) { return getSpecificPostStringDecorated(ID, false) } // GetSpecificPost gets a specific post for a given id. // returns SQL.ErrNoRows if no post could be found // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design func GetSpecificPost(ID int, onlyNotDeleted bool) (Post, error) { return getSpecificPostStringDecorated(strconv.Itoa(ID), onlyNotDeleted) } var specificPostSQL = abstractSelectPosts + "\nWHERE recentposts.selfid = ?" var specificPostSQLNotDeleted = specificPostSQL + "\nWHERE recentposts.is_deleted = FALSE" func getSpecificPostStringDecorated(ID string, onlyNotDeleted bool) (Post, error) { var sql string if onlyNotDeleted { sql = specificPostSQL } else { sql = specificPostSQLNotDeleted } posts, err := getPostsExcecution(sql, ID) if err != nil { return Post{}, err } if len(posts) == 0 { return Post{}, errors.New("Could not find a post with the ID: " + ID) } return posts[0], nil } // getRecentPostsInternal returns the most recent N posts, on a specific board if specified, only with files if specified // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design // TODO: rework so it uses all features/better sql // get recent posts func getRecentPostsInternal(amount int, onlyWithFile bool, boardID int, onSpecificBoard bool) ([]RecentPost, error) { // recentposts = join all non-deleted posts with the post id of their thread and the board it belongs on, sort by date and grab top x posts // singlefiles = the top file per post id // Left join singlefiles on recentposts where recentposts.selfid = singlefiles.post_id // Coalesce filenames to "" (if filename = null -> "" else filename) // Query might benefit from [filter on posts with at least one file -> ] filter N most recent -> manually loop N results for file/board/parentthreadid recentQueryStr := `SELECT recentposts.selfid AS id, recentposts.toppostid AS parentid, recentposts.boardname, recentposts.boardid, recentposts.ip, recentposts.name, recentposts.tripcode, recentposts.message, COALESCE(singlefiles.filename, '') as filename, COALESCE(singlefiles.thumbnail_width, 0) as thumb_w, COALESCE(singlefiles.thumbnail_height, 0) as thumb_h FROM (SELECT posts.id AS selfid, topposts.id AS toppostid, boards.dir AS boardname, boards.id AS boardid, posts.ip, posts.name, posts.tripcode, posts.message, posts.email, posts.created_on FROM DBPREFIXposts AS posts JOIN DBPREFIXthreads AS threads ON threads.id = posts.thread_id JOIN DBPREFIXposts AS topposts ON threads.id = topposts.thread_id JOIN DBPREFIXboards AS boards ON threads.board_id = boards.id WHERE topposts.is_top_post = TRUE AND posts.is_deleted = FALSE ) as recentposts LEFT JOIN (SELECT files.post_id, filename, files.thumbnail_width, files.thumbnail_height FROM DBPREFIXfiles as files JOIN (SELECT post_id, min(file_order) as file_order FROM DBPREFIXfiles GROUP BY post_id) as topfiles ON files.post_id = topfiles.post_id AND files.file_order = topfiles.file_order ) AS singlefiles ON recentposts.selfid = singlefiles.post_id` var rows *sql.Rows var err error if onlyWithFile && onSpecificBoard { recentQueryStr += "\n" + `WHERE singlefiles.filename IS NOT NULL AND recentposts.boardid = ? ORDER BY recentposts.created_on DESC LIMIT ?` rows, err = QuerySQL(recentQueryStr, boardID, amount) } if onlyWithFile && !onSpecificBoard { recentQueryStr += "\n" + `WHERE singlefiles.filename IS NOT NULL ORDER BY recentposts.created_on DESC LIMIT ?` rows, err = QuerySQL(recentQueryStr, amount) } if !onlyWithFile && onSpecificBoard { recentQueryStr += "\n" + `WHERE recentposts.boardid = ? ORDER BY recentposts.created_on DESC LIMIT ?` rows, err = QuerySQL(recentQueryStr, boardID, amount) } if !onlyWithFile && !onSpecificBoard { recentQueryStr += "\nORDER BY recentposts.created_on DESC LIMIT ?" rows, err = QuerySQL(recentQueryStr, amount) } if err != nil { return nil, err } defer rows.Close() var recentPostsArr []RecentPost for rows.Next() { recentPost := new(RecentPost) var formattedHTML template.HTML if err = rows.Scan( &recentPost.PostID, &recentPost.ParentID, &recentPost.BoardName, &recentPost.BoardID, &recentPost.IP, &recentPost.Name, &recentPost.Tripcode, &formattedHTML, &recentPost.Filename, &recentPost.ThumbW, &recentPost.ThumbH, ); err != nil { return nil, err } recentPost.Message = formattedHTML recentPostsArr = append(recentPostsArr, *recentPost) } return recentPostsArr, nil } // GetRecentPostsGlobal returns the global N most recent posts from the database. // Deprecated: This method was created to support old functionality during the database refactor of april 2020 // The code should be changed to reflect the new database design func GetRecentPostsGlobal(amount int, onlyWithFile bool) ([]RecentPost, error) { return getRecentPostsInternal(amount, onlyWithFile, 0, false) }