- All response types now use lowercase json tags (id, name, photo_url, etc.) - email/phone/birthdate/last_seen_at/photo_url use omitempty - audit.Metadata type []byte (was map[string]any) - fixes register/login inconsistency where login returned ID/Name (CamelCase) while register returned id/name (lowercase)
101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
package preferences
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// допустимые значения (для валидации)
|
|
var AllowedDrinks = map[string]bool{
|
|
"beer": true, "wine": true, "whiskey": true, "vodka": true,
|
|
"cocktail": true, "rum": true, "gin": true, "tequila": true,
|
|
"cider": true, "champagne": true, "non_alcoholic": true,
|
|
}
|
|
|
|
var AllowedActivities = map[string]bool{
|
|
"walk": true, "dinner": true, "bar": true, "cafe": true,
|
|
"travel": true, "movie": true, "concert": true, "sport": true,
|
|
"cooking": true, "gaming": true, "reading": true, "other": true,
|
|
}
|
|
|
|
var AllowedPurposes = map[string]bool{
|
|
"chat": true, "drink": true, "walk": true, "dinner": true,
|
|
"travel": true, "relationship": true, "friendship": true, "other": true,
|
|
}
|
|
|
|
type Prefs struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
Drinks []string `json:"drinks"`
|
|
Activities []string `json:"activities"`
|
|
Purposes []string `json:"purposes"`
|
|
Language string `json:"language"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type Repo struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
func NewRepo(pool *pgxpool.Pool) *Repo {
|
|
return &Repo{pool: pool}
|
|
}
|
|
|
|
func (r *Repo) Get(ctx context.Context, userID uuid.UUID) (*Prefs, error) {
|
|
p := &Prefs{UserID: userID, Drinks: []string{}, Activities: []string{}, Purposes: []string{}, Language: "ru"}
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT drinks, activities, purposes, COALESCE(language, 'ru'), updated_at
|
|
FROM user_preferences WHERE user_id=$1`, userID,
|
|
).Scan(&p.Drinks, &p.Activities, &p.Purposes, &p.Language, &p.UpdatedAt)
|
|
if err != nil {
|
|
// no row = defaults
|
|
p.UpdatedAt = time.Now()
|
|
return p, nil
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (r *Repo) Upsert(ctx context.Context, p *Prefs) error {
|
|
_, err := r.pool.Exec(ctx, `
|
|
INSERT INTO user_preferences (user_id, drinks, activities, purposes, language, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, NOW())
|
|
ON CONFLICT (user_id) DO UPDATE
|
|
SET drinks = EXCLUDED.drinks,
|
|
activities = EXCLUDED.activities,
|
|
purposes = EXCLUDED.purposes,
|
|
language = EXCLUDED.language,
|
|
updated_at = NOW()`,
|
|
p.UserID, p.Drinks, p.Activities, p.Purposes, p.Language,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (r *Repo) Validate(values []string, allowed map[string]bool) []string {
|
|
out := make([]string, 0, len(values))
|
|
seen := make(map[string]bool)
|
|
for _, v := range values {
|
|
v = trimLower(v)
|
|
if v == "" || seen[v] {
|
|
continue
|
|
}
|
|
if allowed[v] {
|
|
out = append(out, v)
|
|
seen[v] = true
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func trimLower(s string) string {
|
|
out := []rune{}
|
|
for _, r := range s {
|
|
if r >= 'A' && r <= 'Z' {
|
|
r = r + 32
|
|
}
|
|
out = append(out, r)
|
|
}
|
|
return string(out)
|
|
}
|