buhapp-backend/internal/preferences/repo.go
ga d4997e85ef Sprint 2: profile, preferences, location, search
- PUT  /api/v1/me (update name, city, bio, gender, photo)
- GET/PUT /api/v1/me/prefs (drinks, activities, purposes, language)
- PUT  /api/v1/me/location (auto-noise ~300m)
- PUT  /api/v1/me/visibility (hide/show on map)
- GET  /api/v1/search/nearby?lat&lng&radius (haversine, 1h TTL)
- GET  /api/v1/users/:id (public profile, no email/phone)
- migrations 0002: user_preferences, user_locations
2026-08-20 15:49:21 +00:00

104 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
Drinks []string
Activities []string
Purposes []string
Language string
UpdatedAt time.Time
}
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,
)
if err != nil {
return err
}
return r.Get(ctx, p.UserID)
}
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)
}