slack5-1770574304/services/preferences-api/internal/adapter/postgres/preferences.go
rdev-worker 5fa5a77bfb
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
build: /implement-feature user-preferences
2026-02-08 18:36:52 +00:00

96 lines
2.8 KiB
Go

package postgres
import (
"context"
"database/sql"
"fmt"
"time"
"git.threesix.ai/jordan/slack5-1770574304/services/preferences-api/internal/domain"
"git.threesix.ai/jordan/slack5-1770574304/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 *sql.DB
}
// NewPreferenceRepository creates a new PostgreSQL preference repository.
// It ensures the schema exists on creation.
func NewPreferenceRepository(db *sql.DB) (*PreferenceRepository, error) {
r := &PreferenceRepository{db: db}
if err := r.ensureSchema(); err != nil {
return nil, fmt.Errorf("ensure schema: %w", err)
}
return r, nil
}
// ensureSchema creates the user_preferences table if it does not exist.
func (r *PreferenceRepository) ensureSchema() error {
_, err := r.db.Exec(`
CREATE TABLE IF NOT EXISTS user_preferences (
user_id TEXT PRIMARY KEY,
theme TEXT NOT NULL DEFAULT 'system',
language TEXT NOT NULL DEFAULT 'en',
notify_email BOOLEAN NOT NULL DEFAULT true,
notify_push BOOLEAN NOT NULL DEFAULT true,
notify_digest TEXT NOT NULL DEFAULT 'weekly',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`)
return err
}
// Get returns the preferences for a user by ID.
// Returns nil, nil when no preferences exist.
func (r *PreferenceRepository) Get(ctx context.Context, userID string) (*domain.UserPreferences, error) {
var p domain.UserPreferences
var updatedAt time.Time
err := r.db.QueryRowContext(ctx,
`SELECT user_id, theme, language, notify_email, notify_push, notify_digest, updated_at
FROM user_preferences WHERE user_id = $1`, userID,
).Scan(
&p.UserID,
&p.Theme,
&p.Language,
&p.Notifications.Email,
&p.Notifications.Push,
&p.Notifications.Digest,
&updatedAt,
)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("get preferences: %w", err)
}
p.UpdatedAt = updatedAt
return &p, nil
}
// Upsert creates or replaces preferences for a user.
func (r *PreferenceRepository) Upsert(ctx context.Context, prefs *domain.UserPreferences) error {
_, err := r.db.ExecContext(ctx, `
INSERT INTO user_preferences (user_id, theme, language, notify_email, notify_push, notify_digest, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW())
ON CONFLICT (user_id) DO UPDATE SET
theme = $2,
language = $3,
notify_email = $4,
notify_push = $5,
notify_digest = $6,
updated_at = NOW()
`, prefs.UserID, prefs.Theme, prefs.Language,
prefs.Notifications.Email, prefs.Notifications.Push, prefs.Notifications.Digest,
)
if err != nil {
return fmt.Errorf("upsert preferences: %w", err)
}
return nil
}