slate-test-1770505673/services/preferences-api/internal/adapter/postgres/preference.go
rdev-worker 868f79c67a
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
build: /implement-feature user-preferences
2026-02-07 23:47:42 +00:00

91 lines
2.6 KiB
Go

// 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()
}