- Add Flask-Login browser session auth (separate from device X-Api-Key auth)
- modules/user_auth.py: User model, require_login decorator, bcrypt helpers
- /login with CSRF token, /logout
- scripts/create_user.py to provision admin user
- Add server-side trip computation from tracking_points
- db.compute_trips_for_device(): gap > 5 min = new trip, haversine distance,
upserts into trips table, auto-triggered in bg thread after each upload batch
- db.list_trips(), db.get_trip_route(): same JSON shape as Pi API
- db.get_overview_stats(): aggregated totals for overview page
- db.list_dashcam_files(), db.get_dashcam_file(): browse/stream helpers
- Add web portal to server/app.py
- Overview page (/): stat cards + recent trips + recent clips
- Trip history (/history): reuses Pi history.js + data-list.js unchanged
via /api/trips + /api/trip/<id> compatibility aliases
- Dashcam archive (/dashcam): file list + inline video player
- /api/v1/portal/dashcam/stream/<id>: Range-request streaming, path traversal guard
- /api/geocode: same Nominatim→reverse_geocoder offline fallback as Pi
- Add server templates (server/templates/)
- base.html: shared shell with 'Remote server' badge in sidebar + mobile topbar
- login.html, index.html, history.html, dashcam.html, components.html
- DB schema: users + trips tables added to init.sql (fresh deploys) +
migrate_phase24.sql (existing deployments)
- Infrastructure: Dockerfile uses repo-root build context to copy shared
static/ assets; docker-compose updated; SECRET_KEY + DEVICE_NAME in .env;
requirements: Flask-Login, bcrypt, reverse_geocoder
Co-authored-by: Copilot <[email protected]>
75 lines
3.0 KiB
SQL
75 lines
3.0 KiB
SQL
-- yaris-obd remote server schema
|
|
-- Applied automatically on first Postgres container start (mounted into
|
|
-- /docker-entrypoint-initdb.d/), so it only ever runs once against a fresh
|
|
-- empty database volume.
|
|
|
|
CREATE TABLE IF NOT EXISTS devices (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE, -- e.g. "yaris-2013"
|
|
api_key_hash TEXT NOT NULL, -- sha256 hex digest of the device's API key, never the raw key
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
last_seen_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tracking_points (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
|
vehicle_name TEXT,
|
|
driver_name TEXT,
|
|
recorded_at TIMESTAMPTZ NOT NULL, -- timestamp taken on the Pi when the point was captured
|
|
gps_lat DOUBLE PRECISION,
|
|
gps_lon DOUBLE PRECISION,
|
|
speed DOUBLE PRECISION,
|
|
odometer DOUBLE PRECISION,
|
|
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
-- Prevent the same locally-queued row from being inserted twice on retry
|
|
UNIQUE (device_id, recorded_at)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tracking_points_device_time
|
|
ON tracking_points (device_id, recorded_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS dashcam_files (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
|
relative_path TEXT NOT NULL, -- original sub-folder path on the dashcam SD card
|
|
size_bytes BIGINT NOT NULL,
|
|
sha256 TEXT NOT NULL,
|
|
storage_path TEXT NOT NULL, -- where the file landed on the server's disk
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (device_id, relative_path, sha256)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_dashcam_files_device
|
|
ON dashcam_files (device_id, uploaded_at DESC);
|
|
|
|
-- ── Phase 24: browser auth + server-side trips ─────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id SERIAL PRIMARY KEY,
|
|
username TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL, -- bcrypt hash
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS trips (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
|
start_time TIMESTAMPTZ NOT NULL,
|
|
end_time TIMESTAMPTZ NOT NULL,
|
|
distance_km DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
duration_s INTEGER NOT NULL DEFAULT 0,
|
|
avg_speed_kmh DOUBLE PRECISION,
|
|
max_speed_kmh DOUBLE PRECISION,
|
|
start_lat DOUBLE PRECISION,
|
|
start_lon DOUBLE PRECISION,
|
|
end_lat DOUBLE PRECISION,
|
|
end_lon DOUBLE PRECISION,
|
|
point_count INTEGER NOT NULL DEFAULT 0,
|
|
computed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (device_id, start_time)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_trips_device_start
|
|
ON trips (device_id, start_time DESC);
|