package store import ( "context" "time" ) // Module is one building block of a blog's layout: a menu, an archive, a // block of custom HTML… placed in one of the areas of the page. type Module struct { ID int64 Area string // header | left | right | above | below | footer Kind string // title | logo | menu | archive | recent | html | rss | text | sitemap Title string // optional heading (side columns and footer) Body string // custom HTML, or the footer text Count int // recent posts to show SortOrder int CreatedAt time.Time UpdatedAt time.Time } const moduleCols = `id, area, kind, title, body, count, sort_order, created_at, updated_at` func scanModule(row interface{ Scan(...any) error }) (*Module, error) { var m Module err := row.Scan(&m.ID, &m.Area, &m.Kind, &m.Title, &m.Body, &m.Count, &m.SortOrder, &m.CreatedAt, &m.UpdatedAt) if err != nil { return nil, wrap(err) } return &m, nil } // ListModules returns every module of the blog, grouped by area in display order. func (bs *BlogStore) ListModules(ctx context.Context) ([]Module, error) { rows, err := bs.db.Query(ctx, `SELECT `+moduleCols+` FROM modules ORDER BY area, sort_order, id`) if err != nil { return nil, err } defer rows.Close() var out []Module for rows.Next() { m, err := scanModule(rows) if err != nil { return nil, err } out = append(out, *m) } return out, rows.Err() } // CreateModule appends a module to the end of its area. func (bs *BlogStore) CreateModule(ctx context.Context, m *Module) (*Module, error) { return scanModule(bs.db.QueryRow(ctx, `INSERT INTO modules (area, kind, title, body, count, sort_order) VALUES ($1,$2,$3,$4,$5,(SELECT coalesce(max(sort_order),-1)+1 FROM modules WHERE area=$1)) RETURNING `+moduleCols, m.Area, m.Kind, m.Title, m.Body, m.Count)) } // ResetModules puts the layout back to what a new blog starts with. func (bs *BlogStore) ResetModules(ctx context.Context) error { tx, err := bs.db.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) if _, err := tx.Exec(ctx, `DELETE FROM modules`); err != nil { return err } if err := insertDefaultModules(ctx, tx); err != nil { return err } return tx.Commit(ctx) } // insertDefaultModules is the starting layout: title and menu in the header, // an RSS link in the footer. func insertDefaultModules(ctx context.Context, tx querier) error { _, err := tx.Exec(ctx, `INSERT INTO modules (area, kind, sort_order) VALUES ('header','title',0), ('header','menu',1), ('footer','rss',0)`) return err }