// Package postgres provides PostgreSQL implementations of repository interfaces. package postgres import ( "context" "database/sql" "fmt" "github.com/jmoiron/sqlx" "git.threesix.ai/jordan/slate-test-1770505673/pkg/logging" "git.threesix.ai/jordan/slate-test-1770505673/services/preferences-api/internal/port" ) // Compile-time verification that PreferenceRepository implements port.PreferenceRepository. var _ port.PreferenceRepository = (*PreferenceRepository)(nil) // PreferenceRepository is a PostgreSQL implementation of port.PreferenceRepository. type PreferenceRepository struct { db *sqlx.DB logger *logging.Logger } // NewPreferenceRepository creates a new PostgreSQL preference repository. func NewPreferenceRepository(db *sqlx.DB, logger *logging.Logger) *PreferenceRepository { return &PreferenceRepository{ db: db, logger: logger.WithComponent("PreferenceRepository"), } } // preferenceRow represents a row in the user_preferences table. type preferenceRow struct { Key string `db:"key"` Value string `db:"value"` } // GetByUserID returns all preferences for a user as a map[key]value. // Returns an empty map (not nil) if the user has no preferences. func (r *PreferenceRepository) GetByUserID(ctx context.Context, userID string) (map[string]string, error) { var rows []preferenceRow err := r.db.SelectContext(ctx, &rows, `SELECT key, value FROM user_preferences WHERE user_id = $1`, userID) if err != nil { return nil, fmt.Errorf("querying preferences: %w", err) } result := make(map[string]string, len(rows)) for _, row := range rows { result[row.Key] = row.Value } return result, nil } // Upsert creates or updates preferences for a user within a single transaction. // Only the provided keys are affected; existing keys not in the map are preserved. func (r *PreferenceRepository) Upsert(ctx context.Context, userID string, prefs map[string]string) error { tx, err := r.db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("beginning transaction: %w", err) } defer func() { _ = tx.Rollback() }() stmt, err := tx.PrepareContext(ctx, ` INSERT INTO user_preferences (user_id, key, value) VALUES ($1, $2, $3) ON CONFLICT (user_id, key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW() `) if err != nil { return fmt.Errorf("preparing statement: %w", err) } defer closeStmt(stmt) for key, value := range prefs { if _, err := stmt.ExecContext(ctx, userID, key, value); err != nil { return fmt.Errorf("upserting preference %s: %w", key, err) } } if err := tx.Commit(); err != nil { return fmt.Errorf("committing transaction: %w", err) } return nil } func closeStmt(stmt *sql.Stmt) { _ = stmt.Close() }