feat: implement Iteration 0 foundation (backend + Flutter client)

Backend (Go):
- Project structure with chi router, pgxpool, goose migrations
- JWT auth (access/refresh tokens) with Firebase token verification
- NoopTokenVerifier for local dev without Firebase credentials
- PostgreSQL user repository with atomic profile updates (transactions)
- Mifflin-St Jeor calorie calculation based on profile data
- REST API: POST /auth/login, /auth/refresh, /auth/logout, GET/PUT /profile, GET /health
- Middleware: auth, CORS (localhost wildcard), logging, recovery, request_id
- Unit tests (51 passing) and integration tests (testcontainers)
- Docker Compose setup with postgres healthcheck and graceful shutdown

Flutter client:
- Riverpod state management with GoRouter navigation
- Firebase Auth (email/password + Google sign-in with web popup support)
- Platform-aware API URLs (web/Android/iOS)
- Dio HTTP client with JWT auth interceptor and concurrent refresh handling
- Secure token storage
- Screens: Login, Register, Home (tabs: Menu, Recipes, Products, Profile)
- Unit tests (17 passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-02-20 13:14:58 +02:00
commit 24219b611e
140 changed files with 13062 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
package user
import (
"encoding/json"
"time"
)
type User struct {
ID string `json:"id"`
FirebaseUID string `json:"-"`
Email string `json:"email"`
Name string `json:"name"`
AvatarURL *string `json:"avatar_url"`
HeightCM *int `json:"height_cm"`
WeightKG *float64 `json:"weight_kg"`
Age *int `json:"age"`
Gender *string `json:"gender"`
Activity *string `json:"activity"`
Goal *string `json:"goal"`
DailyCalories *int `json:"daily_calories"`
Plan string `json:"plan"`
Preferences json.RawMessage `json:"preferences"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type UpdateProfileRequest struct {
Name *string `json:"name"`
HeightCM *int `json:"height_cm"`
WeightKG *float64 `json:"weight_kg"`
Age *int `json:"age"`
Gender *string `json:"gender"`
Activity *string `json:"activity"`
Goal *string `json:"goal"`
Preferences *json.RawMessage `json:"preferences"`
DailyCalories *int `json:"-"` // internal, set by service
}
// HasBodyParams returns true if any body parameter is being updated
// that would require recalculation of daily calories.
func (r *UpdateProfileRequest) HasBodyParams() bool {
return r.HeightCM != nil || r.WeightKG != nil || r.Age != nil ||
r.Gender != nil || r.Activity != nil || r.Goal != nil
}