package store import ( "context" "database/sql" "encoding/json" "fmt" "housecarl/internal/model" ) const settingsKey = "settings" // LoadState reads the whole configuration. func (s *Store) LoadState(ctx context.Context) (*model.State, error) { st := model.NewState() if v, err := s.GetMeta(ctx, settingsKey); err != nil { return nil, err } else if v != "" { if err := json.Unmarshal([]byte(v), &st.Settings); err != nil { return nil, fmt.Errorf("reading settings: %w", err) } } rows, err := s.db.QueryContext(ctx, `SELECT kind, id, data FROM objects ORDER BY kind, id`) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var kind, data string var id int64 if err := rows.Scan(&kind, &id, &data); err != nil { return nil, err } if err := st.Put(kind, id, json.RawMessage(data)); err != nil { return nil, fmt.Errorf("reading %s %d: %w", kind, id, err) } } if err := rows.Err(); err != nil { return nil, err } st.Sort() return st, nil } // entry is marked reverted. func (s *Store) SaveChanges(ctx context.Context, changes []model.Change, e *model.HistoryEntry) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() for _, c := range changes { if err := applyChange(ctx, tx, c); err != nil { return fmt.Errorf("saving %s %d: %w", c.Kind, c.ID, err) } } if e != nil { var uid any if e.UserID != 0 { uid = e.UserID } var revertOf any if e.RevertOf != 0 { revertOf = e.RevertOf } res, err := tx.ExecContext(ctx, `INSERT INTO history (time, user_id, username, via, description, changes, revert_of) VALUES (?,?,?,?,?,?,?)`, ts(e.Time), uid, e.Username, e.Via, e.Description, jsonText(e.Changes), revertOf) if err != nil { return err } if e.ID, err = res.LastInsertId(); err != nil { return err } if e.RevertOf != 0 { if _, err := tx.ExecContext(ctx, `UPDATE history SET reverted_by = ? WHERE id = ?`, e.ID, e.RevertOf); err != nil { return err } } } return tx.Commit() } func applyChange(ctx context.Context, tx *sql.Tx, c model.Change) error { if c.Kind == model.KindSettings { if c.After == nil { return fmt.Errorf("settings can't be deleted") } _, err := tx.ExecContext(ctx, `INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`, settingsKey, string(c.After)) return err } if c.After == nil { _, err := tx.ExecContext(ctx, `DELETE FROM objects WHERE kind = ? AND id = ?`, c.Kind, c.ID) return err } _, err := tx.ExecContext(ctx, `INSERT INTO objects (kind, id, data) VALUES (?, ?, ?) ON CONFLICT(kind, id) DO UPDATE SET data = excluded.data`, c.Kind, c.ID, string(c.After)) return err }