package building import ( "fmt" "os" "path" "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/server/serverutil" ) type catalogThreadData struct { *Post Replies int `json:"replies"` Images int `json:"images"` OmittedPosts int `json:"omitted_posts"` // posts in the thread but not shown on the board page OmittedImages int `json:"omitted_images"` // uploads in the thread but not shown on the board page Stickied int `json:"sticky"` Locked int `json:"closed"` Posts []*Post `json:"-"` uploads []gcsql.Upload } type catalogPage struct { PageNum int `json:"page"` Threads []catalogThreadData `json:"threads"` } type boardCatalog struct { pages []catalogPage // this array gets marshalled, not the boardCatalog object numPages int currentPage int } // fillPages fills the catalog's pages array with pages of the specified size, with the remainder // on the last page func (catalog *boardCatalog) fillPages(threadsPerPage int, threads []catalogThreadData) { catalog.pages = []catalogPage{} // clear the array if it isn't already catalog.numPages = len(threads) / threadsPerPage remainder := len(threads) % threadsPerPage currentThreadIndex := 0 var i int for i = 0; i < catalog.numPages; i++ { catalog.pages = append(catalog.pages, catalogPage{ PageNum: i + 1, Threads: threads[currentThreadIndex : currentThreadIndex+threadsPerPage], }, ) currentThreadIndex += threadsPerPage } if remainder > 0 { catalog.pages = append(catalog.pages, catalogPage{ PageNum: i + 1, Threads: threads[len(threads)-remainder:], }, ) } } func getBoardTopPosts(board string) ([]*Post, error) { 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 { posts = append(posts, p) return nil }) return posts, err } // BuildCatalog builds the catalog for a board with a given id func BuildCatalog(boardID int) error { errEv := gcutil.LogError(nil). Str("building", "catalog"). Int("boardID", boardID) err := gctemplates.InitTemplates(gctemplates.Catalog) if err != nil { errEv.Err(err).Send() return err } board, err := gcsql.GetBoardFromID(boardID) if err != nil { errEv.Err(err).Caller().Msg("Unable to get board information") return err } errEv.Str("boardDir", board.Dir) criticalCfg := config.GetSystemCriticalConfig() catalogPath := path.Join(criticalCfg.DocumentRoot, board.Dir, "catalog.html") catalogFile, err := os.OpenFile(catalogPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, config.NormalFileMode) if err != nil { errEv.Err(err).Caller().Send() return fmt.Errorf("failed opening /%s/catalog.html: %w", board.Dir, err) } if err = config.TakeOwnershipOfFile(catalogFile); err != nil { errEv.Err(err).Caller().Send() return fmt.Errorf("failed taking ownership of /%s/catalog.html", board.Dir) } threadOPs, err := getBoardTopPosts(board.Dir) if err != nil { errEv.Err(err).Caller().Send() return fmt.Errorf("failed building catalog for /%s/", board.Dir) } boardConfig := config.GetBoardConfig(board.Dir) if err = serverutil.MinifyTemplate(gctemplates.Catalog, map[string]any{ "boards": gcsql.AllBoards, "board": board, "boardConfig": boardConfig, "sections": gcsql.AllSections, "threads": threadOPs, }, catalogFile, "text/html"); err != nil { errEv.Err(err).Caller().Send() return fmt.Errorf("failed building catalog for /%s/", board.Dir) } return catalogFile.Close() }