feat: replace age integer with date_of_birth across backend and client
Store date_of_birth (DATE) instead of a static age integer so that age is always computed dynamically from the stored date of birth. - Migration 011: adds date_of_birth, backfills from age, drops age - AgeFromDOB helper computes current age from YYYY-MM-DD string - User model, repository SQL, and service validation updated - Flutter: User.age becomes a computed getter; profile edit screen uses a date picker bounded to [today-120y, today-10y] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ func (r *Repository) UpsertByFirebaseUID(ctx context.Context, uid, email, name,
|
||||
avatar_url = COALESCE(EXCLUDED.avatar_url, users.avatar_url),
|
||||
updated_at = now()
|
||||
RETURNING id, firebase_uid, email, name, avatar_url,
|
||||
height_cm, weight_kg, age, gender, activity, goal, daily_calories,
|
||||
height_cm, weight_kg, date_of_birth, gender, activity, goal, daily_calories,
|
||||
plan, preferences, created_at, updated_at`
|
||||
|
||||
return r.scanUser(r.pool.QueryRow(ctx, query, uid, email, name, avatarPtr))
|
||||
@@ -54,7 +54,7 @@ func (r *Repository) UpsertByFirebaseUID(ctx context.Context, uid, email, name,
|
||||
func (r *Repository) GetByID(ctx context.Context, id string) (*User, error) {
|
||||
query := `
|
||||
SELECT id, firebase_uid, email, name, avatar_url,
|
||||
height_cm, weight_kg, age, gender, activity, goal, daily_calories,
|
||||
height_cm, weight_kg, date_of_birth, gender, activity, goal, daily_calories,
|
||||
plan, preferences, created_at, updated_at
|
||||
FROM users WHERE id = $1`
|
||||
|
||||
@@ -91,9 +91,9 @@ func buildUpdateQuery(id string, req UpdateProfileRequest) (string, []interface{
|
||||
args = append(args, *req.WeightKG)
|
||||
argIdx++
|
||||
}
|
||||
if req.Age != nil {
|
||||
setClauses = append(setClauses, fmt.Sprintf("age = $%d", argIdx))
|
||||
args = append(args, *req.Age)
|
||||
if req.DateOfBirth != nil {
|
||||
setClauses = append(setClauses, fmt.Sprintf("date_of_birth = $%d", argIdx))
|
||||
args = append(args, *req.DateOfBirth)
|
||||
argIdx++
|
||||
}
|
||||
if req.Gender != nil {
|
||||
@@ -132,7 +132,7 @@ func buildUpdateQuery(id string, req UpdateProfileRequest) (string, []interface{
|
||||
UPDATE users SET %s
|
||||
WHERE id = $%d
|
||||
RETURNING id, firebase_uid, email, name, avatar_url,
|
||||
height_cm, weight_kg, age, gender, activity, goal, daily_calories,
|
||||
height_cm, weight_kg, date_of_birth, gender, activity, goal, daily_calories,
|
||||
plan, preferences, created_at, updated_at`,
|
||||
strings.Join(setClauses, ", "), argIdx)
|
||||
args = append(args, id)
|
||||
@@ -184,7 +184,7 @@ func (r *Repository) SetRefreshToken(ctx context.Context, id, token string, expi
|
||||
func (r *Repository) FindByRefreshToken(ctx context.Context, token string) (*User, error) {
|
||||
query := `
|
||||
SELECT id, firebase_uid, email, name, avatar_url,
|
||||
height_cm, weight_kg, age, gender, activity, goal, daily_calories,
|
||||
height_cm, weight_kg, date_of_birth, gender, activity, goal, daily_calories,
|
||||
plan, preferences, created_at, updated_at
|
||||
FROM users
|
||||
WHERE refresh_token = $1 AND token_expires_at > now()`
|
||||
@@ -205,14 +205,19 @@ type scannable interface {
|
||||
func (r *Repository) scanUser(row scannable) (*User, error) {
|
||||
var u User
|
||||
var prefs []byte
|
||||
var dob *time.Time
|
||||
err := row.Scan(
|
||||
&u.ID, &u.FirebaseUID, &u.Email, &u.Name, &u.AvatarURL,
|
||||
&u.HeightCM, &u.WeightKG, &u.Age, &u.Gender, &u.Activity, &u.Goal, &u.DailyCalories,
|
||||
&u.HeightCM, &u.WeightKG, &dob, &u.Gender, &u.Activity, &u.Goal, &u.DailyCalories,
|
||||
&u.Plan, &prefs, &u.CreatedAt, &u.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scan user: %w", err)
|
||||
}
|
||||
if dob != nil {
|
||||
s := dob.Format("2006-01-02")
|
||||
u.DateOfBirth = &s
|
||||
}
|
||||
u.Preferences = json.RawMessage(prefs)
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user