46602f1933
Remove dead code identified in docs/SANITIZATION-REVIEW.md:
- app/blueprints/content_old.py and app/blueprints/playlist.py
- app/models/group.py, app/utils/nginx_config_reader.py
- orphaned templates (content_list, edit_content, upload_content,
player_page) and the related group/Template references
Result: 6 blueprints, 82 routes, no dead modules or orphan templates.
Add files that deploy.sh and docker-entrypoint.sh already require but
which were never tracked:
- https_manager.py (referenced by deploy.sh, migrate_network.sh,
docker-entrypoint.sh)
- Caddyfile.example (seeded by deploy.sh; its absence aborts deploy)
Relocate generated Graphify artifacts from graphify-out/ to
docs/graphify-out/ (110 files, no content change) and archive the
superseded docs under docs/.
Ignore hygiene:
- ignore ad-hoc .env backups (.env.bak*) — they contain live secrets
- keep the pre-sanitization snapshots (docs/legacy code/,
docs/old_code_documentation/) on disk but out of the repo
Fix .env.example: drop a duplicated config block, genericize the
hardcoded host IP, and document HOSTNAME_INTERNAL.
198 lines
8.0 KiB
Markdown
198 lines
8.0 KiB
Markdown
# 01 · System Architecture
|
||
|
||
> Generated from the Graphify knowledge graph (41 communities) and source analysis of `digiserver-v2`.
|
||
|
||
---
|
||
|
||
## 1. System Overview
|
||
|
||
DigiServer v2 is a Flask application serving **three audiences**:
|
||
|
||
1. **Humans (admins)** — manage content, playlists, players, users, HTTPS via the web UI.
|
||
2. **Players (physical signage)** — poll the REST API for playlists, send status feedback, upload edited media.
|
||
3. **Operators** — deploy/update player software remotely over SSH.
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph Browser["Web UI (Admins)"]
|
||
A[Login]
|
||
B[Dashboard / Admin / Content / Players]
|
||
end
|
||
|
||
subgraph App["digiserver-app (Flask :5000)"]
|
||
C[Gunicorn x4]
|
||
D[7 Blueprints]
|
||
E[SQLAlchemy + SQLite]
|
||
F[Cache / Background tasks]
|
||
end
|
||
|
||
subgraph Players["Signage Players"]
|
||
G[Player 1]
|
||
H[Player N]
|
||
end
|
||
|
||
subgraph Proxy["Reverse Proxy"]
|
||
I[Caddy :80/:443]
|
||
J[Umbrella nginx gateway - optional SSO]
|
||
end
|
||
|
||
I -->|reverse_proxy digiserver-app:5000| C
|
||
J -.X-Auth-Username .-> C
|
||
D --> E
|
||
G <-->|"/api/*"| D
|
||
H <-->|"/api/*"| D
|
||
Browser --> I
|
||
C -.SSH deploy/rsync.-> G
|
||
C -.SSH deploy/rsync.-> H
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Layered Architecture
|
||
|
||
Graphify assigns each node a **level** (0 = entry/global → 3 = utility):
|
||
|
||
| Layer | Level | Components |
|
||
|---|---|---|
|
||
| **L0 — Entry / Global** | 0 | `create_app()`, `app.py`, config classes, error handlers, CLI commands |
|
||
| **L1 — Strategic / Core** | 1 | Blueprint route handlers (players, content, api, admin), model classes |
|
||
| **L2 — Implementation** | 2 | Playlist management helpers, processing helpers, model methods |
|
||
| **L3 — Utility** | 3 | `logger.py`, `ssh_deploy.py`, `caddy_manager.py`, `uploads.py`, `pptx_converter.py`, migrations |
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph L0["L0 · Entry"]
|
||
create_app["create_app()"]
|
||
ProxyFix
|
||
ScriptNameFix
|
||
portal_sso["init_portal_sso()"]
|
||
end
|
||
subgraph L1["L1 · Blueprints + Models"]
|
||
bp["main · auth · admin · players · content · playlist · api"]
|
||
models["User Player Content Playlist PlayerEdit PlayerFeedback PlayerUser HTTPSConfig ServerLog"]
|
||
end
|
||
subgraph L2["L2 · Implementation"]
|
||
bg["run_background_task()"]
|
||
dep["deploy_player_to_host()"]
|
||
caddy["CaddyConfigGenerator"]
|
||
pb["build_player_files() / write_base_config()"]
|
||
end
|
||
subgraph L3["L3 · Utility"]
|
||
log["log_action()"]
|
||
up["uploads.py"]
|
||
pptx["pptx_converter.py"]
|
||
end
|
||
create_app --> bp
|
||
create_app --> models
|
||
bp --> log
|
||
bp --> bg
|
||
bp --> dep
|
||
bp --> caddy
|
||
bp --> pb
|
||
bp --> up
|
||
bp --> pptx
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Component Map (files → communities)
|
||
|
||
Graphify clustered the code into **41 communities**. The 12 meaningful ones are shown below (communities 13–40 are mostly migrations, isolated helper functions, and archived code).
|
||
|
||
| Community | Domain (derived) | Files | Role |
|
||
|---|---|---|---|
|
||
| **C0** (90) | **Authentication + audit** | `blueprints/auth.py`, `models/server_log.py`, `utils/logger.py`, `old_code_documentation/blueprint_groups.py` | Auth flows + audit logging |
|
||
| **C1** (80) | **Admin + HTTPS + player users** | `blueprints/admin.py`, `models/https_config.py`, `models/player_user.py`, `utils/caddy_manager.py`, `migrations/add_player_user_table.py` | Admin panel, Caddy HTTPS generation, editing-user registry |
|
||
| **C2** (68) | **Playlist & content workflows** | `blueprints/content.py`, `blueprints/playlist.py`, `models/playlist.py` | Modern playlist-centric content management + legacy redirects |
|
||
| **C3** (62) | **Player API + edits** | `blueprints/api.py`, `models/player_edit.py` | Player-facing REST, edited-media pipeline |
|
||
| **C4** (55) | **Application core** | `app.py`, `config.py`, `models/user.py`, `utils/portal_sso.py`, `utils/script_name_fix.py` | App factory, config, auth identity, middleware |
|
||
| **C5** (54) | **Content & player models** | `models/content.py`, `models/player.py`, `models/player_feedback.py`, `blueprints/main.py` | Media model, player model, feedback, dashboard |
|
||
| **C6** (51) | **Player management UI** | `blueprints/players.py`, `migrations/add_https_config_table.py` | Player CRUD, manage page, deployment polling |
|
||
| **C7** (42) | ***(removed)*** | — | Groups model + legacy nginx reader were deleted during sanitization |
|
||
| **C8** (24) | **Dev tooling** | `old_code_documentation/test_edit_media_api.py`, `Colors`, integrity checker | Test/analysis utilities |
|
||
| **C9** (21) | **Upload processing** | `utils/uploads.py` | Upload progress, video/image processing |
|
||
| **C10** (20) | **Deployment** | `utils/background_tasks.py`, `utils/ssh_deploy.py` | Background SSH deployment engine |
|
||
| **C11** (19) | **Player build/staging** | `utils/player_build.py` | Stage player source code on server |
|
||
| **C12** (8) | **PPTX conversion** | `utils/pptx_converter.py` | LibreOffice PPTX → PDF → PNG |
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
C0["C0 · Auth & logging"]
|
||
C1["C1 · Admin & HTTPS"]
|
||
C2["C2 · Playlists & content"]
|
||
C3["C3 · Player API & edits"]
|
||
C4["C4 · App core"]
|
||
C5["C5 · Content/player models"]
|
||
C6["C6 · Player management UI"]
|
||
C9["C9 · Upload processing"]
|
||
C10["C10 · Deployment"]
|
||
C11["C11 · Player build"]
|
||
C12["C12 · PPTX conversion"]
|
||
|
||
C1 -->|27 edges| C3
|
||
C2 -->|25 edges| C3
|
||
C2 -->|25 edges| C0
|
||
C3 -->|25 edges| C1
|
||
C1 -->|21 edges| C0
|
||
C6 -->|16 edges| C1
|
||
C6 -->|16 edges| C3
|
||
C3 -->|14 edges| C0
|
||
C6 -->|14 edges| C0
|
||
C5 -->|9 edges| C3
|
||
C9 -->|5 edges| C0
|
||
C1 -->|3 edges| C11
|
||
C10 --> C6
|
||
C11 --> C10
|
||
C12 --> C9
|
||
```
|
||
|
||
*(edge weights from `graphify-out/metadata.json` communityLinks)*
|
||
|
||
---
|
||
|
||
## 4. Middleware & Request Pipeline
|
||
|
||
Every request flows through three layers before reaching a blueprint:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
R["Raw request"] --> PF["werkzeug ProxyFix"]
|
||
PF --> SF["ScriptNameFix"]
|
||
SF --> SSO["portal_sso (before_request)"]
|
||
SSO --> BP["Blueprint route"]
|
||
BP --> DB[("SQLAlchemy / SQLite")]
|
||
```
|
||
|
||
1. **ProxyFix** — trusts `X-Forwarded-For/Proto/Host/Port` from Caddy/nginx (1 hop).
|
||
2. **ScriptNameFix** — reads `X-Script-Name` (e.g. `/digiserver`) so `url_for()` produces correct prefixed URLs behind an umbrella nginx gateway.
|
||
3. **Portal SSO** (`portal_sso.py`) — if the umbrella nginx sends `X-Auth-Username` / `X-Auth-Role`, automatically create + log in the local user.
|
||
|
||
---
|
||
|
||
## 5. Key Architectural Decisions
|
||
|
||
| Decision | Implementation |
|
||
|---|---|
|
||
| **Application factory** | `create_app(config_name)` — clean testability, per-env config |
|
||
| **Blueprint isolation** | 7 blueprints with clear URL prefixes; API separate from UI |
|
||
| **Playlist versioning** | `Playlist.version` incremented on every mutation → players poll `/api/playlist-version/<id>` for refresh |
|
||
| **In-memory caching** | Flask-Caching `@cache.memoize(300)` on playlist builders; `cache.clear()` on mutations |
|
||
| **DB-backed audit log** | `log_action()` writes `ServerLog` rows; surfaced on dashboard & admin |
|
||
| **Two auth systems** | Humans: Flask-Login (`User`). Players: `auth_code` Bearer + `quickconnect_code` (bcrypt) |
|
||
| **Remote provisioning** | SSH + rsync/git staged code → write `app_config.json` → run install/start scripts |
|
||
| **HTTPS automation** | `CaddyConfigGenerator` writes `Caddyfile`; reloads Caddy via admin API on `:2019` |
|
||
|
||
---
|
||
|
||
## 6. Runtime Dependencies (requirements.txt)
|
||
|
||
**Runtime:** Flask 3.1, Werkzeug 3.1, SQLAlchemy 2.0.37, Flask-SQLAlchemy 3.1.1, Flask-Migrate, Flask-Bcrypt, Flask-Login, Flask-Caching, Flask-Cors, Flask-Talisman, pdf2image, Pillow, ffmpeg-python, python-magic, bcrypt, cryptography, gunicorn 23, psutil, python-dotenv.
|
||
|
||
**System binaries (Docker):** `poppler-utils`, `ffmpeg`, `libmagic1`, `LibreOffice` (core/impress/writer), `fonts-noto-color-emoji`, `sshpass`, `openssh-client`, `rsync`, `git`.
|
||
|
||
**Dev:** black, flake8, pytest, pytest-cov. *(`gevent` is commented out — incompatible with Python 3.13.)*
|
||
|
||
---
|
||
|
||
> Next: [02 · Knowledge Graph](02-knowledge-graph.md)
|