mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-10 10:47:00 -07:00
608 lines
18 KiB
Go
608 lines
18 KiB
Go
package building
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"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/posting/uploads"
|
|
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
const (
|
|
dirIsAFileStr = `unable to create %q, path exists and is a file`
|
|
genericErrStr = `unable to create %q: %w`
|
|
pathExistsStr = `unable to create %q, path already exists`
|
|
)
|
|
|
|
var (
|
|
ErrNoBoardDir = errors.New("board must have a directory before it is built")
|
|
ErrNoBoardTitle = errors.New("board must have a title before it is built")
|
|
)
|
|
|
|
type boardJSON struct {
|
|
*gcsql.Board
|
|
Cooldowns config.BoardCooldowns `json:"cooldowns"`
|
|
}
|
|
|
|
func boolToInt(b bool) int {
|
|
if b {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// BuildBoardPages builds the front pages for the given board, and returns any error it encountered.
|
|
func BuildBoardPages(board *gcsql.Board, errEv *zerolog.Event) error {
|
|
if errEv == nil {
|
|
errEv = gcutil.LogError(nil).
|
|
Int("boardID", board.ID).
|
|
Str("boardDir", board.Dir)
|
|
defer errEv.Discard()
|
|
}
|
|
err := gctemplates.InitTemplates(gctemplates.BoardPage)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Msg("unable to initialize boardpage template")
|
|
return err
|
|
}
|
|
var currentPageFile *os.File
|
|
var catalog boardCatalog
|
|
var catalogThreads []catalogThreadData
|
|
|
|
threads, err := board.GetThreads(true, true, true)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().
|
|
Msg("Failed getting board threads")
|
|
return fmt.Errorf("error getting threads for /%s/: %w", board.Dir, err)
|
|
}
|
|
topPosts, err := getBoardTopPosts(board.Dir)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Msg("Failed getting board threads")
|
|
return fmt.Errorf("error getting OP posts for /%s/: %w", board.Dir, err)
|
|
}
|
|
opMap := make(map[int]*Post)
|
|
for _, post := range topPosts {
|
|
post.ParentID = 0
|
|
opMap[post.thread.ID] = post
|
|
}
|
|
|
|
postCfg := config.GetBoardConfig(board.Dir).PostConfig
|
|
for _, thread := range threads {
|
|
catalogThread := catalogThreadData{
|
|
Post: opMap[thread.ID],
|
|
Locked: boolToInt(thread.Locked),
|
|
Stickied: boolToInt(thread.Stickied),
|
|
IsSpoilered: boolToInt(thread.IsSpoilered),
|
|
Cyclical: boolToInt(thread.Cyclical),
|
|
}
|
|
errEv.Int("threadID", thread.ID)
|
|
if catalogThread.Images, err = thread.GetReplyFileCount(); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Msg("Failed getting file count")
|
|
return err
|
|
}
|
|
|
|
var maxRepliesOnBoardPage int
|
|
if thread.Stickied {
|
|
// If the thread is stickied, limit replies on the archive page to the
|
|
// configured value for stickied threads.
|
|
maxRepliesOnBoardPage = postCfg.StickyRepliesOnBoardPage
|
|
} else {
|
|
// Otherwise, limit the replies to the configured value for normal threads.
|
|
maxRepliesOnBoardPage = postCfg.RepliesOnBoardPage
|
|
}
|
|
catalogThread.Replies, err = thread.GetReplyCount()
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Msg("Failed getting reply count")
|
|
return fmt.Errorf("error getting reply count: %w", err)
|
|
}
|
|
|
|
catalogThread.Posts, err = getThreadPosts(&thread)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Msg("Failed getting replies")
|
|
return fmt.Errorf("failed getting replies: %w", err)
|
|
}
|
|
if len(catalogThread.Posts) == 0 {
|
|
continue
|
|
}
|
|
if len(catalogThread.Posts) > maxRepliesOnBoardPage {
|
|
op := catalogThread.Posts[0]
|
|
replies := catalogThread.Posts[len(catalogThread.Posts)-maxRepliesOnBoardPage:]
|
|
catalogThread.Posts = []*Post{op}
|
|
catalogThread.Posts = append(catalogThread.Posts, replies...)
|
|
}
|
|
catalogThread.uploads, err = thread.GetUploads()
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Msg("Failed getting thread uploads")
|
|
return fmt.Errorf("failed getting thread uploads: %w", err)
|
|
}
|
|
|
|
var imagesOnBoardPage int
|
|
for _, upload := range catalogThread.uploads {
|
|
for _, post := range catalogThread.Posts {
|
|
if post.ID == upload.PostID {
|
|
imagesOnBoardPage++
|
|
}
|
|
}
|
|
}
|
|
if catalogThread.Replies > maxRepliesOnBoardPage {
|
|
catalogThread.OmittedPosts = catalogThread.Replies - len(catalogThread.Posts) + 1
|
|
catalogThread.OmittedImages = len(catalogThread.uploads) - imagesOnBoardPage
|
|
}
|
|
|
|
catalogThreads = append(catalogThreads, catalogThread)
|
|
}
|
|
|
|
criticalCfg := config.GetSystemCriticalConfig()
|
|
gcutil.DeleteMatchingFiles(path.Join(criticalCfg.DocumentRoot, board.Dir), "\\d.html$")
|
|
|
|
// If there are no posts on the board
|
|
var boardPageFile *os.File
|
|
boardConfig := config.GetBoardConfig(board.Dir)
|
|
if len(threads) == 0 {
|
|
catalog.currentPage = 1
|
|
|
|
// Open 1.html for writing to the first page.
|
|
boardPageFile, err = os.OpenFile(path.Join(criticalCfg.DocumentRoot, board.Dir, "1.html"),
|
|
os.O_CREATE|os.O_RDWR|os.O_TRUNC, config.NormalFileMode)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("page", "board.html").
|
|
Msg("Failed getting board page")
|
|
return fmt.Errorf("failed opening /%s/board.html: %w", board.Dir, err)
|
|
}
|
|
defer boardPageFile.Close()
|
|
|
|
if err = config.TakeOwnershipOfFile(boardPageFile); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Msg("Unable to take ownership of board.html")
|
|
return fmt.Errorf("unable to take ownership of /%s/board.html: %w", board.Dir, err)
|
|
}
|
|
// Render board page template to the file,
|
|
// packaging the board/section list, threads, and board info
|
|
captchaCfg := config.GetSiteConfig().Captcha
|
|
if err = serverutil.MinifyTemplate(gctemplates.BoardPage, map[string]any{
|
|
"boards": gcsql.AllBoards,
|
|
"sections": gcsql.AllSections,
|
|
"threads": threads,
|
|
"numPages": 1,
|
|
"currentPage": 1,
|
|
"board": board,
|
|
"boardConfig": boardConfig,
|
|
"useCaptcha": captchaCfg != nil,
|
|
"captcha": captchaCfg,
|
|
}, boardPageFile, "text/html"); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("page", "board.html").
|
|
Msg("Failed building board")
|
|
return fmt.Errorf("failed building /%s/: %w", board.Dir, err)
|
|
}
|
|
|
|
if err = boardPageFile.Close(); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("page", "board.html").
|
|
Msg("Unable to close board file")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Create the archive pages.
|
|
catalog.fillPages(boardConfig.ThreadsPerPage, catalogThreads)
|
|
|
|
// Create array of page wrapper objects, and open the file.
|
|
var catalogPages boardCatalog
|
|
|
|
// catalog JSON file is built with the pages because pages are recorded in the JSON file
|
|
catalogJSONFile, err := os.OpenFile(path.Join(criticalCfg.DocumentRoot, board.Dir, "catalog.json"),
|
|
os.O_CREATE|os.O_RDWR|os.O_TRUNC, config.NormalFileMode)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().
|
|
Msg("Failed opening catalog.json")
|
|
return fmt.Errorf("failed opening /%s/catalog.json: %w", board.Dir, err)
|
|
}
|
|
defer catalogJSONFile.Close()
|
|
|
|
if err = config.TakeOwnershipOfFile(catalogJSONFile); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Msg("Unable to take ownership of catalog.json")
|
|
return fmt.Errorf("unable to take ownership of /%s/catalog.json: %w", board.Dir, err)
|
|
}
|
|
for _, page := range catalog.pages {
|
|
catalog.currentPage++
|
|
var currentPageFilepath string
|
|
pageFilename := strconv.Itoa(catalog.currentPage) + ".html"
|
|
currentPageFilepath = path.Join(criticalCfg.DocumentRoot, board.Dir, pageFilename)
|
|
currentPageFile, err = os.OpenFile(currentPageFilepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, config.NormalFileMode)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("page", pageFilename).
|
|
Msg("Failed getting board page")
|
|
continue
|
|
}
|
|
|
|
if err = config.TakeOwnershipOfFile(currentPageFile); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("page", pageFilename).
|
|
Msg("Unable to update file ownership")
|
|
return errors.New("unable to set board page file ownership")
|
|
}
|
|
|
|
// Render the boardpage template
|
|
captchaCfg := config.GetSiteConfig().Captcha
|
|
numThreads := len(threads)
|
|
numPages := numThreads / boardConfig.ThreadsPerPage
|
|
if (numThreads % boardConfig.ThreadsPerPage) > 0 {
|
|
numPages++
|
|
}
|
|
data := map[string]any{
|
|
"boards": gcsql.AllBoards,
|
|
"sections": gcsql.AllSections,
|
|
"threads": page.Threads,
|
|
"numPages": numPages,
|
|
"currentPage": catalog.currentPage,
|
|
"board": board,
|
|
"boardConfig": boardConfig,
|
|
"useCaptcha": captchaCfg != nil,
|
|
"captcha": captchaCfg,
|
|
}
|
|
if catalog.currentPage > 1 {
|
|
data["prevPage"] = catalog.currentPage - 1
|
|
}
|
|
if catalog.currentPage < numPages {
|
|
data["nextPage"] = catalog.currentPage + 1
|
|
}
|
|
if err = serverutil.MinifyTemplate(gctemplates.BoardPage, data, currentPageFile, "text/html"); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return fmt.Errorf("failed building /%s/ boardpage: %w", board.Dir, err)
|
|
}
|
|
if err = currentPageFile.Close(); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return fmt.Errorf("failed building /%s/ board page", board.Dir)
|
|
}
|
|
|
|
// Collect up threads for this page.
|
|
page := catalogPage{}
|
|
page.PageNum = catalog.currentPage
|
|
catalogPages.pages = append(catalogPages.pages, page)
|
|
}
|
|
|
|
if err = json.NewEncoder(catalogJSONFile).Encode(catalog.pages); err != nil {
|
|
errEv.Err(err).Caller().Msg("Unable to write catalog JSON to file")
|
|
return errors.New("failed to marshal to catalog JSON")
|
|
}
|
|
if err = catalogJSONFile.Close(); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// BuildBoards builds the specified board IDs, or all boards if no arguments are passed
|
|
// it returns any errors that were encountered
|
|
func BuildBoards(verbose bool, which ...int) error {
|
|
var boards []gcsql.Board
|
|
var err error
|
|
errEv := gcutil.LogError(nil)
|
|
defer errEv.Discard()
|
|
|
|
if which == nil {
|
|
boards, err = gcsql.GetAllBoards(false)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return err
|
|
}
|
|
} else {
|
|
for _, boardID := range which {
|
|
board, err := gcsql.GetBoardFromID(boardID)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().
|
|
Int("boardid", boardID).
|
|
Msg("Unable to get board information")
|
|
return fmt.Errorf("unable to get board information (ID: %d): %w", boardID, err)
|
|
}
|
|
boards = append(boards, *board)
|
|
}
|
|
}
|
|
if len(boards) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
var tmpErr error
|
|
var mu sync.Mutex
|
|
wg.Add(len(boards))
|
|
for b := range boards {
|
|
go func(board *gcsql.Board) {
|
|
defer wg.Done()
|
|
mu.Lock()
|
|
if tmpErr == nil && err == nil {
|
|
tmpErr = buildBoard(board, true)
|
|
}
|
|
mu.Unlock()
|
|
if tmpErr == nil && verbose {
|
|
gcutil.LogInfo().Str("board", board.Dir).
|
|
Msg("Built board successfully")
|
|
}
|
|
mu.Lock()
|
|
if err == nil && tmpErr != nil {
|
|
err = tmpErr
|
|
}
|
|
mu.Unlock()
|
|
}(&boards[b])
|
|
}
|
|
wg.Wait()
|
|
return err
|
|
}
|
|
|
|
// Build builds the board and its thread files
|
|
// if force is true, it doesn't fail if the directories exist but does fail if it is a file
|
|
func buildBoard(board *gcsql.Board, force bool) error {
|
|
var err error
|
|
errEv := gcutil.LogError(nil).
|
|
Str("boardDir", board.Dir).
|
|
Int("boardID", board.ID)
|
|
defer errEv.Discard()
|
|
if board.Dir == "" {
|
|
errEv.Err(ErrNoBoardDir).Caller().Send()
|
|
return ErrNoBoardDir
|
|
}
|
|
if board.Title == "" {
|
|
errEv.Err(ErrNoBoardTitle).Caller().Send()
|
|
return ErrNoBoardTitle
|
|
}
|
|
|
|
oldPosts, err := board.DeleteOldThreads()
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Msg("Unable to delete old threads")
|
|
return err
|
|
}
|
|
boardDir := path.Join(config.GetSystemCriticalConfig().DocumentRoot, board.Dir)
|
|
|
|
if err = os.MkdirAll(boardDir, config.DirFileMode); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
|
errEv.Err(err).Caller().Msg("Unable to create board directory")
|
|
return err
|
|
}
|
|
|
|
for _, postID := range oldPosts {
|
|
post, err := gcsql.GetPostFromID(postID, false)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().
|
|
Int("postID", postID).
|
|
Msg("Unable to get post")
|
|
return err
|
|
}
|
|
upload, err := post.GetUpload()
|
|
if err != nil {
|
|
errEv.Err(err).Caller().
|
|
Int("postID", postID).
|
|
Msg("Unable to get post uploads")
|
|
return err
|
|
}
|
|
var filePath string
|
|
if upload != nil && !upload.IsEmbed() {
|
|
filePath = path.Join(boardDir, "src", upload.Filename)
|
|
if err = os.Remove(filePath); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Int("postID", postID).
|
|
Str("upload", filePath).Send()
|
|
return err
|
|
}
|
|
thumbPath, catalogThumbPath := uploads.GetThumbnailFilenames(
|
|
path.Join(boardDir, "thumb", upload.Filename))
|
|
if err = os.Remove(thumbPath); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Int("postID", postID).
|
|
Str("thumbnail", thumbPath).Send()
|
|
return err
|
|
}
|
|
if post.IsTopPost && board.EnableCatalog {
|
|
if err = os.Remove(catalogThumbPath); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Int("postID", postID).
|
|
Str("catalogThumbPath", catalogThumbPath).Send()
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if err = post.UnlinkUploads(false); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Int("postID", postID).Send()
|
|
return err
|
|
}
|
|
if post.IsTopPost {
|
|
filePath = path.Join(boardDir, "res", strconv.Itoa(post.ID)+".html")
|
|
if err = os.Remove(filePath); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Int("postID", postID).
|
|
Str("threadFile", filePath).Send()
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
dirPath := board.AbsolutePath()
|
|
resPath := board.AbsolutePath("res")
|
|
srcPath := board.AbsolutePath("src")
|
|
thumbPath := board.AbsolutePath("thumb")
|
|
dirInfo, _ := os.Stat(dirPath)
|
|
resInfo, _ := os.Stat(resPath)
|
|
srcInfo, _ := os.Stat(srcPath)
|
|
thumbInfo, _ := os.Stat(thumbPath)
|
|
if dirInfo != nil {
|
|
if !force {
|
|
errEv.Err(os.ErrExist).Caller().
|
|
Str("dirPath", dirPath).Send()
|
|
return fmt.Errorf(pathExistsStr, dirPath)
|
|
}
|
|
if !dirInfo.IsDir() {
|
|
errEv.Err(os.ErrExist).Caller().
|
|
Str("dirPath", dirPath).Send()
|
|
return fmt.Errorf(dirIsAFileStr, dirPath)
|
|
}
|
|
} else if err = os.Mkdir(dirPath, config.DirFileMode); err != nil {
|
|
errEv.Err(os.ErrExist).Caller().
|
|
Str("dirPath", dirPath).Send()
|
|
return fmt.Errorf(genericErrStr, dirPath, err)
|
|
}
|
|
if err = config.TakeOwnership(dirPath); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("dirPath", dirPath).Send()
|
|
return fmt.Errorf(genericErrStr, dirPath, err)
|
|
}
|
|
|
|
if resInfo != nil {
|
|
if !force {
|
|
err = fmt.Errorf(pathExistsStr, resPath)
|
|
errEv.Err(err).Caller().
|
|
Str("resPath", resPath).Send()
|
|
return err
|
|
}
|
|
if !resInfo.IsDir() {
|
|
err = fmt.Errorf(dirIsAFileStr, resPath)
|
|
errEv.Err(err).Caller().
|
|
Str("resPath", resPath).Send()
|
|
return err
|
|
}
|
|
} else if err = os.Mkdir(resPath, config.DirFileMode); err != nil {
|
|
err = fmt.Errorf(genericErrStr, resPath, err)
|
|
errEv.Err(err).Caller().
|
|
Str("resPath", resPath).Send()
|
|
return fmt.Errorf(genericErrStr, resPath, err)
|
|
}
|
|
if err = config.TakeOwnership(resPath); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("resPath", resPath).Send()
|
|
return fmt.Errorf(genericErrStr, resPath, err)
|
|
}
|
|
|
|
if srcInfo != nil {
|
|
if !force {
|
|
err = fmt.Errorf(pathExistsStr, srcPath)
|
|
errEv.Err(err).Caller().
|
|
Str("srcPath", srcPath).Send()
|
|
return err
|
|
}
|
|
if !srcInfo.IsDir() {
|
|
err = fmt.Errorf(dirIsAFileStr, srcPath)
|
|
errEv.Err(err).Caller().
|
|
Str("srcPath", srcPath).Send()
|
|
return err
|
|
}
|
|
} else if err = os.Mkdir(srcPath, config.DirFileMode); err != nil {
|
|
err = fmt.Errorf(genericErrStr, srcPath, err)
|
|
errEv.Err(err).Caller().
|
|
Str("srcPath", srcPath).Send()
|
|
return err
|
|
}
|
|
if config.TakeOwnership(srcPath); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("srcPath", srcPath).Send()
|
|
return fmt.Errorf(genericErrStr, srcPath, err)
|
|
}
|
|
|
|
if thumbInfo != nil {
|
|
if !force {
|
|
return fmt.Errorf(pathExistsStr, thumbPath)
|
|
}
|
|
if !thumbInfo.IsDir() {
|
|
return fmt.Errorf(dirIsAFileStr, thumbPath)
|
|
}
|
|
} else if err = os.Mkdir(thumbPath, config.DirFileMode); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("thumbPath", thumbPath).Send()
|
|
return fmt.Errorf(genericErrStr, thumbPath, err)
|
|
}
|
|
if config.TakeOwnership(thumbPath); err != nil {
|
|
errEv.Err(err).Caller().
|
|
Str("thumbPath", thumbPath).Send()
|
|
return fmt.Errorf(genericErrStr, thumbPath, err)
|
|
}
|
|
|
|
if err = BuildBoardPages(board, errEv); err != nil {
|
|
return err
|
|
}
|
|
if err = BuildThreads(true, board.Dir, 0); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return err
|
|
}
|
|
if err = gcsql.ResetBoardSectionArrays(); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return err
|
|
}
|
|
if err = BuildFrontPage(); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return err
|
|
}
|
|
if board.EnableCatalog {
|
|
if err = BuildCatalog(board.ID); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return err
|
|
}
|
|
}
|
|
if err = BuildBoardListJSON(); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type boardsListJSON struct {
|
|
Boards []boardJSON `json:"boards"`
|
|
}
|
|
|
|
// BuildBoardListJSON generates a JSON file with info about the boards
|
|
func BuildBoardListJSON() error {
|
|
boardsJsonPath := path.Join(config.GetSystemCriticalConfig().DocumentRoot, "boards.json")
|
|
boardListFile, err := os.OpenFile(boardsJsonPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, config.NormalFileMode)
|
|
errEv := gcutil.LogError(nil).Str("building", "boards.json")
|
|
defer errEv.Discard()
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return fmt.Errorf("unable to open boards.json for writing: %w", err)
|
|
}
|
|
|
|
if err = config.TakeOwnershipOfFile(boardListFile); err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return fmt.Errorf("unable to update boards.json ownership: %w", err)
|
|
}
|
|
|
|
boardsListJSONData := boardsListJSON{
|
|
Boards: make([]boardJSON, len(gcsql.AllBoards)),
|
|
}
|
|
|
|
for b, board := range gcsql.AllBoards {
|
|
boardsListJSONData.Boards[b] = boardJSON{
|
|
Cooldowns: config.GetBoardConfig(board.Dir).Cooldowns,
|
|
}
|
|
boardsListJSONData.Boards[b].Board = &gcsql.AllBoards[b]
|
|
}
|
|
|
|
// TODO: properly check if the board is in a hidden section
|
|
boardJSON, err := json.Marshal(boardsListJSONData)
|
|
if err != nil {
|
|
errEv.Err(err).Caller().Send()
|
|
return fmt.Errorf("failed to create boards.json: %w", err)
|
|
}
|
|
|
|
if _, err = serverutil.MinifyWriter(boardListFile, boardJSON, "application/json"); err != nil {
|
|
errEv.Err(err).Caller().Msg("Failed writing to boards.json")
|
|
return errors.New("failed writing boards.json file")
|
|
}
|
|
if err = boardListFile.Close(); err != nil {
|
|
errEv.Err(err).Caller().Msg("Failed closing boards.json")
|
|
return errors.New("failed closing boards.json")
|
|
}
|
|
return nil
|
|
}
|