package store import ( "context" "database/sql" "encoding/json" "fmt" "time" "git.nakama.town/fmartingr/hako/internal/archival/rules" "github.com/huandu/go-sqlbuilder" ) // SettingsStore handles database operations for settings type SettingsStore struct { readDB *sql.DB writeDB *sql.DB } // NewSettingsStore creates a new SettingsStore func NewSettingsStore(readDB, writeDB *sql.DB) *SettingsStore { return &SettingsStore{ readDB: readDB, writeDB: writeDB, } } // Get retrieves a setting value by key func (s *SettingsStore) Get(ctx context.Context, key string) (string, error) { sb := sqlbuilder.NewSelectBuilder() sb.Select("value") sb.From("settings") sb.Where(sb.Equal("key", key)) query, args := sb.Build() row := s.readDB.QueryRowContext(ctx, query, args...) var value string err := row.Scan(&value) if err != nil { if err == sql.ErrNoRows { return "", nil // Not found is not an error, return empty string } return "", fmt.Errorf("failed to get setting: %w", err) } return value, nil } // Set creates or updates a setting value by key func (s *SettingsStore) Set(ctx context.Context, key, value string) error { // SQLite supports INSERT OR REPLACE query := `INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, ?)` now := time.Now().Format("2006-01-02 15:04:05") _, err := s.writeDB.ExecContext(ctx, query, key, value, now) if err != nil { return fmt.Errorf("failed to set setting: %w", err) } return nil } // GetRulesConfig retrieves and parses the rules configuration func (s *SettingsStore) GetRulesConfig(ctx context.Context) (*rules.RuleConfig, error) { value, err := s.Get(ctx, "rules") if err != nil { return nil, fmt.Errorf("failed to get rules setting: %w", err) } if value == "" { // Return default config if not set return &rules.RuleConfig{ Rules: []rules.Rule{}, DefaultArchivers: []rules.ArchiverConfig{{Key: "obelisk"}, {Key: "thumbnail"}}, }, nil } // Parse JSON config, err := rules.UnmarshalRuleConfig([]byte(value)) if err != nil { return nil, fmt.Errorf("failed to parse rules config: %w", err) } return config, nil } // SetRulesConfig serializes and stores the rules configuration func (s *SettingsStore) SetRulesConfig(ctx context.Context, config *rules.RuleConfig) error { // Serialize to JSON // We need to manually construct JSON since Rule is an interface // Convert rules to JSON by marshaling each rule individually rulesJSON := make([]json.RawMessage, len(config.Rules)) for i, rule := range config.Rules { ruleBytes, err := json.Marshal(rule) if err != nil { return fmt.Errorf("failed to marshal rule %d: %w", i, err) } rulesJSON[i] = ruleBytes } jsonData := map[string]interface{}{ "rules": rulesJSON, "default_extractors": config.DefaultArchivers, // JSON key kept for backward compatibility } valueBytes, err := json.Marshal(jsonData) if err != nil { return fmt.Errorf("failed to marshal rules config: %w", err) } return s.Set(ctx, "rules", string(valueBytes)) }