Date: 2026-06-17
Update: 2026-06-18
Update: 2026-06-30
- The Supabase bridge runs one sync cycle at a time and commits by domain: operations, demo targets, history, then training.
- Each bridge transaction sets
lock_timeoutandstatement_timeoutfrom:TRASH_SORTER_DB_LOCK_TIMEOUT_MSdefault2000TRASH_SORTER_DB_STATEMENT_TIMEOUT_MSdefault15000
- Lock-not-available, deadlock, and serialization failures retry at the domain transaction boundary with bounded jittered retry. Statement timeout, authentication, and schema errors fail the cycle clearly instead of retrying forever.
- Conflict upserts skip unchanged rows with
IS DISTINCT FROMwhere possible, so timestamps only move when business data changes. - A successful sync writes a heartbeat. Docker healthcheck verifies process health and heartbeat freshness.
- Production User analytics, history, device, report, experience, advisor, and CSV export now read scoped Supabase data through authenticated Next.js routes.
- Every query derives
owner_usernamefrom the verified session. Browser query parameters cannot switch account scope. - The hardware bridge retries history uploads with
(device_id, local_history_id)and skips rows that do not have an owner instead of publishing unassigned history. - A valid saved session is visibly restored; an expired token is removed before returning to login. User routes continue to redirect away from Admin pages.
- Production browser code blocks direct local-agent calls. Admin camera and training remain available only through the allowlisted HTTPS hardware bridge.
- A User without assigned hardware receives a completed empty dashboard and
the message
Chưa được gán thiết bị, never another User's data.
- Vercel hosts the Next.js web app.
- Supabase owns cloud Postgres/RLS, Storage later, and Realtime subscriptions.
- Vercel serves a small cloud auth API for
/api/auth/login,/api/me,/api/auth/logout, and/api/auth/change-passwordagainst the sharedaccounts/sessionstables. This lets the deployed web login without a running local agent. - The Windows machine attached to USB camera/UART remains the hardware bridge.
- Browser users never receive service-role secrets, camera stream tokens, UART controls, or training controls unless they are Admin through the local/admin API surface.
- Create a Supabase project and apply
supabase/migrations/202606170001_full_cloud_readiness.sql. - Enable RLS on every exposed table; the migration does this by default.
- Create one
profilesrow per Supabase Auth user:role='admin'for operators.role='user'for field/user accounts.
- Apply/create the local-agent auth tables in Supabase Postgres (
accounts,sessions,chat_usage) by running the local agent or auth management scripts withTRASH_SORTER_AUTH_DATABASE_URLpointed at Supabase. - On Vercel, set server-side auth database variables:
TRASH_SORTER_AUTH_DATABASE_URLDATABASE_URLas a fallback with the same pooled/direct Postgres URLDEEPSEEK_API_KEYfor production/api/user/chatand/api/admin/chat- Optional
DEEPSEEK_BASE_URLandDEEPSEEK_TIMEOUT_SECONDS
- On Vercel, set browser-safe variables only when cloud UI reads Supabase
directly:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYNEXT_PUBLIC_AGENT_URLonly when the deployed web must still call a reachable agent. If omitted in production, the web uses the Vercel origin for cloud auth routes.
- On the local hardware machine, set only server-side bridge secrets:
TRASH_SORTER_SUPABASE_DATABASE_URL- Optional existing local-agent vars such as
TRASH_SORTER_AUTH_DATABASE_URL,DATABASE_URL, andDEEPSEEK_API_KEY.
The Vercel chat routes call DeepSeek server-side and read only role-scoped aggregate
history, map, alert, schedule, and enabled knowledge data. They never send session
tokens, database credentials, raw logs, image paths, or hardware secrets. User chat
shares the existing chat_usage table and enforces 36 requests per calendar month.
Chat responses use SSE (meta, delta, done, and safe error events) so the UI
renders the first provider tokens without waiting for the complete answer.
Run from the project root:
$env:TRASH_SORTER_SUPABASE_DATABASE_URL="postgresql://..."
python -m uv run python scripts/supabase_hardware_bridge.py --once
python -m uv run python scripts/supabase_hardware_bridge.py --interval 10The bridge syncs:
devicesbin_stationsbinsalerts- safe
historymetadata - current
training_jobsstatus metadata
It does not publish camera frames, local file paths, dataset images, passwords, session tokens, raw logs, or any endpoint for User-triggered camera/training actions.
Preview the persistent seed without writing:
python -m uv run python scripts/seed_supabase_demo_data.pyApply it after setting TRASH_SORTER_SUPABASE_DATABASE_URL or POSTGRES_URL:
python -m uv run python scripts/seed_supabase_demo_data.py --applyApply supabase/migrations/202606180005_performance_demo_seed.sql before the
current seed. Each active User receives three assigned stations, nine child bins,
one online demo device, collection/alert records, and 240 deterministic,
idempotent history rows spanning 180 days. Set
NEXT_PUBLIC_DEMO_HARDWARE_TARGET=1 on Vercel and
TRASH_SORTER_DEMO_HARDWARE_TARGET=1 on the hardware bridge. The latest bin
selected on the map receives the next local fullness reading; 95% or higher
is stored as full and displayed as Đã đầy.
Admin:
- Read/manage devices, bin map, alerts, schedules, issues, history, knowledge, and training metadata.
- Use local/admin APIs for camera, dataset, settings, model, audio, UART, and training controls.
User:
- Read only assigned active stations, child bins, alerts, schedules, own history, and role-allowed knowledge.
- Insert only assigned collection events and device issues.
- Cannot update roles, model, audio, device inventory, global map metadata, training jobs, camera stream, dataset, settings, logs, or admin APIs.
The migration writes narrow events to public.realtime_events:
bin_status_changedalert_createdalert_resolvedcollection_completeddevice_issue_createddevice_status_changed
Frontend code should subscribe to this table or mirror these rows into Supabase Broadcast. Payloads intentionally contain IDs/status values, not raw row dumps.
GET /api/user/analytics?range_days=7|30|90|180GET /api/user/history?limit=&offset=&range_days=GET /api/user/deviceGET /api/user/report?range_days=GET /api/user/experience?range_days=GET /api/user/dashboard-summary?range_days=7|30|90|180POST /api/user/advisorGET /api/user/history/export.csv?range_days=
These routes require an active User session. Missing sessions return 401,
non-User sessions return 403, and all database reads use the session username.
The dashboard uses the summary route to authenticate once and reuse one analytics
aggregate for report and experience output; the individual routes remain available
for dedicated screens and backward compatibility.
Before deploying:
python -m uv run pytest -q
cd web
npm run build
npm run test:e2eAlso manually verify:
- User account cannot open
/admin?tab=training. - User token receives 403 from camera/training/dataset/settings/model/log APIs.
- Map marker popup shows
Đã đầywhen a bin reaches95%or statusfull. - Supabase RLS tests confirm User can read only assigned rows.
- Ask EcoPet
Hôm nay bạn thế nào?and confirm the reply is relevant Vietnamese with diacritics, not the hardware bridge fallback. - Temporarily test an invalid AI key in Preview and confirm the response is the accented safe fallback without exposing the key or provider response body.