11 KiB
Copilot instructions for infoscreen_2025
Use this as your shared context when proposing changes. Keep edits minimal and match existing patterns referenced below.
Big picture
- Multi-service app orchestrated by Docker Compose.
- API: Flask + SQLAlchemy (MariaDB), in
server/exposed on :8000 (health:/health). - Dashboard: React + Vite in
dashboard/, dev on :5173, served via Nginx in prod. - MQTT broker: Eclipse Mosquitto, config in
mosquitto/config/mosquitto.conf. - Listener: MQTT consumer handling discovery + heartbeats in
listener/listener.py. - Scheduler: Publishes active events (per group) to MQTT retained topics in
scheduler/scheduler.py. - Nginx: Reverse proxy routes
/api/*and/screenshots/*to API; everything else to dashboard (nginx.conf).
- API: Flask + SQLAlchemy (MariaDB), in
Service boundaries & data flow
-
Database connection string is passed as
DB_CONN(mysql+pymysql) to Python services.- API builds its engine in
server/database.py(loads.envonly in development). - Scheduler loads
DB_CONNinscheduler/db_utils.py. - Listener also creates its own engine for writes to
clients.
- API builds its engine in
-
MQTT topics (paho-mqtt v2, use Callback API v2):
- Discovery:
infoscreen/discovery(JSON includesuuid, hw/ip data). ACK toinfoscreen/{uuid}/discovery_ack. Seelistener/listener.py. - Heartbeat:
infoscreen/{uuid}/heartbeatupdatesClient.last_alive(UTC). - Event lists (retained):
infoscreen/events/{group_id}fromscheduler/scheduler.py. - Per-client group assignment (retained):
infoscreen/{uuid}/group_idviaserver/mqtt_helper.py.
- Discovery:
-
Screenshots: server-side folders
server/received_screenshots/andserver/screenshots/; Nginx exposes/screenshots/{uuid}.jpgviaserver/wsgi.pyroute. -
Presentation conversion (PPT/PPTX/ODP → PDF):
- Trigger: on upload in
server/routes/eventmedia.pyfor media typesppt|pptx|odp(compute sha256, upsertConversion, enqueue job). - Worker: RQ worker runs
server.worker.convert_event_media_to_pdf, calls Gotenberg LibreOffice endpoint, writes toserver/media/converted/. - Services: Redis (queue) and Gotenberg added in compose; worker service consumes the
conversionsqueue. - Env:
REDIS_URL(defaultredis://redis:6379/0),GOTENBERG_URL(defaulthttp://gotenberg:3000). - Endpoints:
POST /api/conversions/<media_id>/pdf(ensure/enqueue),GET /api/conversions/<media_id>/status,GET /api/files/converted/<path>(serve PDFs). - Storage: originals under
server/media/…, outputs underserver/media/converted/(prod compose mounts a shared volume for this path).
- Trigger: on upload in
Data model highlights (see models/models.py)
-
Enums:
EventType(presentation, website, video, message, webuntis),MediaType(file/website types), andAcademicPeriodType(schuljahr, semester, trimester). -
Tables:
clients,client_groups,events,event_media,users,academic_periods,school_holidays. -
Academic periods:
academic_periodstable supports educational institution cycles (school years, semesters). Events and media can be optionally linked viaacademic_period_id(nullable for backward compatibility). -
Times are stored as timezone-aware; treat comparisons in UTC (see scheduler and routes/events).
-
Conversions:
- Enum
ConversionStatus:pending,processing,ready,failed. - Table
conversions:id,source_event_media_id(FK→event_media.idondelete CASCADE),target_format,target_path,status,file_hash(sha256),started_at,completed_at,error_message. - Indexes:
(source_event_media_id, target_format),(status, target_format); Unique:(source_event_media_id, target_format, file_hash).
- Enum
API patterns
- Blueprints live in
server/routes/*and are registered inserver/wsgi.pywith/api/...prefixes. - Session usage: instantiate
Session()per request, commit when mutating, and alwayssession.close()before returning. - Examples:
- Clients:
server/routes/clients.pyincludes bulk group updates and MQTT sync (publish_multiple_client_groups). - Groups:
server/routes/groups.pycomputes “alive” using a grace period that varies byENV. - Events:
server/routes/events.pyserializes enum values to strings and normalizes times to UTC. - Media:
server/routes/eventmedia.pyimplements a simple file manager API rooted atserver/media/. - Academic periods:
server/routes/academic_periods.pyexposes:GET /api/academic_periods— list all periodsGET /api/academic_periods/active— currently active periodPOST /api/academic_periods/active— set active period (deactivates others)GET /api/academic_periods/for_date?date=YYYY-MM-DD— period covering given date
- Clients:
Frontend patterns (dashboard)
-
Vite React app; proxies
/apiand/screenshotsto API in dev (vite.config.ts). -
Uses Syncfusion components; Vite config pre-bundles specific packages to avoid alias issues.
-
Environment:
VITE_API_URLprovided at build/run; in dev compose, proxy handles/apiso local fetches can use relative/api/...paths. -
Theming: Syncfusion Material 3 theme is used. All component CSS is imported centrally in
dashboard/src/main.tsx(base, navigations, buttons, inputs, dropdowns, popups, kanban, grids, schedule, filemanager, notifications, layouts, lists, calendars, splitbuttons, icons). Tailwind CSS has been removed. -
Scheduler (appointments page): top bar includes Group and Academic Period selectors (Syncfusion DropDownList). Selecting a period calls
POST /api/academic_periods/active, moves the calendar to today’s month/day within the period year, and refreshes a right-aligned indicator row showing:- Holidays present in the current view (count)
- Period label (display_name or name) with a badge indicating whether any holidays exist in that period (overlap check)
-
Program info page (
dashboard/src/programminfo.tsx):- Loads data from
dashboard/public/program-info.json(app name, version, build info, tech stack, changelog). - Uses Syncfusion card classes (
e-card,e-card-header,e-card-title,e-card-content) for consistent styling. - Changelog is paginated with
PagerComponent(from@syncfusion/ej2-react-grids), default page size 5; adjustpageSizeor add a selector as needed.
- Loads data from
-
Groups page (
dashboard/src/infoscreen_groups.tsx):- Migrated to Syncfusion inputs and popups: Buttons, TextBox, DropDownList, Dialog; Kanban remains for drag/drop.
- Unified toast/dialog wording; replaced legacy alerts with toasts; spacing handled via inline styles to avoid Tailwind dependency.
Note: Syncfusion usage in the dashboard is already documented above; if a UI for conversion status/downloads is added later, link its routes and components here.
Local development
- Compose: development is
docker-compose.yml+docker-compose.override.yml.- API (dev):
server/Dockerfile.devwith debugpy on 5678, Flask appwsgi:appon :8000. - Dashboard (dev):
dashboard/Dockerfile.devexposes :5173 and waits for API viadashboard/wait-for-backend.sh. - Mosquitto: allows anonymous in dev; WebSocket on :9001.
- API (dev):
- Common env vars:
DB_CONN,DB_USER,DB_PASSWORD,DB_HOST=db,DB_NAME,ENV,MQTT_USER,MQTT_PASSWORD.- Alembic: prod compose runs
alembic ... upgrade headandserver/init_defaults.pybefore gunicorn. - Local dev: prefer
python server/initialize_database.pyfor one-shot setup (migrations + defaults + academic periods). server/init_academic_periods.pyremains available to (re)seed school years.
- Alembic: prod compose runs
Production
docker-compose.prod.ymluses prebuilt images (ghcr.io/robbstarkaustria/*).- Nginx serves dashboard and proxies API; TLS certs expected in
certs/and mounted to/etc/nginx/certs.
Environment variables (reference)
- DB_CONN — Preferred DB URL for services. Example:
mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME} - DB_USER, DB_PASSWORD, DB_NAME, DB_HOST — Used to assemble DB_CONN in dev if missing; inside containers
DB_HOST=db. - ENV —
developmentorproduction; in development,server/database.pyloads.env. - MQTT_BROKER_HOST, MQTT_BROKER_PORT — Defaults
mqttand1883; MQTT_USER/MQTT_PASSWORD optional (dev often anonymous per Mosquitto config). - VITE_API_URL — Dashboard build-time base URL (prod); in dev the Vite proxy serves
/apitoserver:8000. - HEARTBEAT_GRACE_PERIOD_DEV / HEARTBEAT_GRACE_PERIOD_PROD — Groups “alive” window (defaults ~15s dev / 180s prod).
- REFRESH_SECONDS — Optional scheduler republish interval;
0disables periodic refresh.
Conventions & gotchas
- Always compare datetimes in UTC; some DB values may be naive—normalize before comparing (see
routes/events.py). - Use retained MQTT messages for state that clients must recover after reconnect (events per group, client group_id).
- In-container DB host is
db; do not uselocalhostinside services. - No separate dev vs prod secret conventions: use the same env var keys across environments (e.g.,
DB_CONN,MQTT_USER,MQTT_PASSWORD). - When adding a new route:
- Create a Blueprint in
server/routes/..., - Register it in
server/wsgi.py, - Manage
Session()lifecycle, and - Return JSON-safe values (serialize enums and datetimes).
- Create a Blueprint in
- When extending media types, update
MediaTypeand any logic ineventmediaand dashboard that depends on it. - Academic periods: Events/media can be optionally associated with periods for educational organization. Only one period should be active at a time (
is_active=True). - Initialization scripts: legacy DB init scripts were removed; use Alembic and
initialize_database.pygoing forward.
Quick examples
- Add client description persists to DB and publishes group via MQTT: see
PUT /api/clients/<uuid>/descriptioninroutes/clients.py. - Bulk group assignment emits retained messages for each client:
PUT /api/clients/group. - Listener heartbeat path:
infoscreen/<uuid>/heartbeat→ setsclients.last_alive.
Questions or unclear areas? Tell us if you need: exact devcontainer debugging steps, stricter Alembic workflow, or a seed dataset beyond init_defaults.py.
Academic Periods System
- Purpose: Organize events and media by educational cycles (school years, semesters, trimesters).
- Design: Fully backward compatible - existing events/media continue to work without period assignment.
- Usage: New events/media can optionally reference
academic_period_idfor better organization and filtering. - Constraints: Only one period can be active at a time; use
init_academic_periods.pyfor Austrian school year setup. - UI Integration: The dashboard highlights the currently selected period and whether a holiday plan exists within that date range. Holiday linkage currently uses date overlap with
school_holidays; an explicitacademic_period_idonschool_holidayscan be added later if tighter association is required.
Changelog Style Guide (Program info)
- Source:
dashboard/public/program-info.json; newest entry first - Fields per release:
version,date(YYYY-MM-DD),changes(array of short bullets) - Tone: concise, user-facing; German wording; area prefixes allowed (e.g., “UI: …”, “API: …”)
- Categories via emoji or words: Added (🆕/✨), Changed (🛠️), Fixed (✅/🐛), Removed (🗑️), Security (🔒), Deprecated (⚠️)
- Breaking changes must be prefixed with
BREAKING: - Keep ≤ 8–10 bullets; summarize or group micro-changes
- JSON hygiene: valid JSON, no trailing commas, don’t edit historical entries except typos