Files
infoscreen/.github/copilot-instructions.md
RobbStarkAustria 4d807be6f8 UI: switch to Syncfusion M3, remove Tailwind;
paginate changelog; docs updated; bump to 2025.1.0-alpha.8
2025-10-11 12:10:12 +00:00

11 KiB
Raw Blame History

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).

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 .env only in development).
    • Scheduler loads DB_CONN in scheduler/db_utils.py.
    • Listener also creates its own engine for writes to clients.
  • MQTT topics (paho-mqtt v2, use Callback API v2):

    • Discovery: infoscreen/discovery (JSON includes uuid, hw/ip data). ACK to infoscreen/{uuid}/discovery_ack. See listener/listener.py.
    • Heartbeat: infoscreen/{uuid}/heartbeat updates Client.last_alive (UTC).
    • Event lists (retained): infoscreen/events/{group_id} from scheduler/scheduler.py.
    • Per-client group assignment (retained): infoscreen/{uuid}/group_id via server/mqtt_helper.py.
  • Screenshots: server-side folders server/received_screenshots/ and server/screenshots/; Nginx exposes /screenshots/{uuid}.jpg via server/wsgi.py route.

  • Presentation conversion (PPT/PPTX/ODP → PDF):

    • Trigger: on upload in server/routes/eventmedia.py for media types ppt|pptx|odp (compute sha256, upsert Conversion, enqueue job).
    • Worker: RQ worker runs server.worker.convert_event_media_to_pdf, calls Gotenberg LibreOffice endpoint, writes to server/media/converted/.
    • Services: Redis (queue) and Gotenberg added in compose; worker service consumes the conversions queue.
    • Env: REDIS_URL (default redis://redis:6379/0), GOTENBERG_URL (default http://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 under server/media/converted/ (prod compose mounts a shared volume for this path).

Data model highlights (see models/models.py)

  • Enums: EventType (presentation, website, video, message, webuntis), MediaType (file/website types), and AcademicPeriodType (schuljahr, semester, trimester).

  • Tables: clients, client_groups, events, event_media, users, academic_periods, school_holidays.

  • Academic periods: academic_periods table supports educational institution cycles (school years, semesters). Events and media can be optionally linked via academic_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.id ondelete 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).

API patterns

  • Blueprints live in server/routes/* and are registered in server/wsgi.py with /api/... prefixes.
  • Session usage: instantiate Session() per request, commit when mutating, and always session.close() before returning.
  • Examples:
    • Clients: server/routes/clients.py includes bulk group updates and MQTT sync (publish_multiple_client_groups).
    • Groups: server/routes/groups.py computes “alive” using a grace period that varies by ENV.
    • Events: server/routes/events.py serializes enum values to strings and normalizes times to UTC.
    • Media: server/routes/eventmedia.py implements a simple file manager API rooted at server/media/.
    • Academic periods: server/routes/academic_periods.py exposes:
      • GET /api/academic_periods — list all periods
      • GET /api/academic_periods/active — currently active period
      • POST /api/academic_periods/active — set active period (deactivates others)
      • GET /api/academic_periods/for_date?date=YYYY-MM-DD — period covering given date

Frontend patterns (dashboard)

  • Vite React app; proxies /api and /screenshots to API in dev (vite.config.ts).

  • Uses Syncfusion components; Vite config pre-bundles specific packages to avoid alias issues.

  • Environment: VITE_API_URL provided at build/run; in dev compose, proxy handles /api so 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 todays 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; adjust pageSize or add a selector as needed.
  • 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.dev with debugpy on 5678, Flask app wsgi:app on :8000.
    • Dashboard (dev): dashboard/Dockerfile.dev exposes :5173 and waits for API via dashboard/wait-for-backend.sh.
    • Mosquitto: allows anonymous in dev; WebSocket on :9001.
  • 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 head and server/init_defaults.py before gunicorn.
    • Local dev: prefer python server/initialize_database.py for one-shot setup (migrations + defaults + academic periods).
    • server/init_academic_periods.py remains available to (re)seed school years.

Production

  • docker-compose.prod.yml uses 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 — development or production; in development, server/database.py loads .env.
  • MQTT_BROKER_HOST, MQTT_BROKER_PORT — Defaults mqtt and 1883; 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 /api to server:8000.
  • HEARTBEAT_GRACE_PERIOD_DEV / HEARTBEAT_GRACE_PERIOD_PROD — Groups “alive” window (defaults ~15s dev / 180s prod).
  • REFRESH_SECONDS — Optional scheduler republish interval; 0 disables 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 use localhost inside 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:
    1. Create a Blueprint in server/routes/...,
    2. Register it in server/wsgi.py,
    3. Manage Session() lifecycle, and
    4. Return JSON-safe values (serialize enums and datetimes).
  • When extending media types, update MediaType and any logic in eventmedia and 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.py going forward.

Quick examples

  • Add client description persists to DB and publishes group via MQTT: see PUT /api/clients/<uuid>/description in routes/clients.py.
  • Bulk group assignment emits retained messages for each client: PUT /api/clients/group.
  • Listener heartbeat path: infoscreen/<uuid>/heartbeat → sets clients.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_id for better organization and filtering.
  • Constraints: Only one period can be active at a time; use init_academic_periods.py for 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 explicit academic_period_id on school_holidays can 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 ≤ 810 bullets; summarize or group micro-changes
  • JSON hygiene: valid JSON, no trailing commas, dont edit historical entries except typos