38 Commits

Author SHA1 Message Date
tliu93 da236643f2 M2: frontend walkthrough fixes + explicit dev compose stack
frontend / frontend (push) Successful in 2m0s
pytest / test (push) Successful in 1m32s
Post-M2 self-walkthrough polish, batched into one commit.

Map / heat:
- fix heat-layer white-screen crash after login (add layer to map before
  setLatLngs; an off-map leaflet.heat layer has a null _map and throws)
- normalize each heat layer to the densest pixel cell visible in the CURRENT
  viewport (maxZoom:0 so intensity factor f=1) and recompute on moveend/zoomend,
  so sparse poo data reaches red and stays normalized at any zoom level
- dark CARTO basemap tiles when the color scheme is dark

UI:
- dark-mode toggle in the top-right, beside the settings gear
- switch top-right nav (records / theme / settings / logout) to Feather icons
  with hover tooltips
- home: Grafana-style quick time-range presets + back/forward shift buttons,
  placed between the From/To pickers and Apply; fix Select/tooltip z-index
  (Leaflet stacking) and the shift-button height alignment

API client:
- stop flooding GET /api/session with 401s: the session probe and the login
  endpoint own their 401s (no global redirect), which fixes the logout hang and
  the spinning login page

Compose:
- rename docker-compose.override.yml -> docker-compose.dev.yml as an explicit,
  non-auto-layered dev stack (8001, -dev container names, prod-copy ./data DB);
  update tests/test_deployment.py (read dev.yml, tolerate the !override tag) and
  the README "Docker Compose" section

Tests:
- pixel-grid peak counter, time-range presets, heat-layer ordering regression,
  and 401-redirect regression
2026-06-13 15:20:50 +02:00
tliu93 bd09523e94 M2-T13: docs wrap-up + retire frontend constraints + dependency cleanup
- README: add 前端 v2 (React SPA) section (dev/build/codegen/hosting/gates),
  update directory listing, drop stale Jinja descriptions
- architecture-overview: retire '不引入前后端分离' constraint; reflect SPA + JSON API
- roadmap: mark M2 done
- remove orphaned jinja2 dependency (recompile requirements*.txt; no other churn)
- delete empty tests/test_auth.py stub; drop dead _extract_csrf_token in test_api_data
- verified image still builds and app imports with the slimmer deps
2026-06-13 15:20:50 +02:00
tliu93 51f712f602 M2-T12: multi-stage Dockerfile (node build -> python runtime) + frontend CI
- Dockerfile: node:22-slim stage runs npm ci + npm run build; python runtime
  stage COPY --from copies dist to /app/frontend/dist (matches SPA_DIST_DIR);
  runtime image has no node
- .dockerignore: exclude frontend/node_modules and frontend/dist from context
- .github/workflows/frontend.yml: npm ci + codegen-sync + lint/typecheck/test/build
- tests/test_deployment.py: skip COPY --from sources in the context-existence
  check; assert the multi-stage frontend build wiring
