package store import ( "context" "encoding/json" ) // SaveDesign stores the design form in one go: the theme, the modules of every // area and the menu, so a half-applied save is impossible. mods and menu are // the complete new state in display order: rows with an ID are updated, rows // without one inserted, and anything else is deleted. func (bs *BlogStore) SaveDesign(ctx context.Context, theme json.RawMessage, mods []Module, menu []MenuItem) error { tx, err := bs.db.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) if _, err := tx.Exec(ctx, `UPDATE settings SET theme=$1, updated_at=now()`, theme); err != nil { return err } keep := []int64{} for _, m := range mods { if m.ID != 0 { keep = append(keep, m.ID) } } if _, err := tx.Exec(ctx, `DELETE FROM modules WHERE id <> ALL($1)`, keep); err != nil { return err } order := map[string]int{} // sort_order restarts in every area for _, m := range mods { pos := order[m.Area] order[m.Area]++ if m.ID != 0 { // area and kind never change _, err = tx.Exec(ctx, `UPDATE modules SET title=$2, body=$3, count=$4, sort_order=$5, updated_at=now() WHERE id=$1`, m.ID, m.Title, m.Body, m.Count, pos) } else { _, err = tx.Exec(ctx, `INSERT INTO modules (area, kind, title, body, count, sort_order) VALUES ($1,$2,$3,$4,$5,$6)`, m.Area, m.Kind, m.Title, m.Body, m.Count, pos) } if err != nil { return wrap(err) } } keep = keep[:0] for _, it := range menu { if it.ID != 0 { keep = append(keep, it.ID) } } if _, err := tx.Exec(ctx, `DELETE FROM menu_items WHERE id <> ALL($1)`, keep); err != nil { return err } for i, it := range menu { switch { case it.ID != 0: _, err = tx.Exec(ctx, `UPDATE menu_items SET label=$2, url=$3, sort_order=$4 WHERE id=$1`, it.ID, it.Label, it.URL, i) case it.PageID != nil: // a page deleted meanwhile simply adds nothing _, err = tx.Exec(ctx, `INSERT INTO menu_items (page_id, sort_order) SELECT id, $2 FROM pages WHERE id=$1 ON CONFLICT (page_id) WHERE page_id IS NOT NULL DO UPDATE SET sort_order=excluded.sort_order`, *it.PageID, i) default: _, err = tx.Exec(ctx, `INSERT INTO menu_items (label, url, sort_order) VALUES ($1,$2,$3)`, it.Label, it.URL, i) } if err != nil { return wrap(err) } } return tx.Commit(ctx) }