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:
dbastrikin
2026-03-09 23:37:58 +02:00
parent 765346d4e4
commit c9ddb708b1
13 changed files with 202 additions and 73 deletions

View File

@@ -13,7 +13,8 @@ class User {
final int? heightCm;
@JsonKey(name: 'weight_kg')
final double? weightKg;
final int? age;
@JsonKey(name: 'date_of_birth')
final String? dateOfBirth;
final String? gender;
final String? activity;
final String? goal;
@@ -30,7 +31,7 @@ class User {
this.avatarUrl,
this.heightCm,
this.weightKg,
this.age,
this.dateOfBirth,
this.gender,
this.activity,
this.goal,
@@ -42,6 +43,19 @@ class User {
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
int? get age {
if (dateOfBirth == null) return null;
final dob = DateTime.tryParse(dateOfBirth!);
if (dob == null) return null;
final now = DateTime.now();
int years = now.year - dob.year;
if (now.month < dob.month ||
(now.month == dob.month && now.day < dob.day)) {
years--;
}
return years;
}
bool get hasCompletedOnboarding =>
heightCm != null && weightKg != null && age != null && gender != null;
heightCm != null && weightKg != null && dateOfBirth != null && gender != null;
}

View File

@@ -13,7 +13,7 @@ User _$UserFromJson(Map<String, dynamic> json) => User(
avatarUrl: json['avatar_url'] as String?,
heightCm: (json['height_cm'] as num?)?.toInt(),
weightKg: (json['weight_kg'] as num?)?.toDouble(),
age: (json['age'] as num?)?.toInt(),
dateOfBirth: json['date_of_birth'] as String?,
gender: json['gender'] as String?,
activity: json['activity'] as String?,
goal: json['goal'] as String?,
@@ -29,7 +29,7 @@ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'avatar_url': instance.avatarUrl,
'height_cm': instance.heightCm,
'weight_kg': instance.weightKg,
'age': instance.age,
'date_of_birth': instance.dateOfBirth,
'gender': instance.gender,
'activity': instance.activity,
'goal': instance.goal,