- verified with a real docker build (image serves SPA, no node at runtime)
2026-06-13 15:20:50 +02:00
tliu93 a9830c42d8 M2-T11: serve React SPA from FastAPI and remove Jinja pages
- app/main.py serves the SPA build (SPA_DIST_DIR, default frontend/dist):
  mounts /assets and a GET catch-all returning index.html for client routes;
  catch-all 404s on /api/*, never swallows /docs, /openapi.json, /static, assets,
  ingestion/ticktick/status; skips SPA serving when dist absent (backend-only CI)
- delete app/api/routes/pages.py, app/api/routes/auth.py, app/templates/
  (all replaced by /api/* + SPA; auth service layer kept)
- remove/replace Jinja page tests (JSON coverage already in test_api_*);
  add tests/test_spa_hosting.py for the fallback contract
- regenerate openapi/ (Jinja paths gone) and frontend schema.d.ts
2026-06-13 15:20:50 +02:00
tliu93 2bc5d6ea9a M2-T05: add SMTP test action API (POST /api/config/smtp/test)
- reuses send_smtp_test_email; tri-state result success(200)/config-error(400)/failed(502)
- session + CSRF protected; never echoes SMTP secrets
- SmtpTestResponse schema; regenerate openapi/
- extend tests/test_api_config.py (3 states + 401 + missing-CSRF 403)
2026-06-13 15:20:50 +02:00
tliu93 048414c5cb M2-T04: add single-row record CRUD API (patch/delete)
- PATCH/DELETE /api/locations/{person}/{datetime} and /api/poo/{timestamp}
- update only non-PK fields (PK immutable); 404 on missing PK
- delete scoped to exact full PK with rowcount guard (0->404, 1->ok);
  no batch/truncate/drop path
- session + CSRF protected; bare ingestion endpoints untouched
- service helpers in app/services/location.py and poo.py; regenerate openapi/
- tests/test_api_record_crud.py
2026-06-12 23:33:08 +02:00
tliu93 0fba7cfe11 M2-T03: add read-only data JSON API
- GET /api/locations (inclusive time window start/end, pagination, cap 5000)
- GET /api/poo (pagination, cap 1000, newest first)
- GET /api/public-ip (current state + recent history, cap 1000)
- all session-protected, read-only, bounded (no full-table export)
- typed response schemas; register router; regenerate openapi/
- tests/test_api_data.py
2026-06-12 23:24:17 +02:00
tliu93 8da1f13e60 M2-T02: add session/auth JSON API for the SPA
- GET /api/session (user + csrf_token, 401 when unauthenticated)
- POST /api/auth/login (sets HttpOnly session cookie; 401 on bad creds; no CSRF)
- POST /api/auth/logout (session+CSRF; revokes session, clears cookie; 204)
- POST /api/auth/password (session+CSRF; reuses change_password; 400 on failure; 204)
- reuses app/services/auth.py and shared require_session/require_csrf deps
- register router in app/main.py; regenerate openapi/
- tests/test_api_session.py
2026-06-12 23:15:56 +02:00
tliu93 c2b1b7b751 M2-T01: add config JSON API (GET/PUT /api/config)
- new app/api/routes/api/ package with shared require_session (401) and
  require_csrf (presence-only X-CSRF-Token, 403) dependencies
- GET /api/config returns masked config sections; PUT /api/config reuses
  save_config_updates (blank secret keeps old; invalid -> 422, no write)
- session-protected; PUT also CSRF-protected
- register router in app/main.py; regenerate openapi/
- tests/test_api_config.py
2026-06-12 23:08:14 +02:00
tliu93 3628ac51e5 chore(m2): green the ruff baseline before M2 orchestration
- ignore E402 in scripts/*.py (deliberate sys.path bootstrap before app imports)
- drop unused pathlib.Path import in tests/test_auth.py

Establishes a clean ruff gate so each M2 task can be verified green at its boundary.
2026-06-12 22:56:21 +02:00
tliu93 c1a5d7a425 fix(docker): stop COPYing removed alembic_location/alembic_poo into the image
pytest / test (push) Successful in 50s
docker-image / build-and-push (push) Successful in 3m52s
M1-T04 deleted the alembic_location / alembic_poo chains and their .ini files,
but the Dockerfile still COPYed those four paths, so the release image build
failed at 'COPY alembic_poo.ini ./' (path not found). Drop the four stale COPY
lines (only alembic_app remains). Add test_dockerfile_copy_sources_exist, which
asserts every Dockerfile COPY source exists in the build context, so this class
of breakage fails pytest instead of only surfacing in the image CI.

Verified with a real local 'docker build' (succeeds) and pytest (98 passed).
2026-06-12 20:48:34 +02:00
tliu93 a337b06c94 M1-rework: rename leftover pk_cols param in reconciliation test stubs
pytest / test (push) Successful in 48s
pytest / test (pull_request) Failing after 10m49s
Round-2 audit nit (review-notes/M1-full-review-2.md): two _always_fail
monkeypatch stubs still named their (ignored) third positional parameter
pk_cols after _reconcile switched to a full columns list. Rename to columns
for consistency. Test-only, no behavior change.

pytest 97 passed; ruff clean.
2026-06-12 19:11:13 +02:00
tliu93 1cbe6c46d2 M1-rework: harden legacy-migration reconciliation to full-row equality
Audit finding (review-notes/M1-full-review-1.md, FINDING 1): _reconcile only
checked primary-key presence, so a source row skipped by INSERT OR IGNORE due
to a value difference against a pre-existing same-PK target row would
false-pass. Compare ALL columns with SQLite's NULL-safe IS operator instead,
so reconciliation is a true full-row guarantee (idempotent re-runs still pass
because the rows match column-for-column). Add tests for the value-mismatch
abort and for idempotency under full-row reconciliation. Remove the now-unused
pk_cols parameter.

pytest 97 passed; ruff clean (pre-existing only); data-safety grep still empty.
2026-06-12 19:05:56 +02:00
tliu93 af8c602988 M1-T05: drop location/poo database config from Settings and tests
Remove the dead location_database_url / poo_database_url fields and the
location_sqlite_path / poo_sqlite_path computed properties from Settings;
drop them from the config-page payload and from .env.example. Update the
test hardcodes (test_config, test_public_ip, test_smtp) and reduce the
conftest test_database_urls fixture to the single app DB. The one-time
migration script keeps reading legacy URLs from env/CLI, independent of
Settings.

pytest 95 passed; ruff clean (pre-existing only).
2026-06-12 16:57:54 +02:00
tliu93 0d898e09f2 M1-T04: converge startup chain onto the single app DB
run_all_migrations() now adopts/initializes only the app DB and returns
{'app': ...}. app/main.py drops the location/poo readiness checks
(ensure_location_db_ready / ensure_poo_db_ready) and their imports;
ensure_runtime_dirs only provisions the app DB path; lifespan still
fail-closes on a missing/unmanaged app DB. Delete the retired
location/poo adopt scripts and the alembic_location / alembic_poo
chains. Update tests to single-DB expectations and drop the obsolete
location/poo adoption + readiness tests.

pytest 95 passed; ruff clean (pre-existing only); a fresh app DB
initialized via scripts.run_migrations contains location + poo_records.
2026-06-12 16:50:05 +02:00
tliu93 3d3c2bcc57 M1-T03: unify data layer, models, deps and routes onto single app DB
Collapse the three data layers into one. app/db.py now exposes a single
Base, a cached engine bound to app_database_url with SQLite WAL enabled, and
get_engine/get_session_local/reset_db_caches/get_db_session. Delete
app/auth_db.py, app/poo_db.py and app/models/base.py. All models (auth,
config, public_ip, location, poo) inherit the one Base and register on a
single metadata. Dependencies converge to a single get_db; all routes use it.

Also update the alembic env.py files (app/location/poo) and tests that
imported the removed modules so the suite stays green, and drop the obsolete
test_legacy_style_location_db test whose flow (app reading a separate location
DB) no longer exists. Location/poo Alembic chains, adopt scripts and adoption
tests remain for M1-T04; config fields remain for M1-T05.

pytest 109 passed; ruff clean (pre-existing only); WAL verified; single
Base.metadata holds all seven tables.
2026-06-12 16:35:07 +02:00
tliu93 bc8dd062d5 M1-T02: add idempotent legacy data migration script
scripts/migrate_legacy_data.py copies rows from the legacy locationRecorder.db
/ pooRecorder.db into the unified app DB's location / poo_records tables using
ATTACH + INSERT OR IGNORE (idempotent via PK-conflict skip; explicit columns,
never SELECT *). After copy it reconciles every source row against the target
and raises / exits non-zero on any shortfall. Missing legacy files are a safe
no-op (skipped); --dry-run writes nothing. Not part of the Alembic chain; run
manually once at cut-over. Never deletes or overwrites any file.

Validated end-to-end on copies of the real production DBs: dry-run reported
75103 location + 874 poo rows and wrote nothing; the real run copied all rows
with reconciliation passing; a second run copied 0 (idempotent).
2026-06-12 16:13:55 +02:00
tliu93 779e160b95 add ip change notification and refine sender display
pytest / test (push) Successful in 57s
pytest / test (pull_request) Successful in 54s
2026-04-29 13:03:12 +02:00
tliu93 3ea3498e58 add smtp module and testing 2026-04-29 12:11:10 +02:00
tliu93 5a420bd37b add get public and storage feature 2026-04-29 11:45:49 +02:00
tliu93 4acdd2dc60 fix ci test
pytest / test (push) Successful in 45s
pytest / test (pull_request) Successful in 44s
2026-04-22 13:31:26 +02:00
tliu93 a76d6bfb71 change adoption to separate step
pytest / test (push) Failing after 46s
pytest / test (pull_request) Failing after 45s
2026-04-22 13:28:00 +02:00
tliu93 35aee79d93 Restore legacy poo inbound dispatch
pytest / test (push) Successful in 43s
docker-image / build-and-push (push) Successful in 3m38s
2026-04-20 23:33:57 +02:00
tliu93 1805d5d8ea Finalize first Python release
pytest / test (push) Successful in 40s
pytest / test (pull_request) Successful in 41s
2026-04-20 20:40:04 +02:00
tliu93 795c84f177 Stabilize auth tests in CI
pytest / test (push) Successful in 43s
2026-04-20 17:43:24 +02:00
tliu93 fe0409dafe Refine runtime config and redirect settings 2026-04-20 17:36:05 +02:00
tliu93 982af62f4f Migrate TickTick OAuth and action tasks 2026-04-20 17:06:03 +02:00
tliu93 179aae264e Persist runtime config in app db and seed from env 2026-04-20 15:56:10 +02:00
tliu93 3f7c9e43d9 Switch auth password hashing to Argon2 2026-04-20 15:26:36 +02:00
tliu93 e1aad408ab Add auth foundation and app DB management 2026-04-20 15:16:47 +02:00
tliu93 044b47c573 Migrate poo recorder and align Alembic naming 2026-04-20 11:48:48 +02:00
tliu93 e334df992f Add Home Assistant inbound gateway 2026-04-20 10:42:35 +02:00
tliu93 151ad46275 Add Home Assistant outbound adapter 2026-04-20 10:11:02 +02:00
tliu93 d0dc8e893a Tighten location request validation 2026-04-19 23:18:20 +02:00
tliu93 1a2f9c75d9 Harden location db startup validation 2026-04-19 23:02:43 +02:00
tliu93 8aeb0723c1 Add location db adoption runbook 2026-04-19 21:57:31 +02:00
tliu93 32cc6847fd Migrate location recorder and refine db config 2026-04-19 21:39:23 +02:00
tliu93 31390882ef Bootstrap Python rewrite skeleton 2026-04-19 20:19:58 +02:00