feat: dish recognition job context, diary linkage, home widget, history page

Backend:
- Rename recognition_jobs → dish_recognition_jobs; add target_date and
  target_meal_type columns to capture scan context at submission time
- Add job_id FK on meal_diary so entries are linked to their origin job
- New GET /ai/jobs endpoint returns today's unlinked jobs for the current user
- diary.Entry and CreateRequest gain job_id field; repository reads/writes it
- CORS middleware: allow Accept-Language and Cache-Control headers
- Logging middleware: implement http.Flusher on responseWriter (needed for SSE)
- Consolidate migrations into a single 001_initial_schema.sql

Flutter:
- POST /ai/recognize-dish now sends target_date and target_meal_type
- DishResultSheet accepts jobId; _addToDiary includes it in the diary payload,
  saves last-used meal type to SharedPreferences, invalidates todayJobsProvider
- TodayJobsNotifier + todayJobsProvider: loads unlinked jobs via GET /ai/jobs
- Home screen shows _TodayJobsWidget (up to 3 tiles) between macros and meals;
  tapping a done tile reopens DishResultSheet with the stored result
- Quick Actions row: third button "История" → /scan/history
- New RecognitionHistoryScreen: full-screen list of today's unlinked jobs
- LocalPreferences wrapper over SharedPreferences (last_used_meal_type)
- app_theme: apply Google Fonts Roboto as default font family

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-03-19 16:11:21 +02:00
parent 1aaf20619d
commit cf69a4a3d9
21 changed files with 682 additions and 113 deletions

View File

@@ -390,6 +390,31 @@ CREATE TABLE shopping_lists (
UNIQUE (user_id, menu_plan_id)
);
-- ---------------------------------------------------------------------------
-- dish_recognition_jobs
-- ---------------------------------------------------------------------------
CREATE TABLE dish_recognition_jobs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
user_plan TEXT NOT NULL,
image_base64 TEXT NOT NULL,
mime_type TEXT NOT NULL DEFAULT 'image/jpeg',
lang TEXT NOT NULL DEFAULT 'en',
target_date DATE,
target_meal_type TEXT,
status TEXT NOT NULL DEFAULT 'pending',
-- pending | processing | done | failed
result JSONB,
error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ
);
CREATE INDEX idx_dish_recognition_jobs_user
ON dish_recognition_jobs (user_id, created_at DESC);
CREATE INDEX idx_dish_recognition_jobs_pending
ON dish_recognition_jobs (status, user_plan, created_at ASC);
-- ---------------------------------------------------------------------------
-- meal_diary
-- ---------------------------------------------------------------------------
@@ -400,33 +425,14 @@ CREATE TABLE meal_diary (
meal_type TEXT NOT NULL,
portions DECIMAL(5,2) NOT NULL DEFAULT 1,
source TEXT NOT NULL DEFAULT 'manual',
dish_id UUID NOT NULL REFERENCES dishes(id) ON DELETE RESTRICT,
recipe_id UUID REFERENCES recipes(id) ON DELETE SET NULL,
dish_id UUID NOT NULL REFERENCES dishes(id) ON DELETE RESTRICT,
recipe_id UUID REFERENCES recipes(id) ON DELETE SET NULL,
portion_g DECIMAL(10,2),
job_id UUID REFERENCES dish_recognition_jobs(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_meal_diary_user_date ON meal_diary (user_id, date);
-- ---------------------------------------------------------------------------
-- recognition_jobs
-- ---------------------------------------------------------------------------
CREATE TABLE recognition_jobs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
user_plan TEXT NOT NULL,
image_base64 TEXT NOT NULL,
mime_type TEXT NOT NULL DEFAULT 'image/jpeg',
lang TEXT NOT NULL DEFAULT 'en',
status TEXT NOT NULL DEFAULT 'pending',
-- pending | processing | done | failed
result JSONB,
error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ
);
CREATE INDEX idx_recognition_jobs_user ON recognition_jobs (user_id, created_at DESC);
CREATE INDEX idx_recognition_jobs_pending ON recognition_jobs (status, user_plan, created_at ASC);
CREATE INDEX idx_meal_diary_job_id ON meal_diary (job_id) WHERE job_id IS NOT NULL;
-- ---------------------------------------------------------------------------
-- Seed data: languages
@@ -605,8 +611,8 @@ INSERT INTO dish_category_translations (category_slug, lang, name) VALUES
('snack', 'ru', 'Снэк');
-- +goose Down
DROP TABLE IF EXISTS recognition_jobs;
DROP TABLE IF EXISTS meal_diary;
DROP TABLE IF EXISTS dish_recognition_jobs;
DROP TABLE IF EXISTS shopping_lists;
DROP TABLE IF EXISTS menu_items;
DROP TABLE IF EXISTS menu_plans;