updates to digiserver app server

This commit is contained in:
2026-09-10 21:03:16 +03:00
parent 046f5e5efd
commit 1c5186463a
129 changed files with 33014 additions and 17 deletions
+18
View File
@@ -837,6 +837,24 @@ def receive_edited_media():
with open(metadata_path, 'w') as f: with open(metadata_path, 'w') as f:
json.dump(metadata, f, indent=2) json.dump(metadata, f, indent=2)
# ── Preserve the original filename ──────────────────────────────
# `content.filename` is (re)pointed at the latest edit below so the
# player downloads the newest version. The pristine original must be
# remembered separately, otherwise the original is lost forever and
# the UI shows the edited image as the "original".
if not content.original_filename:
if not content.filename.startswith('edited_media/'):
# Normal case: current filename is still the pristine upload.
content.original_filename = content.filename
else:
# Re-process: content.filename already points at an edited
# file. Recover the pristine original from the first (v1) edit.
first_edit = PlayerEdit.query.filter_by(content_id=content.id)\
.order_by(PlayerEdit.version.asc(), PlayerEdit.created_at.asc())\
.first()
if first_edit and first_edit.original_name:
content.original_filename = first_edit.original_name
# ── Point Content.filename to the latest edit ──────────────────── # ── Point Content.filename to the latest edit ────────────────────
# This tells the player to download the latest edited version. # This tells the player to download the latest edited version.
old_filename = content.filename old_filename = content.filename
+6
View File
@@ -829,6 +829,7 @@ def process_file_in_background(app, filepath: str, filename: str, file_ext: str,
page_filename = os.path.basename(page_file) page_filename = os.path.basename(page_file)
page_content = Content( page_content = Content(
filename=page_filename, filename=page_filename,
original_filename=page_filename,
content_type='image', content_type='image',
duration=duration, duration=duration,
file_size=os.path.getsize(page_file) file_size=os.path.getsize(page_file)
@@ -886,6 +887,7 @@ def process_file_in_background(app, filepath: str, filename: str, file_ext: str,
slide_filename = os.path.basename(slide_file) slide_filename = os.path.basename(slide_file)
slide_content = Content( slide_content = Content(
filename=slide_filename, filename=slide_filename,
original_filename=slide_filename,
content_type='image', content_type='image',
duration=duration, duration=duration,
file_size=os.path.getsize(slide_file) file_size=os.path.getsize(slide_file)
@@ -930,6 +932,7 @@ def process_file_in_background(app, filepath: str, filename: str, file_ext: str,
if processing_success and os.path.exists(filepath): if processing_success and os.path.exists(filepath):
content = Content( content = Content(
filename=filename, filename=filename,
original_filename=filename,
content_type=detected_type, content_type=detected_type,
duration=duration, duration=duration,
file_size=os.path.getsize(filepath) file_size=os.path.getsize(filepath)
@@ -1313,6 +1316,7 @@ def upload_media():
# Create content record for page # Create content record for page
page_content = Content( page_content = Content(
filename=page_filename, filename=page_filename,
original_filename=page_filename,
content_type='image', content_type='image',
duration=duration, duration=duration,
file_size=os.path.getsize(page_file) file_size=os.path.getsize(page_file)
@@ -1373,6 +1377,7 @@ def upload_media():
# Create content record for slide # Create content record for slide
slide_content = Content( slide_content = Content(
filename=slide_filename, filename=slide_filename,
original_filename=slide_filename,
content_type='image', content_type='image',
duration=duration, duration=duration,
file_size=os.path.getsize(slide_file) file_size=os.path.getsize(slide_file)
@@ -1419,6 +1424,7 @@ def upload_media():
if os.path.exists(filepath): if os.path.exists(filepath):
content = Content( content = Content(
filename=filename, filename=filename,
original_filename=filename,
content_type=detected_type, content_type=detected_type,
duration=duration, duration=duration,
file_size=os.path.getsize(filepath) file_size=os.path.getsize(filepath)
+48
View File
@@ -21,6 +21,10 @@ class Content(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False, unique=True, index=True) filename = db.Column(db.String(255), nullable=False, unique=True, index=True)
# The pristine original filename as uploaded. `filename` gets overwritten
# with an edited_media/... path when a player edits the content, so this
# column preserves the original so it can always be referenced/restored.
original_filename = db.Column(db.String(255), nullable=True, index=True)
content_type = db.Column(db.String(50), nullable=False, index=True) content_type = db.Column(db.String(50), nullable=False, index=True)
duration = db.Column(db.Integer, default=10, nullable=True) duration = db.Column(db.Integer, default=10, nullable=True)
file_size = db.Column(db.BigInteger, nullable=True) file_size = db.Column(db.BigInteger, nullable=True)
@@ -52,6 +56,28 @@ class Content(db.Model):
def group_count(self) -> int: def group_count(self) -> int:
"""Get number of groups containing this content.""" """Get number of groups containing this content."""
return self.groups.count() return self.groups.count()
@property
def original_display_name(self) -> str:
"""Name of the original (unedited) file for display purposes."""
return self.original_filename or self.filename
@property
def original_media_path(self) -> str:
"""Path (relative to uploads/) where the original unedited file lives.
When a player edits content the original file is archived under
edited_media/<id>/original_<name>. If never edited, the original IS the
current file.
"""
if self.original_filename and self.original_filename != self.filename:
return f"edited_media/{self.id}/original_{self.original_filename}"
return self.filename
@property
def current_media_path(self) -> str:
"""Path (relative to uploads/) of the current/latest version to display."""
return self.filename
def is_image(self) -> bool: def is_image(self) -> bool:
"""Check if content is an image.""" """Check if content is an image."""
@@ -68,3 +94,25 @@ class Content(db.Model):
def is_weblink(self) -> bool: def is_weblink(self) -> bool:
"""Check if content is a web link (URL) rather than an uploaded file.""" """Check if content is a web link (URL) rather than an uploaded file."""
return self.content_type == 'weblink' return self.content_type == 'weblink'
@property
def has_player_edits(self) -> bool:
"""Whether this content has been edited on a player."""
return self.edits.count() > 0
@property
def original_display_name(self) -> str:
"""Display name of the original (unedited) file."""
return self.original_filename or self.filename
@property
def original_media_path(self) -> str:
"""uploads/-relative path of the pristine original file.
After a player edit the original is archived at
edited_media/<id>/original_<name>; before any edit it is simply the
current file.
"""
if self.original_filename and self.filename != self.original_filename:
return f"edited_media/{self.id}/original_{self.original_filename}"
return self.filename
+4 -4
View File
@@ -326,11 +326,11 @@
<div class="media-library" style="max-height: 350px; overflow-y: auto;"> <div class="media-library" style="max-height: 350px; overflow-y: auto;">
{% if media_files %} {% if media_files %}
{% for media in media_files %} {% for media in media_files %}
<div class="media-item" title="{{ media.filename }}"> <div class="media-item" title="{{ media.original_display_name }}">
{% if media.content_type == 'image' %} {% if media.content_type == 'image' %}
<div class="media-thumbnail" style="width: 100%; height: 100px; overflow: hidden; border-radius: 6px; margin-bottom: 8px; background: #f0f0f0; display: flex; align-items: center; justify-content: center;"> <div class="media-thumbnail" style="width: 100%; height: 100px; overflow: hidden; border-radius: 6px; margin-bottom: 8px; background: #f0f0f0; display: flex; align-items: center; justify-content: center;">
<img src="{{ url_for('static', filename='uploads/' + media.filename) }}" <img src="{{ url_for('static', filename='uploads/' + media.current_media_path) }}"
alt="{{ media.filename }}" alt="{{ media.original_display_name }}"
style="max-width: 100%; max-height: 100%; object-fit: cover;" style="max-width: 100%; max-height: 100%; object-fit: cover;"
onerror="this.style.display='none'; this.parentElement.innerHTML='<span style=\'font-size: 48px;\'>📷</span>'"> onerror="this.style.display='none'; this.parentElement.innerHTML='<span style=\'font-size: 48px;\'>📷</span>'">
</div> </div>
@@ -347,7 +347,7 @@
📁 📁
</div> </div>
{% endif %} {% endif %}
<div class="media-name" style="font-size: 11px; line-height: 1.3;">{{ media.filename[:25] }}{% if media.filename|length > 25 %}...{% endif %}</div> <div class="media-name" style="font-size: 11px; line-height: 1.3;">{{ media.original_display_name[:25] }}{% if media.original_display_name|length > 25 %}...{% endif %}</div>
<div style="font-size: 10px; color: #999; margin-top: 4px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div> <div style="font-size: 10px; color: #999; margin-top: 4px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
</div> </div>
{% endfor %} {% endfor %}
@@ -318,7 +318,7 @@
{% if content.content_type == 'weblink' %} {% if content.content_type == 'weblink' %}
<a href="{{ content.url }}" target="_blank" rel="noopener noreferrer">{{ content.url }}</a> <a href="{{ content.url }}" target="_blank" rel="noopener noreferrer">{{ content.url }}</a>
{% else %} {% else %}
{{ content.filename }} {{ content.original_display_name }}
{% endif %} {% endif %}
</td> </td>
<td> <td>
@@ -434,7 +434,7 @@
{% elif content.content_type == 'video' %}🎥 {% elif content.content_type == 'video' %}🎥
{% elif content.content_type == 'pdf' %}📄 {% elif content.content_type == 'pdf' %}📄
{% else %}📁{% endif %} {% else %}📁{% endif %}
{{ content.filename }} {{ content.original_display_name }}
</div> </div>
<div style="font-size: 12px; color: #999;"> <div style="font-size: 12px; color: #999;">
{{ content.file_size_mb }} MB {{ content.file_size_mb }} MB
+7 -7
View File
@@ -275,11 +275,11 @@
<div class="media-card"> <div class="media-card">
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }}, {{ media.edit_count }})" title="Delete">🗑️</button> <button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }}, {{ media.edit_count }})" title="Delete">🗑️</button>
<div class="media-thumbnail"> <div class="media-thumbnail">
<img src="{{ url_for('static', filename='uploads/' + media.filename) }}" <img src="{{ url_for('static', filename='uploads/' + media.current_media_path) }}"
alt="{{ media.filename }}" alt="{{ media.original_display_name }}"
onerror="this.style.display='none'; this.parentElement.innerHTML='<span class=\'media-icon\'>📷</span>'"> onerror="this.style.display='none'; this.parentElement.innerHTML='<span class=\'media-icon\'>📷</span>'">
</div> </div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div> <div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info"> <div class="media-info">
<span class="type-badge image">Image</span> <span class="type-badge image">Image</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div> <div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
@@ -308,7 +308,7 @@
<div class="media-thumbnail"> <div class="media-thumbnail">
<span class="media-icon">🎥</span> <span class="media-icon">🎥</span>
</div> </div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div> <div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info"> <div class="media-info">
<span class="type-badge video">Video</span> <span class="type-badge video">Video</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div> <div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
@@ -337,7 +337,7 @@
<div class="media-thumbnail"> <div class="media-thumbnail">
<span class="media-icon">📄</span> <span class="media-icon">📄</span>
</div> </div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div> <div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info"> <div class="media-info">
<span class="type-badge pdf">PDF</span> <span class="type-badge pdf">PDF</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div> <div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
@@ -366,7 +366,7 @@
<div class="media-thumbnail"> <div class="media-thumbnail">
<span class="media-icon">📊</span> <span class="media-icon">📊</span>
</div> </div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div> <div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info"> <div class="media-info">
<span class="type-badge pptx">PPTX</span> <span class="type-badge pptx">PPTX</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div> <div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
@@ -395,7 +395,7 @@
<div class="media-thumbnail"> <div class="media-thumbnail">
<span class="media-icon">📁</span> <span class="media-icon">📁</span>
</div> </div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div> <div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info"> <div class="media-info">
<span class="type-badge">{{ media.content_type }}</span> <span class="type-badge">{{ media.content_type }}</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div> <div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
+4 -4
View File
@@ -333,7 +333,7 @@
<div class="card-header"> <div class="card-header">
<div class="card-header-title"> <div class="card-header-title">
<span class="card-header-icon"></span> <span class="card-header-icon"></span>
<span>📄 {{ original_content.filename if original_content else data.original_name }}</span> <span>📄 {{ original_content.original_display_name if original_content else data.original_name }}</span>
<span style="font-size: 0.9rem; color: #64748b; font-weight: normal;"> <span style="font-size: 0.9rem; color: #64748b; font-weight: normal;">
({{ data.versions|length + 1 }} version{{ 's' if (data.versions|length + 1) > 1 else '' }}) ({{ data.versions|length + 1 }} version{{ 's' if (data.versions|length + 1) > 1 else '' }})
</span> </span>
@@ -420,10 +420,10 @@
{% if original_content %} {% if original_content %}
<div class="version-item" <div class="version-item"
id="version-{{ content_id }}-original" id="version-{{ content_id }}-original"
onclick="event.stopPropagation(); selectVersion({{ content_id }}, 'original', '{{ original_content.filename }}', 'System', 'N/A', '{{ original_content.uploaded_at | localtime('%Y-%m-%d %H:%M') }}', '{{ url_for('static', filename='uploads/' ~ original_content.filename) }}')"> onclick="event.stopPropagation(); selectVersion({{ content_id }}, 'original', '{{ original_content.original_display_name }}', 'System', 'N/A', '{{ original_content.uploaded_at | localtime('%Y-%m-%d %H:%M') }}', '{{ url_for('static', filename='uploads/' ~ original_content.original_media_path) }}')">
<div class="version-thumbnail"> <div class="version-thumbnail">
{% if original_content.filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')) %} {% if original_content.original_display_name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')) %}
<img src="{{ url_for('static', filename='uploads/' ~ original_content.filename) }}" <img src="{{ url_for('static', filename='uploads/' ~ original_content.original_media_path) }}"
alt="Original"> alt="Original">
{% else %} {% else %}
<div style="color: white; font-size: 2rem;">📄</div> <div style="color: white; font-size: 2rem;">📄</div>
+2
View File
@@ -110,6 +110,8 @@ echo -e " • Adding email to https_config..."
docker compose exec -T digiserver-app python /app/migrations/add_email_to_https_config.py docker compose exec -T digiserver-app python /app/migrations/add_email_to_https_config.py
echo -e " • Migrating player_user global settings..." echo -e " • Migrating player_user global settings..."
docker compose exec -T digiserver-app python /app/migrations/migrate_player_user_global.py docker compose exec -T digiserver-app python /app/migrations/migrate_player_user_global.py
echo -e " • Adding original_filename to content..."
docker compose exec -T digiserver-app python /app/migrations/add_original_filename_to_content.py
echo -e "${GREEN}✅ All database migrations completed${NC}" echo -e "${GREEN}✅ All database migrations completed${NC}"
echo "" echo ""
+198
View File
@@ -0,0 +1,198 @@
# 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/group 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"]
nginx["NginxConfigReader"]
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 1340 are mostly migrations, isolated helper functions, and archived code).
| Community | Domain (derived) | Files | Role |
|---|---|---|---|
| **C0** (90) | **Authentication + legacy groups** | `blueprints/auth.py`, `blueprints/content_old.py`, `models/server_log.py`, `utils/logger.py`, `utils/group_player_management.py`, `old_code_documentation/blueprint_groups.py` | Auth flows + audit logging + (legacy) group features |
| **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) | **Groups (legacy)** | `models/group.py`, `utils/nginx_config_reader.py` | Archived groups feature + legacy nginx reader |
| **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)
+148
View File
@@ -0,0 +1,148 @@
# 02 · The Graphify Knowledge Graph
This document explains the **interactive knowledge graph** generated by the [Graphify](https://marketplace.visualstudio.com/items?itemName=anytechiestudio.graphify-vscode) extension, and how to use it to explore and maintain DigiServer v2.
---
## 1. What Was Generated
Running `Graphify: Build Knowledge Graph` on the project root produced `graphify-out/`:
| Artifact | Description |
|---|---|
| `graph.html` | **Interactive visualizer** — zoom/pan/filter nodes, click to jump to source |
| `graph.json` | Raw graph data (nodes, links, communities, heat) |
| `GRAPH_REPORT.md` | God nodes, surprising connections, community list, knowledge gaps |
| `COMPASS.md` | Token-optimized architecture summary (god nodes, layers) |
| `DOMAINS.md` | Community ID → domain table |
| `intelligence.json` | AI-detected intelligence (god nodes, surprising links) |
| `graph.compact.txt` | Compact graph dump, ideal for LLM context |
| `wiki/` | Markdown articles per community + per god-node, with Mermaid diagrams |
**Graph statistics:**
```
631 nodes · 1162 edges · 41 communities
Extraction: 56% EXTRACTED · 44% INFERRED (avg confidence 0.62)
Node types: 313 FUNCTION · 300 FILE · 18 CLASS
Link types: 311 uses · 290 calls · 275 rationale_for · 243 contains · 39 method · 4 inherits
```
---
## 2. Opening the Visualizer
1. In VS Code, click the **Graphify** activity-bar icon (sidebar).
2. Click **Build Knowledge Graph** (or right-click any folder → *Graphify: Build Knowledge Graph*).
3. Once built, click **Open Interactive Visualizer** — or open `graphify-out/graph.html` directly in a browser.
> ⚠️ The visualizer opens best inside the VS Code webview (activity bar). Opening `graph.html` via `file://` may show a minor console error but still renders the graph data (nodes/edges/communities listed in the sidebar).
**View modes:** Deep Dive · Full Detail · Heatmap · Simulation (component-removal impact).
---
## 3. God Nodes — The Core Abstractions
These are the most-connected nodes (graph centrality). They are the architectural "load-bearing" abstractions:
| Rank | Node | Degree | File | Why it matters |
|---|---|---|---|---|
| 1 | `log_action()` | 116 | `app/utils/logger.py` | Every important action across all blueprints logs here |
| 2 | `PlayerEdit` | 99 | `app/models/player_edit.py` | Central record of on-player media edits |
| 3 | `PlayerUser` | 74 | `app/models/player_user.py` | Maps player edit user codes to names |
| 4 | `Playlist` | 33 | `app/models/playlist.py` | Core content organization + version sync |
| 5 | `CaddyConfigGenerator` | 31 | `app/utils/caddy_manager.py` | HTTPS Caddyfile generation/reload |
| 6 | `HTTPSConfig` | 30 | `app/models/https_config.py` | Persisted HTTPS settings |
| 7 | `Content` | 24 | `app/models/content.py` | Media model used by everything |
| 8 | `User` | 19 | `app/models/user.py` | Human accounts (admin/user/viewer) |
| 9 | `create_app()` | 13 | `app/app.py` | Application factory |
| 10 | `models/__init__.py` | 13 | `app/models/__init__.py` | Package export hub |
---
## 4. Heatmap — Hottest Code
`heat` (01) reflects how central/complex a node is in the graph. The hottest functions drive most of the system's behaviour:
| Heat | Function | File |
|---|---|---|
| 0.620 | `receive_edited_media()` | `app/blueprints/api.py` |
| 0.614 | `deploy_player_to_host()` | `app/utils/ssh_deploy.py` |
| 0.592 | `add_player()` | `app/blueprints/players.py` |
| 0.537 | `upload_media()` | `app/blueprints/content.py` |
| 0.429 | `manage_player()` | `app/blueprints/players.py` |
| 0.425 | `receive_player_feedback()` | `app/blueprints/api.py` |
| 0.407 | `update_https_config()` | `app/blueprints/admin.py` |
| 0.376 | `process_file_in_background()` | `app/blueprints/content.py` |
| 0.365 | `CaddyConfigGenerator` | `app/utils/caddy_manager.py` |
| 0.354 | `get_playlist_by_quickconnect()` | `app/blueprints/api.py` |
---
## 5. Communities (Clusters)
Graphify groups related code into communities. See [01-architecture.md §3](01-architecture.md#3-component-map-files--communities) for the full mapping. The largest:
- **C0 · Auth & logging** (90 nodes) — login/logout/register + audit logging + legacy groups
- **C1 · Admin & HTTPS** (80 nodes) — admin panel, Caddy generation, editing users
- **C2 · Playlists & content** (68 nodes) — the modern content/playlist workflow
- **C3 · Player API & edits** (62 nodes) — player-facing REST + edited-media pipeline
- **C4 · Application core** (55 nodes) — `create_app`, config, user, middleware
- **C5 · Content/player models** (54 nodes) — `Content`, `Player`, feedback, dashboard
Each community has a wiki article with a **Mermaid class diagram**, key concepts, source files, and an audit trail (extracted vs inferred edges). Example: `graphify-out/wiki/Community_4.md`.
---
## 6. Surprising Connections (AI-detected)
Graphify flags links that are non-obvious. Examples from `GRAPH_REPORT.md`:
- `reset_user_password()``log_action()` — admin password resets are audited
- `upload_header_logo()``log_action()` — logo changes are audited
- `delete_editing_user()``log_action()` — editing-user deletion is audited
- `delete_playlist()``log_action()` — playlist deletion is audited
- `Main playlist management page` uses `PlayerEdit` — the content UI surfaces edit counts
**Pattern:** virtually every destructive/admin action flows through `log_action()`, which is why it is the #1 god node.
---
## 7. Known Gaps in the Graph
- **178 isolated nodes** (≤1 connection) — mostly standalone migration scripts and model helper methods.
- **Thin communities 1340** — single-file migrations, isolated utility functions, and archived `old_code_documentation/` scripts. These are not "broken" — they are intentionally decoupled.
- The graph does **not** index templates or static assets (Python AST extraction only).
---
## 8. Querying the Graph (CLI)
The `anytechie-graphify` engine (installed in `.venv`) ships a CLI for graph-aware questions:
```bash
# Shortest path between two nodes
python -m graphify path "Content" "Playlist"
# Explain a node and its neighbours
python -m graphify explain "deploy_player_to_host()"
# BFS traversal to answer a question (no LLM)
python -m graphify query "how is a playlist synced to a player?"
# Grounded chat over the graph (requires an LLM API key)
python -m graphify ask "what happens when a player uploads edited media?"
```
---
## 9. Keeping the Graph Fresh
- **After code changes:** `Graphify: Build Knowledge Graph` again (or run `python -m graphify update <path>`).
- **Git hooks:** `python -m graphify hook install` auto-rebuilds on `post-commit`/`post-checkout`.
- The graph is stored in `graphify-out/` (not committed to git by default).
---
> Next: [03 · Data Model](03-data-model.md)
+198
View File
@@ -0,0 +1,198 @@
# 03 · Data Model
DigiServer v2 uses **SQLAlchemy 2.0** with **SQLite** (production: `instance/dashboard.db`, dev: `instance/dev.db`, tests: in-memory). All models live in `app/models/` and are re-exported from `app/models/__init__.py`.
---
## 1. Entity-Relationship Overview
```mermaid
erDiagram
user ||--o{ server_log : "writes"
player ||--o{ player_feedback : "sends (cascade)"
player ||--o{ player_edit : "edits (cascade)"
player }o--o| playlist : "assigned_to"
content ||--o{ player_edit : "edited (cascade)"
content ||--o{ player_feedback : "playing"
playlist ||--o{ content : "playlist_content M2M (position,duration,muted,edit_on_player_enabled)"
content }o--o{ group : "group_content M2M (legacy)"
player_user ||--o{ player_edit : "user_code"
https_config ||--o| https_config : "singleton row"
```
---
## 2. Tables & Columns
### `user` — human accounts
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `username` | String(80) | unique, NOT NULL, indexed |
| `password` | String(120) | bcrypt hash, NOT NULL |
| `role` | String(20) | default `'user'`; also `'admin'`, `'viewer'`; indexed |
| `theme` | String(20) | default `'light'` |
| `created_at` | DateTime | NOT NULL |
| `last_login` | DateTime | nullable |
Key methods: `is_admin` (property), `update_last_login()`. Mixin: `UserMixin` (Flask-Login).
### `player` — signage devices
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `name` | String(255) | NOT NULL |
| `hostname` | String(255) | unique, NOT NULL, indexed |
| `location` | String(255) | nullable |
| `auth_code` | String(255) | unique, NOT NULL, indexed (legacy Bearer auth) |
| `password_hash` | String(255) | NOT NULL (bcrypt) |
| `quickconnect_code` | String(255) | nullable, bcrypt-hashed |
| `orientation` | String(16) | default `'Landscape'` |
| `status` | String(50) | default `'offline'`, indexed |
| `last_seen` | DateTime | indexed |
| `last_heartbeat` | DateTime | indexed |
| `created_at` | DateTime | NOT NULL |
| `playlist_id` | Integer | FK → `playlist.id` ON DELETE SET NULL, indexed |
| `deployment_status` | String(50) | default `'pending'`; pending/deploying/deployed/failed |
| `last_deployment_at` | DateTime | nullable |
| `last_deployment_status` | String(50) | nullable |
| `last_deployment_message` | Text | nullable |
Relationships: `playlist`, `feedback` (→ PlayerFeedback, cascade delete-orphan).
Methods: `is_online` (5-min window), `update_status()`, `set_password()/check_password()`, `set_quickconnect_code()/check_quickconnect_code()`, static `authenticate(hostname, password, quickconnect_code)`.
### `content` — media items (also weblinks)
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `filename` | String(255) | unique, NOT NULL, indexed; may point to `edited_media/<id>/...` after a player edit |
| `original_filename` | String(255) | nullable, indexed — pristine upload name |
| `content_type` | String(50) | NOT NULL, indexed; image/video/pdf/pptx/weblink/other |
| `duration` | Integer | default 10, nullable |
| `file_size` | BigInteger | nullable |
| `url` | String(2048) | nullable — target URL for `weblink` content |
| `description` | Text | nullable |
| `uploaded_at` | DateTime | NOT NULL, indexed |
Relationships: `playlists` (M2M via `playlist_content`), `groups` (M2M via `group_content`).
Properties/methods: `file_size_mb`, `group_count`, `original_display_name`, `original_media_path`, `current_media_path`, `is_image()/is_video()/is_pdf()/is_weblink()`, `has_player_edits`.
### `playlist` — ordered collections of content
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `name` | String(100) | unique, NOT NULL, indexed |
| `description` | Text | nullable |
| `orientation` | String(20) | default `'Landscape'` |
| `version` | Integer | default 1, NOT NULL — **sync detection** |
| `is_active` | Boolean | default True |
| `created_at` | DateTime | NOT NULL |
| `updated_at` | DateTime | NOT NULL, onupdate |
Methods: `player_count`, `content_count`, `total_duration` (properties), `increment_version()`, `get_content_ordered()`.
### `playlist_content` — association table (M2M playlist ↔ content)
| Column | Type | Notes |
|---|---|---|
| `playlist_id` | Integer | FK → `playlist.id` CASCADE, composite PK |
| `content_id` | Integer | FK → `content.id` CASCADE, composite PK |
| `position` | Integer | default 0 — ordering |
| `duration` | Integer | default 10 — per-playlist override |
| `muted` | Boolean | default True |
| `edit_on_player_enabled` | Boolean | default False — whether player-side editing allowed |
### `player_edit` — on-player media edits
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `player_id` | Integer | FK → `player.id` CASCADE, indexed |
| `content_id` | Integer | FK → `content.id` CASCADE, indexed |
| `original_name` | String(255) | NOT NULL |
| `new_name` | String(255) | NOT NULL |
| `version` | Integer | default 1 |
| `user` | String(255) | nullable (user code) |
| `time_of_modification` | DateTime | nullable |
| `metadata_path` | String(512) | nullable |
| `edited_file_path` | String(512) | NOT NULL |
| `created_at` | DateTime | NOT NULL, indexed |
Relationships: `player`, `content`. Method: `to_dict()`.
### `player_feedback` — status messages from players
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `player_id` | Integer | FK → `player.id`, NOT NULL, indexed |
| `status` | String(50) | default `'unknown'` |
| `current_content_id` | Integer | FK → `content.id`, nullable |
| `message` | Text | nullable |
| `error` | Text | nullable |
| `timestamp` | DateTime | NOT NULL, indexed |
Properties/classmethod: `is_error`, `age_seconds`, `get_latest_for_player()`.
### `player_user` — mapping of player edit user codes
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `user_code` | String(255) | **globally unique**, NOT NULL, indexed |
| `user_name` | String(255) | nullable |
| `created_at` | DateTime | NOT NULL |
| `updated_at` | DateTime | NOT NULL, onupdate |
Method: `to_dict()`. *(Migrated from a per-player table — see `migrate_player_user_global.py`.)*
### `server_log` — DB-backed audit log
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `level` | String(20) | NOT NULL, indexed, default `'info'` |
| `message` | Text | NOT NULL |
| `timestamp` | DateTime | NOT NULL, indexed |
Classmethods: `log_info`, `log_warning`, `log_error`.
### `https_config` — HTTPS settings (singleton row)
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `https_enabled` | Boolean | default False |
| `hostname` | String(255) | nullable |
| `domain` | String(255) | nullable |
| `ip_address` | String(45) | nullable (IPv6-capable) |
| `email` | String(255) | nullable (Let's Encrypt contact) |
| `port` | Integer | default 443 |
| `created_at` | DateTime | NOT NULL |
| `updated_at` | DateTime | NOT NULL, onupdate |
| `updated_by` | String(255) | nullable |
Classmethods: `get_config()` (first row), `create_or_update(...)`. Method: `to_dict()`.
### `group` + `group_content` — **ARCHIVED / LEGACY**
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | PK |
| `name` | String(100) | unique, NOT NULL, indexed |
| `description` | Text | nullable |
| `created_at` / `updated_at` | DateTime | NOT NULL |
`group_content(group_id, content_id)` composite-PK M2M. The current `Player` model has **no** `group_id` column — group features are archived (see [09 · Legacy](09-legacy-and-migrations.md)).
---
## 3. Relationship Summary
| Relationship | Cardinality | FK / Mechanism |
|---|---|---|
| `playlist``content` | M:N | `playlist_content` (positioned, with extras) |
| `group``content` | M:N | `group_content` (legacy) |
| `player``playlist` | N:1 | `player.playlist_id` (ON DELETE SET NULL) |
| `player``player_feedback` | 1:N | `player_feedback.player_id` (cascade) |
| `player``player_edit` | 1:N | `player_edit.player_id` (cascade) |
| `content``player_edit` | 1:N | `player_edit.content_id` (cascade) |
| `player_user``player_edit` | 1:N | `player_edit.user` = `player_user.user_code` (logical) |
| `https_config` | 1 row | singleton via `get_config()` |
---
> Next: [04 · Application Core](04-application-core.md)
+178
View File
@@ -0,0 +1,178 @@
# 04 · Application Core
Covers the application factory, configuration, extensions, middleware, CLI commands, context processors, and template layer. This is **Community 4** in the Graphify knowledge graph.
---
## 1. Application Factory — `app/app.py`
The entire app is constructed by `create_app(config_name=None)`:
```mermaid
flowchart TD
A["create_app()"] --> B["Set instance_path"]
B --> C["Load config: Dev / Prod / Test"]
C --> D["ProxyFix middleware"]
D --> E["ScriptNameFix middleware"]
E --> F["init extensions: db bcrypt login migrate cache cors"]
F --> G["configure_login_manager()"]
G --> H["register_blueprints()"]
H --> I["register_error_handlers()"]
I --> J["register_commands()"]
J --> K["register_context_processors()"]
K --> L["register_template_filters()"]
L --> M["init_portal_sso(app)"]
M --> N["db.create_all() (idempotent)"]
N --> O["return app"]
```
### Blueprint registration
```python
def register_blueprints(app):
from app.blueprints.main import main_bp
from app.blueprints.auth import auth_bp
from app.blueprints.admin import admin_bp
from app.blueprints.players import players_bp
from app.blueprints.content import content_bp
from app.blueprints.playlist import playlist_bp
from app.blueprints.api import api_bp
...
```
> Note: `app.blueprints.content_old` is **not** imported — it is dead/legacy code.
---
## 2. Configuration — `app/config.py`
Four classes: `Config` (base) → `DevelopmentConfig`, `ProductionConfig`, `TestingConfig`.
| Setting | Value | Notes |
|---|---|---|
| `MAX_CONTENT_LENGTH` | 2 GB | upload cap |
| `UPLOAD_FOLDER` | `app/static/uploads` | media |
| `UPLOAD_FOLDERLOGO` | `app/static/resurse` | logos |
| `ALLOWED_EXTENSIONS` | png jpg jpeg gif bmp mp4 avi mkv mov webm pdf ppt pptx | |
| `PERMANENT_SESSION_LIFETIME` | 30 min | |
| `SESSION_COOKIE_SECURE` | False (True in prod) | |
| `ITEMS_PER_PAGE` | 20 | |
| `SERVER_VERSION` | 2.0.0 | shown in UI footer |
| `PLAYER_CODE_DIR` | `/app/data/player` | staged player source |
| `PLAYER_REPO_URL` | env `PLAYER_REPO_URL` | Kiwy-Signage repo |
| DB (dev) | `instance/dev.db` | |
| DB (prod) | `instance/dashboard.db` | |
| DB (test) | in-memory | |
---
## 3. Extensions — `app/extensions.py`
Centralizes shared singletons:
```python
db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager() # login_view='auth.login'
migrate = Migrate()
cache = Cache()
cors = CORS()
```
CORS is configured in `create_app` for `/api/*`: all origins, GET/POST/OPTIONS/PUT/DELETE, supports credentials.
---
## 4. Middleware
### ProxyFix (Werkzeug)
`app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)` — trusts one reverse-proxy hop (Caddy / umbrella nginx).
### ScriptNameFix — `app/utils/script_name_fix.py`
`ScriptNameFix` WSGI middleware sets `SCRIPT_NAME` from `HTTP_X_SCRIPT_NAME` so `url_for()` generates correct paths when the app is mounted under a sub-path (e.g. `/digiserver`).
---
## 5. Login Manager
```python
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Please log in to access this page.'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
```
---
## 6. Error Handlers
| Code | Template | Behaviour |
|---|---|---|
| 404 | `errors/404.html` | Not found |
| 403 | `errors/403.html` | Forbidden |
| 500 | `errors/500.html` | Rolls back DB session |
| 413 | `errors/413.html` | Payload too large |
| 408 | `errors/408.html` | Request timeout |
The `/api` blueprint registers its own **JSON** 404/405/500 handlers.
---
## 7. CLI Commands
| Command | Purpose |
|---|---|
| `flask init-db` | `db.create_all()` |
| `flask create-admin --username X` | Create admin user (prompts for password) |
| `flask seed-db` | Seed sample data (blocked in production) |
---
## 8. Context Processors
- **`inject_config`** — exposes `server_version`, `build_date`, `logo_exists` to all templates.
- **`inject_user_theme`** — exposes the authenticated user's `theme` (`light`/`dark`) for the UI toggle.
---
## 9. Template Filters
- **`localtime`** — converts naive/UTC datetimes to local time with a configurable `strftime` format (default `%Y-%m-%d %H:%M`).
---
## 10. Template & Static Layout
```
app/templates/
├── base.html ← main layout (theme, logos, nav)
├── dashboard.html
├── auth/ login.html, register.html, change_password.html
├── admin/ admin.html, user_management.html, leftover_media.html,
│ dependencies.html, customize_logos.html, editing_users.html,
│ https_config.html, build_player.html
├── content/ content_list.html (legacy), content_list_new.html (modern),
│ media_library.html, upload_content.html (legacy),
│ upload_media.html, manage_playlist_content.html, edit_content.html
├── players/ players_list.html, add_player.html, edit_player.html,
│ manage_player.html, player_page.html, player_fullscreen.html,
│ edited_media.html, edited_media_report.html, _deploy_badge.html
└── errors/ 403.html, 404.html, 500.html (+413/408)
app/static/
├── icons/ edit, home, info, monitor, moon, playlist, sun, trash, upload, warning (SVG)
├── uploads/ uploaded media + edited_media/<content_id>/ (versioned edits)
└── (resurse/ logo storage — referenced by config)
```
---
## 11. Portal SSO — `app/utils/portal_sso.py`
`init_portal_sso(app)` registers a `before_request` hook that reads `X-Auth-Username` / `X-Auth-Role` headers (set by the umbrella nginx gateway). If present, it auto-creates and logs in the local `User` (`_get_or_create_user`). This lets the app sit behind an existing corporate SSO portal.
---
> Next: [05 · Blueprints & API](05-blueprints-api.md)
+280
View File
@@ -0,0 +1,280 @@
# 05 · Blueprints & REST API
DigiServer v2 registers **7 active blueprints** (`content_old.py` is legacy dead code and is not registered).
---
## 1. Blueprint Overview
| Blueprint | URL prefix | Module | Focus |
|---|---|---|---|
| `main_bp` | `/` | `blueprints/main.py` | Dashboard, health |
| `auth_bp` | `/` | `blueprints/auth.py` | Login/logout/register/password |
| `admin_bp` | `/admin` | `blueprints/admin.py` | Admin panel, HTTPS, build, users, logos |
| `players_bp` | `/players` | `blueprints/players.py` | Player management |
| `content_bp` | `/content` | `blueprints/content.py` | Media library + modern playlist management |
| `playlist_bp` | `/playlist` | `blueprints/playlist.py` | Legacy per-player playlist (redirects) |
| `api_bp` | `/api` | `blueprints/api.py` | Player-facing REST + deployment API |
---
## 2. `main` — Dashboard & Health (`/`)
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/` | GET | `dashboard` | Counts (players/playlists/content), storage MB, 20 recent logs (cached 60s except `viewer` role) |
| `/health` | GET | `health` | Health JSON: `SELECT 1` DB ping + free disk space |
---
## 3. `auth` — Authentication (`/`)
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/login` | GET/POST | `login` | bcrypt login, `remember`, `next` redirect, failed-attempt logging |
| `/logout` | GET | `logout` | Logs out + logs action |
| `/register` | GET/POST | `register` | Self-registration → default `viewer` role |
| `/change-password` | GET/POST | `change_password` | Change own password (verifies current) |
---
## 4. `admin` — Admin Panel (`/admin`)
**Security:** `@login_required` on all routes; `@admin_required` (custom decorator, `role == 'admin'`) on user-management.
### Users
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/` | GET | `admin_panel` | Stats, recent logs, users, storage |
| `/user/create` | POST | `create_user` | Create user (username ≥3, password ≥6) |
| `/user/<id>/role` | POST | `change_user_role` | Change role (not own) |
| `/user/<id>/delete` | POST | `delete_user` | Delete user (not own) |
| `/user/<id>/password` | POST | `reset_user_password` | Admin reset password |
| `/users` | GET | `user_management` | User list page |
| `/theme` | POST | `change_theme` | light/dark theme |
### Media housekeeping
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/leftover-media` | GET | `leftover_media` | Content not assigned to any playlist, by type + size |
| `/delete-leftover-images` | POST | — | Bulk-delete leftover images (+ archive + PlayerEdit) |
| `/delete-leftover-videos` | POST | — | Bulk-delete leftover videos |
| `/delete-single-leftover/<id>` | POST | — | Delete one leftover item |
### System / dependencies
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/system/info` | GET | `system_info` | JSON: platform + psutil (CPU/mem/disk) |
| `/dependencies` | GET | `dependencies` | Checks LibreOffice, Poppler, FFmpeg, emoji fonts |
| `/install-libreoffice` | POST | — | `sudo -n install_libreoffice.sh` |
| `/install-emoji-fonts` | POST | — | `sudo -n install_emoji_fonts.sh` |
| `/logo/upload` | POST | `upload_logo` | Upload logo.png |
| `/logs/clear` | POST | `clear_logs` | Delete all `ServerLog` rows |
| `/customize-logos` | GET | `customize_logos` | Logo customization page |
| `/upload-header-logo` | POST | — | Save header_logo.png |
| `/upload-login-logo` | POST | — | Save login_logo.png |
### Editing users
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/editing-users` | GET | `manage_editing_users` | List `PlayerUser` + per-user edit counts |
| `/editing-users/<id>/update` | POST | — | Rename editing user |
| `/editing-users/<id>/delete` | POST | — | Delete editing user |
### HTTPS (Caddy)
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/https-config` | GET | `https_config` | HTTPS config page (auto-corrects DB flag if request is HTTPS) |
| `/https-config/update` | POST | `update_https_config` | Validate + save config → regenerate Caddyfile → reload Caddy |
| `/https-config/status` | GET | `https_config_status` | Config as JSON |
### Player build & deployment
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/build-player` | GET | `build_player` | Build page + staged-code status |
| `/build-player` | POST | `build_player_action` | `build_files` / `save_config` / `build_and_config` → clone/refresh code, write `app_config.json`, persist `instance/player_build.json` |
---
## 5. `players` — Player Management (`/players`)
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/` and `/list` | GET | `list` | Player list + live deployment-status polling data |
| `/add` | GET/POST | `add_player` | Create player; optional **background SSH deployment** |
| `/bulk/delete` | POST | — | JSON bulk delete |
| `/bulk/assign-playlist` | POST | — | JSON bulk assign to playlist |
| `/deployment-status` | GET | — | JSON deployment status for all players (polling) |
| `/<int:player_id>` | GET | — | Redirect to `manage_player` |
| `/<id>/edit` | GET/POST | — | Edit name/location (legacy) |
| `/<id>/delete` | POST | — | Delete player + feedback |
| `/<id>/regenerate-auth` | POST | — | New auth code |
| `/<id>/manage` | GET/POST | `manage_player` | Manage page: `update_credentials` / `assign_playlist`; feedback, edits, status |
| `/<id>/edited-media` | GET | `edited_media` | All edited media + user mapping |
| `/<id>/edited-media-report` | GET | `edited_media_report` | Tabular report |
| `/<id>/fullscreen` | GET | — | Player fullscreen (NO auth; optional `?auth=` check), cached playlist |
| `/<id>/reorder` | POST | — | Legacy stub → 400 "use Playlists page" |
| `/<id>/playlist/reorder` | POST | — | Legacy per-player reorder (JSON up/down) |
| `/<id>/playlist/remove` | POST | — | Legacy removal (bumps `playlist_version`) |
Helper: `get_player_playlist(player_id)``@cache.memoize(300)` builds playlist dicts (`url`, `type`, `duration`, `position`, `muted`, `audio`).
---
## 6. `content` — Media Library & Modern Playlist Management (`/content`)
> This is the **primary workflow** in v2 (1,519 lines).
### Library & uploads
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/` | GET | `content_list` | Main page `content_list_new.html`: playlists, last-3 media, counts, players |
| `/media-library` | GET | `media_library` | All media by type + edit counts |
| `/media/<id>/delete` | POST | `delete_media` | Delete file + archive + edits; removes from playlists + bumps versions |
| `/upload-media-page` | GET | `upload_media_page` | Upload page (playlist selector) |
| `/upload-media` | POST | `upload_media` | Core upload: images optimized, videos validated, **PDF/PPTX → Full-HD PNGs**, large files → background threads |
### Web links
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/add-weblink` | POST | `add_weblink` | Create weblink Content (http/https validated), optional playlist add |
| `/playlist/<id>/add-weblink` | POST | — | Create weblink + append to playlist |
### Playlist management
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/playlist/create` | POST | — | Create playlist (name, description, orientation) |
| `/playlist/<id>/delete` | POST | — | Delete + unassign players |
| `/playlist/<id>/manage` | GET | `manage_playlist_content` | Ordered content + available library (no weblinks) |
| `/playlist/<id>/add-content` | POST | — | Add library content at next position |
| `/playlist/<id>/remove-content/<content_id>` | POST | — | Remove content (deletes orphan weblinks) |
| `/playlist/<id>/bulk-remove` | POST | — | JSON bulk remove |
| `/playlist/<id>/reorder` | POST | — | JSON reorder (position = index) |
| `/playlist/<id>/update-muted/<content_id>` | POST | — | Toggle muted |
| `/playlist/<id>/update-edit-enabled/<content_id>` | POST | — | Toggle `edit_on_player_enabled` |
| `/playlist/<id>/update-duration/<content_id>` | POST | — | Per-playlist duration (≥1s) |
| `/player/<id>/assign-playlist` | POST | — | Assign/unassign player to playlist |
**All playlist mutations** call `Playlist.increment_version()` and `cache.clear()`.
### Processing helpers (module-level)
`process_image_file`, `process_video_file_extended` (ffprobe), `process_pdf_file` (pdf2image @300 DPI → `_pageNNN.png`), `process_presentation_file` (LibreOffice → PDF → pdftoppm → Full-HD PNG), `create_fullhd_image`, `resize_image_to_fullhd`, `optimize_image_to_fullhd`, `process_file_in_background` (thread wrapper).
---
## 7. `playlist` — Legacy Per-Player Routes (`/playlist`)
Redirects/legacy — kept for compatibility:
| Route | Methods | Purpose |
|---|---|---|
| `/<int:player_id>` | GET | Redirect to modern manage page |
| `/<id>/add` | POST | Add content to player's playlist |
| `/<id>/remove/<content_id>` | POST | Remove + renumber |
| `/<id>/reorder` | POST | JSON reorder from `content_ids` |
| `/<id>/update-duration/<content_id>` | POST | Update duration |
| `/<id>/update-muted/<content_id>` | POST | Update muted |
| `/<id>/clear` | POST | Clear all items |
---
## 8. `api` — Player-Facing REST + Deployment API (`/api`)
**Custom decorators:** `rate_limit(max_requests, window)` (in-memory, IP/Bearer keyed) and `verify_player_auth` (validates Bearer `auth_code` → sets `request.player`).
### Health & auth
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/health` | GET | `health_check` | API health (`version: 2.0.0`) |
| `/certificate` | GET | — | TLS certificate test |
| `/auth/player` | POST | `authenticate_player` | hostname + password/quickconnect → auth_code, playlist_id, orientation; sets online |
| `/auth/verify` | POST | `verify_auth_code` | Verify auth code → player info |
### Playlist delivery
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/playlists` | GET | `get_playlist_by_quickconnect` | Kivy-compatible: `?hostname=&quickconnect_code=` → playlist + version + hashed quickconnect |
| `/playlists/<player_id>` | GET | `get_player_playlist` | Bearer-authed fetch (cached) |
| `/playlist-version/<player_id>` | GET | `get_playlist_version` | Lightweight refresh-poll version check |
**Payload keys the players expect** (from `get_cached_playlist`): `file_name`, `type`, `duration`, `position`, `url`, `description`, `edit_on_player_enabled`, `muted`, `audio` (`"on"`/`"off"`).
### Feedback & monitoring
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/player-feedback` | POST | `receive_player_feedback` | Status (playing/paused/error/restarting); infers player; **auto-marks deployment `deployed`** |
| `/player-status/<player_id>` | GET | `get_player_status` | Online (5-min), latest feedback |
| `/system-info` | GET | — | Counts: players online/total, groups, content, 24h logs |
| `/content` | GET | — | List content with counts |
| `/logs` | GET | `get_logs` | Query logs (`limit`/`level`/`since`) |
### Edited media
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/player-edit-media` | POST | `receive_edited_media` | Bearer-authed multipart upload of edited image + metadata; versionizes to `edited_media/<content_id>/`; records `PlayerEdit`; auto-creates `PlayerUser`; repoints `Content.filename`; bumps playlist version; clears cache |
### SSH deployment
| Route | Methods | Endpoint | Purpose |
|---|---|---|---|
| `/deploy/test-ssh` | POST | `test_ssh_connection` | Test SSH (sshpass/ssh) |
| `/deploy/player` | POST | `deploy_player` | Full remote deploy (`deploy_player_to_host`); API key = `sha256(name:hostname)[:32]` |
**Error handlers:** JSON 404 / 405 / 500.
---
## 9. API Call Flows (Mermaid)
### Player bootstrap (auth → playlist)
```mermaid
sequenceDiagram
participant P as Player
participant A as /api
P->>A: POST /api/auth/player {hostname, password|quickconnect}
A->>A: Player.authenticate()
A-->>P: {auth_code, playlist_id, orientation}
P->>A: GET /api/playlists/<id> (Bearer auth_code)
A-->>P: playlist items + version
loop refresh poll
P->>A: GET /api/playlist-version/<id>
A-->>P: {version}
end
```
### Edited media upload
```mermaid
sequenceDiagram
participant P as Player
participant A as /api
participant DB as SQLite
P->>A: POST /api/player-edit-media (Bearer, multipart image + metadata)
A->>DB: locate Content (filename / edited_media/<id>/ regex / last PlayerEdit)
A->>DB: move original → edited_media/<id>/original_*
A->>DB: save new version + side-car JSON
A->>DB: get_or_create PlayerUser (user_code)
A->>DB: create PlayerEdit
A->>DB: repoint Content.filename → latest edit (preserve original_filename)
A->>DB: Playlist.increment_version()
A-->>P: 200 OK
```
### Deployment
```mermaid
sequenceDiagram
participant Admin as Admin UI
participant S as Server (app)
participant T as Background task
participant H as Player host
Admin->>S: add player / deploy
S->>T: background_player_deployment()
T->>H: sshpass test_ssh_connection()
T->>H: rsync staged code (or git clone/pull)
T->>H: write config/app_config.json
T->>H: temp passwordless sudo → install.sh → start.sh
T->>H: cleanup sudoers
T-->>S: update player.deployment_status = deployed|failed
Admin->>S: GET /players/deployment-status (poll)
```
---
> Next: [06 · Utils & Services](06-utils-services.md)
+140
View File
@@ -0,0 +1,140 @@
# 06 · Utilities & Services
All shared services live in `app/utils/`. This document details each module, its main functions, and how they fit together.
---
## Overview Table
| Module | Community | Responsibility | Key symbols |
|---|---|---|---|
| `logger.py` | C0 | DB-backed audit logging | `log_action()`, `get_recent_logs()`, `clear_old_logs()` |
| `group_player_management.py` | C0 | Group/player stats (legacy) | `get_player_status_info()`, `assign_player_to_group()`, `get_online_players_count()` |
| `caddy_manager.py` | C1 | HTTPS Caddyfile generation | `CaddyConfigGenerator`, `write_caddyfile()`, `reload_caddy()` |
| `background_tasks.py` | C10 | Async task execution | `run_background_task()`, `background_player_deployment()` |
| `ssh_deploy.py` | C10 | Remote player provisioning | `deploy_player_to_host()`, `test_ssh_connection()`, `generate_player_config()` |
| `player_build.py` | C11 | Stage player source code | `build_player_files()`, `write_base_config()`, `load/save_build_settings()` |
| `pptx_converter.py` | C12 | PPTX → PDF → PNG | `pptx_to_pdf_libreoffice()`, `validate_pptx_file()`, `cleanup_libreoffice_processes()` |
| `uploads.py` | C9 | Upload progress + file ops | `get/set/clear_upload_progress()`, `save_uploaded_file()`, `process_video_file()` |
| `portal_sso.py` | C4 | SSO auto-login | `init_portal_sso()`, `_get_or_create_user()` |
| `script_name_fix.py` | C4 | WSGI sub-path middleware | `ScriptNameFix` |
| `nginx_config_reader.py` | C7 | Legacy nginx status parsing | `NginxConfigReader`, `get_nginx_status()` |
---
## 1. `logger.py` — Audit Logging ⭐ God Node (116 edges)
The most-connected module in the system. Every meaningful action across all blueprints is recorded here.
| Function | Purpose |
|---|---|
| `log_action(level, message)` | Create + commit a `ServerLog` row |
| `log_info(message)` / `log_warning(message)` / `log_error(message)` | Convenience wrappers |
| `get_recent_logs(limit, level)` | Query recent logs (used by dashboard/admin) |
| `clear_old_logs(days)` | Housekeeping |
**Usage pattern:** admin actions (`reset_user_password`, `upload_header_logo`, `delete_editing_user`, `delete_playlist`, login failures, etc.) all funnel through `log_action()`.
---
## 2. `background_tasks.py` — Async Execution
| Function | Purpose |
|---|---|
| `run_background_task(func, *args)` | Spawns a **daemon thread** with a Flask app context pushed, then runs `func` |
| `background_player_deployment(...)` | Runs SSH deployment in background; updates `player.deployment_status` (`deploying → deployed|failed`) |
Used by `players.add_player` to avoid blocking the HTTP request during long remote installs.
---
## 3. `ssh_deploy.py` — Remote Player Provisioning ⭐ (heat 0.614)
The deployment engine. Full pipeline implemented by `deploy_player_to_host(...)`:
```mermaid
flowchart TD
A["deploy_player_to_host()"] --> B["test_ssh_connection() (sshpass)"]
B --> C["mkdir remote dir"]
C --> D{"staged code present?"}
D -- yes --> E["rsync pre-staged code"]
D -- no --> F["git clone / pull"]
E --> G["write config/app_config.json"]
F --> G
G --> H["temp passwordless sudo"]
H --> I["run install.sh"]
I --> J["run start.sh"]
J --> K["cleanup sudoers"]
K --> L["return steps[]"]
```
Other helpers:
- `get_local_player_code_status()` — inspect staged code state.
- `detect_server_ip()` / `parse_server_address()` — resolve the server address players should reach.
- `generate_player_config()` / `generate_app_config()` — write player's `config/app_config.json` with `server_ip`, `port`, `screen_name`, `quickconnect_key`, `orientation`, `use_https`, `verify_ssl`.
---
## 4. `caddy_manager.py` — HTTPS Automation ⭐ God Node
`CaddyConfigGenerator` produces the Caddyfile for the reverse proxy and reloads Caddy without restart.
| Method | Purpose |
|---|---|
| `generate_caddyfile(config)` | Pick template by mode: **HTTP-only** (`:80`), **domain** (Let's Encrypt), or **IP** (internal CA self-signed). Includes `reverse_proxy digiserver-app:5000`, 2 GB body limit, gzip, security headers |
| `write_caddyfile(content, path=/etc/caddy/Caddyfile)` | Write to disk |
| `reload_caddy()` | POST to Caddy admin API `http://caddy:2019/load` |
Triggered from `admin.update_https_config` after saving `HTTPSConfig`.
---
## 5. `player_build.py` — Staging Player Code
| Function | Purpose |
|---|---|
| `build_player_files(dir, repo_url, branch)` | Clone or `fetch + reset` the player repository into `PLAYER_CODE_DIR` |
| `write_base_config(...)` | Write `config/app_config.json` (blank screen / quickconnect) |
| `load_build_settings()` / `save_build_settings()` | JSON at `instance/player_build.json` |
| `get_player_server_settings` / `make_build_record` / `get_short_head` | Build metadata helpers |
---
## 6. `uploads.py` — Upload & Media Processing
| Function | Purpose |
|---|---|
| `get_upload_progress` / `set_upload_progress` / `clear_upload_progress` | In-memory per-file progress (for the upload page) |
| `save_uploaded_file` | Save a multipart upload |
| `process_video_file` | FFmpeg → H.264 main, 30 fps, ≤1080p, faststart |
| `process_pdf_file` | PDF processing (stub — real logic in content.py) |
| `get_file_size` / `delete_file` | FS helpers |
---
## 7. `pptx_converter.py` — LibreOffice Integration
| Function | Purpose |
|---|---|
| `pptx_to_pdf_libreoffice(pptx_path, output_dir)` | Headless LibreOffice → PDF (300 s timeout) |
| `validate_pptx_file()` | Validate a file is a real PPTX |
| `cleanup_libreoffice_processes()` | `pkill soffice` — clean hanging processes |
Used by the upload pipeline: **PPTX → PDF → PNG slides (Full HD)**.
---
## 8. `portal_sso.py` & `script_name_fix.py` — Gateway Integration
- **`portal_sso.py`** — `before_request` reads `X-Auth-Username`/`X-Auth-Role` from the umbrella nginx and auto-logs-in the local user (creating it on first arrival). See [04 · Application Core §11](04-application-core.md#11-portal-sso--apputilsportal_sso_py).
- **`script_name_fix.py`** — WSGI middleware mapping `X-Script-Name``SCRIPT_NAME` so `url_for()` is correct behind a path-prefixed gateway.
---
## 9. `nginx_config_reader.py` — Legacy (informational)
`NginxConfigReader` parses an `nginx.conf` and reports `ssl_enabled`, ports, upstreams, `server_names`, `ssl_protocols`, `client_max_body_size`, `gzip`. **Legacy** — the current reverse proxy is Caddy; retained for reference and the old deployment stack.
---
> Next: [07 · Deployment](07-deployment.md)
+162
View File
@@ -0,0 +1,162 @@
# 07 · Deployment
DigiServer v2 runs as a **Docker Compose** stack (app + Caddy) with optional remote SSH provisioning of players.
---
## 1. Architecture
```mermaid
flowchart LR
subgraph Host["Docker Host"]
subgraph Net["digiserver-network"]
APP["digiserver-app\nFlask + Gunicorn :5000"]
CAD["caddy:2-alpine\n:80 / :443"]
end
VOL1["./data/instance → /app/instance\n(SQLite DBs)"]
VOL2["./data/uploads → /app/app/static/uploads"]
VOL3["./data/Caddyfile → /etc/caddy/Caddyfile"]
VOL4["./data/caddy-data → /data\n./data/caddy-config → /config"]
end
Browser["Browser"] --> CAD
CAD --> APP
APP --> VOL1
APP --> VOL2
CAD --> VOL3
CAD --> VOL4
APP -.SSH/rsync.-> Players["Remote player hosts"]
```
---
## 2. `docker-compose.yml`
### Service `digiserver-app`
- **Build:** `.` (Dockerfile, `python:3.13-slim`)
- **Ports:** `5000:5000` (direct dev access; also `expose: 5000`)
- **Volumes:** `./data/instance:/app/instance`, `./data/uploads:/app/app/static/uploads`
- **Env:** `FLASK_ENV=production`, `SECRET_KEY`, `ADMIN_USERNAME`, `ADMIN_PASSWORD`
- **Healthcheck:** HTTP GET `http://localhost:5000/` (30s interval, 40s start)
- **Restart:** `unless-stopped`
### Service `caddy`
- **Image:** `caddy:2-alpine`
- **Ports:** `8080:80`, `8443:443`
- **Volumes:** `./data/Caddyfile:/etc/caddy/Caddyfile:rw`, `./data/caddy-data:/data`, `./data/caddy-config:/config`, `./data/caddy-logs:/var/log/caddy`
- **Depends on:** app (service started)
- **Healthcheck:** `wget` on port 80
---
## 3. Dockerfile
| Stage | Content |
|---|---|
| Base | `python:3.13-slim` |
| System deps | `poppler-utils`, `ffmpeg`, `libmagic1`, `sudo`, `fonts-noto-color-emoji`, `LibreOffice` (core/impress/writer), `sshpass`, `openssh-client`, `rsync`, `git` |
| Python | `COPY requirements.txt``pip install` (cached layer), then `COPY . .` |
| App config | `FLASK_APP=app.app:create_app`, `FLASK_ENV=production`, `EXPOSE 5000` |
| User | Non-root `appuser` (UID 1000) with **passwordless sudo** limited to `apt-get`, `install_libreoffice.sh`, `install_emoji_fonts.sh` |
| Runtime | `HEALTHCHECK` (HTTP 5000), `ENTRYPOINT /docker-entrypoint.sh` |
---
## 4. `docker-entrypoint.sh`
1. Create `/app/instance` and `/app/app/static/uploads`.
2. If `dashboard.db` is missing: create app + `db.create_all()`, then create/update the admin user from `ADMIN_USERNAME` / `ADMIN_PASSWORD`.
3. Start **Gunicorn**: `--bind 0.0.0.0:5000 --workers 4 --timeout 120 app.app:create_app('production')`.
---
## 5. `deploy.sh` (One-Shot Deployment)
```
1. Validate compose + project; create data/ subdirs; copy nginx configs
2. docker compose up -d + verify containers "Up"
3. Run migration scripts (add_https_config_table, add_player_user_table,
add_email_to_https_config, migrate_player_user_global,
add_original_filename_to_content)
4. Run /app/https_manager.py enable <hostname> <domain> <email> <ip> <port>
⚠ https_manager.py is NOT in the current repo — this step needs attention
5. Verify DB tables via SQLAlchemy inspector
6. caddy validate + https_manager.py status; print access URLs + default creds
```
---
## 6. HTTPS Setup (Caddy)
HTTPS is configured through the **Admin → HTTPS Configuration** page, which:
1. Saves `HTTPSConfig` (hostname, domain, IP, email, port, enabled).
2. `CaddyConfigGenerator.generate_caddyfile(config)` picks a template:
- **HTTP-only**`:80` reverse proxy
- **Domain** → automatic Let's Encrypt
- **IP** → internal CA self-signed
3. Writes `/etc/caddy/Caddyfile` and reloads Caddy via `POST http://caddy:2019/load`.
The current `data/Caddyfile` (HTTP mode) includes: admin API on `0.0.0.0:2019`, `:80` block → `digiserver-app:5000`, 2 GB body limit, gzip, security headers, access log.
---
## 7. `verify-deployment.sh` — Pre/Post-Deployment Checks
Sections checked (pass/fail/warn counters):
- git status
- `.env` / `.env.example`
- Docker + Compose versions + `compose config` syntax
- Dockerfile best practices (HEALTHCHECK, non-root, slim base)
- `requirements.txt` critical packages + versions
- migrations directory
- **SSL cert expiry** (openssl)
- Flask config (`ProductionConfig`, `SESSION_COOKIE_SECURE`)
- nginx.conf checks
- runtime container health
- security best practices
> ⚠ Note: the script still references `docker-compose` (v1) and `digiserver-nginx` — the current stack uses Compose v2 + Caddy.
---
## 8. Player Deployment Pipeline
```mermaid
sequenceDiagram
participant U as Admin
participant A as App
participant B as Background
participant H as Player host
U->>A: Add player (name, hostname, password/quickconnect)
A->>B: background_player_deployment()
B->>B: build/refresh staged code (PLAYER_CODE_DIR)
B->>H: sshpass + ssh test
B->>H: rsync code (or git clone/pull fallback)
B->>H: write config/app_config.json (server_ip, quickconnect, https...)
B->>H: temporary passwordless sudo
B->>H: ./install.sh && ./start.sh
B->>H: remove temp sudoers
B-->>A: player.deployment_status = 'deployed' | 'failed'
U->>A: poll /players/deployment-status
```
Statuses: `pending → deploying → deployed | failed`, with `last_deployment_at/status/message` persisted on the player row. Receiving player feedback also auto-marks deployment `deployed`.
---
## 9. Ports & Volumes Summary
| Item | Value |
|---|---|
| App port (container) | 5000 |
| App port (host, dev) | 5000 |
| Caddy HTTP | 8080 → 80 |
| Caddy HTTPS | 8443 → 443 |
| Caddy admin API | 2019 (container) |
| DB volume | `./data/instance` |
| Uploads volume | `./data/uploads` |
| Caddy data/config/logs | `./data/caddy-*` |
---
> Next: [08 · Workflows](08-workflows.md)
+150
View File
@@ -0,0 +1,150 @@
# 08 · End-to-End Workflows
This document traces the main user journeys through the system, tying together blueprints, models, utils, and the Graphify graph.
---
## 1. Content Upload → Playlist → Player
```mermaid
flowchart LR
U["Admin uploads media"] --> UF["/content/upload-media"]
UF -->|image| IMG["optimize_image_to_fullhd"]
UF -->|video| VID["validate via ffprobe"]
UF -->|pdf| PDF["pdf2image @300dpi → page PNGs"]
UF -->|pptx| PPT["LibreOffice → PDF → PNG slides"]
UF -->|large file| BG["process_file_in_background (thread)"]
UF --> DB[("Content row")]
DB --> PLA["/content/playlist/<id>/add-content"]
PLA --> PLC["playlist_content (position, duration, muted, edit_on_player_enabled)"]
PLC --> VER["Playlist.increment_version() + cache.clear()"]
VER --> API["/api/playlists/<id>"]
API --> P["Player fetches new playlist"]
```
**Key files:** `app/blueprints/content.py`, `app/utils/uploads.py`, `app/utils/pptx_converter.py`, `app/models/content.py`, `app/models/playlist.py`.
---
## 2. Playlist Synchronization (the "version" mechanism)
Every playlist mutation bumps `Playlist.version`. Players detect changes by polling:
```mermaid
sequenceDiagram
participant U as Admin UI
participant App
participant DB as SQLite
participant P as Player
U->>App: reorder / mute / duration / add / remove
App->>DB: mutate playlist_content
App->>DB: playlist.increment_version()
App->>App: cache.clear()
loop while player runs
P->>App: GET /api/playlist-version/<player_id>
App-->>P: {version: N}
alt version changed
P->>App: GET /api/playlists/<player_id>
App-->>P: new playlist payload
end
end
```
**Playlist payload items** include: `file_name`, `type`, `duration`, `position`, `url` (weblink or static file), `description`, `edit_on_player_enabled`, `muted`, `audio`.
---
## 3. On-Player Media Editing Pipeline ⭐ (heat 0.620)
When a signage operator edits an image directly on the player device:
```mermaid
flowchart TD
A["Player posts edited image"] --> B["POST /api/player-edit-media (Bearer + multipart)"]
B --> C{"Locate Content"}
C -->|by filename| D["match filename"]
C -->|edited_media regex| E["match edited_media/<id>/"]
C -->|fallback| F["last PlayerEdit"]
D/E/F --> G["Move original → edited_media/<content_id>/original_*"]
G --> H["Save new version + side-car metadata JSON"]
H --> I{"PlayerUser exists for user_code?"}
I -- no --> J["auto-create PlayerUser"]
I -- yes --> K["reuse"]
J/K --> L["Create PlayerEdit (version, original_name, new_name...)"]
L --> M["Repoint Content.filename → latest edit"]
M --> N["Preserve original_filename"]
N --> O["Playlist.increment_version() + cache.clear()"]
O --> P["Player sees updated media on refresh"]
```
**Why this is a god node:** `PlayerEdit` (99 edges) and `PlayerUser` (74 edges) make this the most-connected data flow in the system. See `app/models/player_edit.py`, `app/models/player_user.py`, `app/blueprints/api.py`.
---
## 4. HTTPS Configuration (Caddy)
```mermaid
flowchart TD
A["Admin → /admin/https-config"] --> B["POST /admin/https-config/update"]
B --> C["Validate + save HTTPSConfig (email, domain, ip, port)"]
C --> D["CaddyConfigGenerator.generate_caddyfile()"]
D --> E{"Mode"}
E -->|HTTP| F[":80 reverse proxy"]
E -->|domain| G["Let's Encrypt automatic HTTPS"]
E -->|IP| H["Internal CA self-signed"]
F/G/H --> I["write_caddyfile(/etc/caddy/Caddyfile)"]
I --> J["POST http://caddy:2019/load (hot reload)"]
J --> K["HTTPS live without restart"]
```
---
## 5. User Management & Auditing
```mermaid
flowchart TD
A["Admin panel"] --> B["/admin/user/create"]
A --> C["/admin/user/<id>/role"]
A --> D["/admin/user/<id>/password"]
A --> E["/admin/user/<id>/delete"]
B/C/D/E --> L["log_action(level, message)"]
L --> S[("server_log")]
S --> F["dashboard / admin recent logs"]
S --> G["/api/logs (limit, level, since)"]
```
**Roles:** `admin` (full), `user` (manage content/players), `viewer` (read-only dashboard).
---
## 6. Portal SSO (optional corporate gateway)
```mermaid
sequenceDiagram
participant B as Browser
participant G as Umbrella nginx gateway
participant A as DigiServer
B->>G: request (authenticated by portal)
G->>A: proxy + headers X-Auth-Username, X-Auth-Role
A->>A: init_portal_sso before_request
A->>A: _get_or_create_user(username, role)
A-->>B: dashboard (auto logged-in)
```
---
## 7. Media Housekeeping (Leftovers)
Content not assigned to any playlist is flagged as "leftover":
```mermaid
flowchart LR
L["/admin/leftover-media"] --> T["grouped by type: image/video/pdf/pptx + sizes"]
T --> D["/admin/delete-leftover-images|videos"]
D --> X["delete file + edited_media archive + PlayerEdit rows"]
```
---
> Next: [09 · Legacy & Migrations](09-legacy-and-migrations.md)
+64
View File
@@ -0,0 +1,64 @@
# 09 · Migrations & Legacy Components
---
## 1. Migrations
Migrations are **standalone Python scripts** (run via `docker compose exec ... python`, **not** Alembic revisions — there is no `migrations/versions/`).
| Script | Table(s) | What it does |
|---|---|---|
| `add_https_config_table.py` | `https_config` | Creates table via `db.create_all()` |
| `add_player_user_table.py` | `player_user` | Creates table via `db.create_all()` |
| `add_email_to_https_config.py` | `https_config` | `ALTER TABLE ... ADD COLUMN email VARCHAR(255)` (idempotent) |
| `add_deployment_fields_to_player.py` | `player` | Adds `deployment_status`, `last_deployment_at`, `last_deployment_status`, `last_deployment_message` (idempotent) |
| `add_url_to_content.py` | `content` | `ALTER TABLE ... ADD COLUMN url VARCHAR(2048)` (weblink support) |
| `add_original_filename_to_content.py` | `content` | Adds `original_filename` then **backfills**: from first (v1) `PlayerEdit.original_name` for edited content, else `filename` |
| `migrate_player_user_global.py` | `player_user` | **Drop & recreate**: removes old `player_id` FK, makes `user_code` globally unique, `user_name` nullable |
**Order (from `deploy.sh`):**
```
add_https_config_table.py
add_player_user_table.py
add_email_to_https_config.py
migrate_player_user_global.py
add_original_filename_to_content.py
```
> ⚠ `deploy.sh` also calls `/app/https_manager.py`**that file is not present in this repo**, so that deployment step needs attention.
---
## 2. Legacy / Archived Components
| Component | Status | Notes |
|---|---|---|
| `app/blueprints/content_old.py` | **Dead code** | Legacy per-player content routes (`/`, `/upload`, `/<id>/edit`, `/bulk/delete`, `/upload-progress`, `/preview`, `/statistics`, `/check-duplicates`, `/<id>/groups`). Not imported by `create_app`. |
| `app/blueprints/playlist.py` | **Active but legacy** | Per-player playlist routes kept as redirects to the modern content workflow. |
| `Group` model + group routes | **Archived** | `Player` no longer has `group_id`; group routes commented out; `group_player_management.py` and `group_content` association remain for reference. |
| `nginx` stack | **Replaced by Caddy** | `data/nginx.conf`, `data/nginx-custom-domains.conf`, `data/nginx-logs/`, `data/nginx-ssl/` retained. `utils/nginx_config_reader.py` still parses it. |
| `https_manager.py` | **Missing** | Referenced by `deploy.sh` but not in repo — likely merged into `CaddyConfigGenerator`. |
| `old_code_documentation/` | **Archive** | Full legacy docs, old scripts (`blueprint_groups.py`, `add_muted_column.py`, `fix_player_user_schema.py`, `test_edit_media_*.py`, `check_fix_player.py`, `migrate_add_edit_enabled.py`), deployment guides, HTTPS analysis, player analysis. |
| `QUICK_DEPLOYMENT.md`, `deployment-commands-reference.sh` | **Active reference** | Manual deployment notes. |
---
## 3. Recommended Cleanup (optional)
- Remove `content_old.py` and `old_code_documentation/*.py` scripts that are no longer needed (keep the `.md` docs).
- Resolve the missing `https_manager.py` in `deploy.sh` (use `CaddyConfigGenerator` equivalents).
- Update `verify-deployment.sh` to reference Compose v2 and Caddy instead of `docker-compose`/nginx.
---
## 📚 Back to Index
- [README · Documentation Home](README.md)
- [01 · Architecture](01-architecture.md)
- [02 · Knowledge Graph](02-knowledge-graph.md)
- [03 · Data Model](03-data-model.md)
- [04 · Application Core](04-application-core.md)
- [05 · Blueprints & API](05-blueprints-api.md)
- [06 · Utils & Services](06-utils-services.md)
- [07 · Deployment](07-deployment.md)
- [08 · Workflows](08-workflows.md)
+108
View File
@@ -0,0 +1,108 @@
# DigiServer v2 — Project Documentation
> **Comprehensive technical documentation generated with [Graphify](https://marketplace.visualstudio.com/items?itemName=anytechiestudio.graphify-vscode)** — an interactive knowledge-graph engine for AI-assisted coding.
**Version:** 2.0.0 · **Build date:** 2025-11-12 · **Generated:** 2026-08-16
DigiServer v2 is a **digital signage management server** built with **Flask**. It manages media content (images, videos, PDFs, presentations), organizes it into **playlists**, assigns **players** (physical signage displays), and remotely **deploys and monitors** those players over SSH — including HTTPS/SSL provisioning via Caddy and an on-player photo-editing pipeline.
---
## 🗂 Documentation Index
| Document | Purpose |
|---|---|
| [**01-architecture.md**](01-architecture.md) | System overview, layers, component map, architecture diagrams |
| [**02-knowledge-graph.md**](02-knowledge-graph.md) | Graphify graph: 41 communities, god nodes, and how to use the visualizer |
| [**03-data-model.md**](03-data-model.md) | Database schema: all 10 tables, relationships, ER diagrams |
| [**04-application-core.md**](04-application-core.md) | App factory, config, extensions, middleware, CLI, templates |
| [**05-blueprints-api.md**](05-blueprints-api.md) | All 7 blueprints + full REST API reference |
| [**06-utils-services.md**](06-utils-services.md) | Background tasks, SSH deploy, Caddy manager, player build, SSO, uploads |
| [**07-deployment.md**](07-deployment.md) | Docker, Caddy, deployment pipeline, HTTPS setup, verify script |
| [**08-workflows.md**](08-workflows.md) | End-to-end flows: upload, playlists, player edits, deployment, sync |
| [**09-legacy-and-migrations.md**](09-legacy-and-migrations.md) | Migration scripts, deprecated/archived components |
**Auto-generated Graphify artifacts** (regenerate anytime — see [02-knowledge-graph.md](02-knowledge-graph.md)):
```
graphify-out/
├── graph.html ← Interactive visualizer (open in browser)
├── graph.json ← Raw graph data (631 nodes / 1162 edges)
├── GRAPH_REPORT.md ← God nodes, communities, knowledge gaps
├── COMPASS.md ← Token-optimized architecture summary
├── DOMAINS.md ← Community → domain mapping
├── intelligence.json ← AI-detected insights (god nodes, surprises)
├── graph.compact.txt ← Compact graph dump for LLM context
└── wiki/ ← Per-community + per-god-node Markdown articles
├── index.md
├── Community_0.md … Community_40.md
├── CaddyConfigGenerator.md, Content.md, HTTPSConfig.md,
├── PlayerEdit.md, PlayerUser.md, Playlist.md, User.md,
├── log_action().md, create_app().md …
```
---
## ⚡ Quick Facts
| Metric | Value |
|---|---|
| Framework | Flask 3.1 + SQLAlchemy 2.0 (SQLite) + Gunicorn |
| Application type | Digital signage content/playlist/player management |
| Graph size | **631 nodes · 1162 edges · 41 communities** |
| Blueprints | 7 active (`main`, `auth`, `admin`, `players`, `content`, `playlist`, `api`) |
| Database tables | 10 (`user`, `player`, `player_edit`, `player_feedback`, `player_user`, `content`, `group`, `playlist`, `server_log`, `https_config`) |
| Reverse proxy | Caddy 2 (automatic HTTPS / Let's Encrypt) |
| Deployment | Docker Compose (app + Caddy) + remote SSH player provisioning |
| Key externals | LibreOffice, Poppler (pdf2image), FFmpeg, sshpass/rsync |
---
## 🔑 Core Abstractions (Graphify "God Nodes")
Detected automatically from graph centrality:
1. `log_action()` — 116 edges · DB-backed audit logging (`app/utils/logger.py`)
2. `PlayerEdit` — 99 edges · on-player edit records (`app/models/player_edit.py`)
3. `PlayerUser` — 74 edges · player edit user mapping (`app/models/player_user.py`)
4. `Playlist` — 33 edges · playlist + versioning (`app/models/playlist.py`)
5. `CaddyConfigGenerator` — 31 edges · HTTPS Caddyfile generation (`app/utils/caddy_manager.py`)
6. `HTTPSConfig` — 30 edges · HTTPS settings model (`app/models/https_config.py`)
7. `Content` — 24 edges · media content model (`app/models/content.py`)
8. `User` — 19 edges · admin/user/viewer accounts (`app/models/user.py`)
9. `create_app()` — 13 edges · application factory (`app/app.py`)
10. `app/models/__init__.py` — 13 edges · models package export
---
## 🧭 Quick Navigation
### The 7 Active Blueprints
| Blueprint | Prefix | Responsibility |
|---|---|---|
| `main` | `/` | Dashboard + health check |
| `auth` | `/` | Login / logout / register / change password |
| `admin` | `/admin` | Users, HTTPS config, player build, logos, logs, leftovers |
| `players` | `/players` | Player CRUD, manage, edited media, deployment status |
| `content` | `/content` | Media library, uploads, playlist management (modern) |
| `playlist` | `/playlist` | Legacy per-player playlist routes (redirects) |
| `api` | `/api` | Player-facing REST API (auth, playlists, feedback, edits, deploy) |
### Data Model at a Glance
```mermaid
erDiagram
user ||--o{ server_log : ""
player ||--o{ player_feedback : "cascade"
player ||--o{ player_edit : "cascade"
player }o--o| playlist : "assigned"
content ||--o{ player_edit : "cascade"
content ||--o{ player_feedback : ""
playlist ||--o{ content : "playlist_content (M2M)"
content }o--o{ group : "group_content (M2M)"
player_user ||--o{ player_edit : "user_code"
https_config ||--|| https_config : "single row config"
```
> **Proceed to [01-architecture.md](01-architecture.md)** for the full system overview and diagrams.
+22
View File
@@ -0,0 +1,22 @@
# Architectural Compass - /home/scheianu/digiserver-v2 (2026-08-16)
> [!NOTE]
> This is a token-optimized summary. For deep logic, see GRAPH_REPORT.md.
## Core Abstractions (God Nodes)
1. `log_action()` (116 edges)
2. `PlayerEdit` (99 edges)
3. `PlayerUser` (74 edges)
4. `Playlist` (33 edges)
5. `CaddyConfigGenerator` (31 edges)
6. `HTTPSConfig` (30 edges)
7. `Content` (24 edges)
8. `User` (19 edges)
9. `create_app()` (13 edges)
10. `Models package for digiserver-v2.` (13 edges)
## System Layers
- **L0: Global/Entry**:
- **L1: Strategic/Core**:
- **L2: Implementation**:
- **L3: Utility**:
+45
View File
@@ -0,0 +1,45 @@
# System Domains (Communities)
| ID | Domain | Rationale |
|---|---|---|
| 0 | Community 0 | |
| 1 | Community 1 | |
| 2 | Community 2 | |
| 3 | Community 3 | |
| 4 | Community 4 | |
| 5 | Community 5 | |
| 6 | Community 6 | |
| 7 | Community 7 | |
| 8 | Community 8 | |
| 9 | Community 9 | |
| 10 | Community 10 | |
| 11 | Community 11 | |
| 12 | Community 12 | |
| 13 | Community 13 | |
| 14 | Community 14 | |
| 15 | Community 15 | |
| 16 | Community 16 | |
| 17 | Community 17 | |
| 18 | Community 18 | |
| 19 | Community 19 | |
| 20 | Community 20 | |
| 21 | Community 21 | |
| 22 | Community 22 | |
| 23 | Community 23 | |
| 24 | Community 24 | |
| 25 | Community 25 | |
| 26 | Community 26 | |
| 27 | Community 27 | |
| 28 | Community 28 | |
| 29 | Community 29 | |
| 30 | Community 30 | |
| 31 | Community 31 | |
| 32 | Community 32 | |
| 33 | Community 33 | |
| 34 | Community 34 | |
| 35 | Community 35 | |
| 36 | Community 36 | |
| 37 | Community 37 | |
| 38 | Community 38 | |
| 39 | Community 39 | |
| 40 | Community 40 | |
+342
View File
@@ -0,0 +1,342 @@
# Graph Report - /home/scheianu/digiserver-v2 (2026-08-16)
## Corpus Check
- 49 files · ~210,805 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 631 nodes · 1162 edges · 41 communities detected
- Extraction: 56% EXTRACTED · 44% INFERRED · 0% AMBIGUOUS · INFERRED: 512 edges (avg confidence: 0.62)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
- [[_COMMUNITY_Community 0|Community 0]]
- [[_COMMUNITY_Community 1|Community 1]]
- [[_COMMUNITY_Community 2|Community 2]]
- [[_COMMUNITY_Community 3|Community 3]]
- [[_COMMUNITY_Community 4|Community 4]]
- [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]]
- [[_COMMUNITY_Community 8|Community 8]]
- [[_COMMUNITY_Community 9|Community 9]]
- [[_COMMUNITY_Community 10|Community 10]]
- [[_COMMUNITY_Community 11|Community 11]]
- [[_COMMUNITY_Community 12|Community 12]]
- [[_COMMUNITY_Community 13|Community 13]]
- [[_COMMUNITY_Community 14|Community 14]]
- [[_COMMUNITY_Community 15|Community 15]]
- [[_COMMUNITY_Community 16|Community 16]]
- [[_COMMUNITY_Community 17|Community 17]]
- [[_COMMUNITY_Community 18|Community 18]]
- [[_COMMUNITY_Community 19|Community 19]]
- [[_COMMUNITY_Community 20|Community 20]]
- [[_COMMUNITY_Community 21|Community 21]]
- [[_COMMUNITY_Community 22|Community 22]]
- [[_COMMUNITY_Community 23|Community 23]]
- [[_COMMUNITY_Community 24|Community 24]]
- [[_COMMUNITY_Community 25|Community 25]]
- [[_COMMUNITY_Community 26|Community 26]]
- [[_COMMUNITY_Community 27|Community 27]]
- [[_COMMUNITY_Community 28|Community 28]]
- [[_COMMUNITY_Community 29|Community 29]]
- [[_COMMUNITY_Community 30|Community 30]]
- [[_COMMUNITY_Community 31|Community 31]]
- [[_COMMUNITY_Community 32|Community 32]]
- [[_COMMUNITY_Community 33|Community 33]]
- [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 36|Community 36]]
- [[_COMMUNITY_Community 37|Community 37]]
- [[_COMMUNITY_Community 38|Community 38]]
- [[_COMMUNITY_Community 39|Community 39]]
- [[_COMMUNITY_Community 40|Community 40]]
## God Nodes (most connected - your core abstractions)
1. `log_action()` - 116 edges
2. `PlayerEdit` - 99 edges
3. `PlayerUser` - 74 edges
4. `Playlist` - 33 edges
5. `CaddyConfigGenerator` - 31 edges
6. `HTTPSConfig` - 30 edges
7. `Content` - 24 edges
8. `User` - 19 edges
9. `create_app()` - 13 edges
10. `Models package for digiserver-v2.` - 13 edges
## Surprising Connections (you probably didn't know these)
- `reset_user_password()` --calls--> `log_action()` [INFERRED]
/home/scheianu/digiserver-v2/app/blueprints/admin.py → /home/scheianu/digiserver-v2/app/utils/logger.py
- `upload_header_logo()` --calls--> `log_action()` [INFERRED]
/home/scheianu/digiserver-v2/app/blueprints/admin.py → /home/scheianu/digiserver-v2/app/utils/logger.py
- `delete_editing_user()` --calls--> `log_action()` [INFERRED]
/home/scheianu/digiserver-v2/app/blueprints/admin.py → /home/scheianu/digiserver-v2/app/utils/logger.py
- `delete_playlist()` --calls--> `log_action()` [INFERRED]
/home/scheianu/digiserver-v2/app/blueprints/content.py → /home/scheianu/digiserver-v2/app/utils/logger.py
- `Main playlist management page.` --uses--> `PlayerEdit` [INFERRED]
/home/scheianu/digiserver-v2/app/blueprints/content.py → /home/scheianu/digiserver-v2/app/models/player_edit.py
## Communities
### Community 0 - "Community 0"
Cohesion: 0.03
Nodes (80): change_password(), login(), logout(), Authentication Blueprint - Login, Logout, Register, register(), add_content_to_group(), add_player_to_group(), delete_group() (+72 more)
### Community 1 - "Community 1"
Cohesion: 0.04
Nodes (72): Add player_user table for user code mappings., admin_panel(), admin_required(), build_player(), change_theme(), change_user_role(), clear_logs(), create_user() (+64 more)
### Community 2 - "Community 2"
Cohesion: 0.04
Nodes (61): add_content_to_playlist(), add_weblink(), add_weblink_to_playlist(), assign_player_to_playlist(), bulk_remove_from_playlist(), content_list(), create_fullhd_image(), delete_playlist() (+53 more)
### Community 3 - "Community 3"
Cohesion: 0.06
Nodes (57): api_not_found(), authenticate_player(), deploy_player(), get_cached_playlist(), get_logs(), get_player_playlist(), get_player_status(), get_playlist_by_quickconnect() (+49 more)
### Community 4 - "Community 4"
Cohesion: 0.07
Nodes (42): add_muted_column(), Add muted column to playlist_content association table., configure_login_manager(), create_app(), DigiServer v2 - Application Factory Modern Flask application with blueprint arch, Configure Flask-Login, Register error handlers, Register CLI commands (+34 more)
### Community 5 - "Community 5"
Cohesion: 0.04
Nodes (27): Content, Content model for media files., String representation of Content., Check if content is an image., Check if content is a video., Content model representing media files for display. Attributes:, Check if content is a PDF., Check if content is a web link (URL) rather than an uploaded file. (+19 more)
### Community 6 - "Community 6"
Cohesion: 0.05
Nodes (45): Add https_config table for HTTPS configuration management., Write Caddyfile to disk. Args: caddyfile_content: C, Reload Caddy configuration without restart. Note: Caddy monitor, Generate a complete Caddyfile. Behaviour: - HTTPS disabled / no, Write Caddyfile to disk. The default path is /etc/caddy/Caddyfile — the, Push the current Caddyfile to Caddy via its admin API (/load). Caddy ap, HTTPSConfig, String representation of HTTPSConfig. (+37 more)
### Community 7 - "Community 7"
Cohesion: 0.06
Nodes (26): create_group(), delete_media(), Delete a media file and remove it from all playlists., Group, Group model for organizing players and content., Group model for organizing players with shared content. Attributes:, String representation of Group., Add a player to this group. Args: player: Player in (+18 more)
### Community 8 - "Community 8"
Cohesion: 0.23
Nodes (23): check_database_integrity(), Colors, get_player_auth_code(), get_sample_content(), main(), print_error(), print_info(), print_section() (+15 more)
### Community 9 - "Community 9"
Cohesion: 0.11
Nodes (20): Get upload progress for a specific upload., upload_content(), upload_progress_status(), clear_upload_progress(), delete_file(), get_file_size(), get_upload_progress(), process_pdf_file() (+12 more)
### Community 10 - "Community 10"
Cohesion: 0.12
Nodes (18): background_player_deployment(), Background task execution for long-running operations., Run a function in a background thread, with a Flask app context pushed., Deploy player code to host in background. Args: hostname: SSH h, run_background_task(), deploy_player_to_host(), generate_app_config(), generate_player_config() (+10 more)
### Community 11 - "Community 11"
Cohesion: 0.15
Nodes (18): build_player_action(), Build/refresh the staged player code and/or write its base config., build_player_files(), get_player_server_settings(), get_short_head(), load_build_settings(), make_build_record(), Utilities for building/staging the player files on the server. Admins use the " (+10 more)
### Community 12 - "Community 12"
Cohesion: 0.29
Nodes (7): cleanup_libreoffice_processes(), pptx_to_pdf_libreoffice(), PowerPoint to PDF converter using LibreOffice., Clean up any hanging LibreOffice processes., Convert PPTX to PDF using LibreOffice for highest quality. This functio, Validate if file is a valid PowerPoint file. Args: filepath: Pa, validate_pptx_file()
### Community 13 - "Community 13"
Cohesion: 0.5
Nodes (3): migrate(), Migration: Add edit_on_player_enabled column to playlist_content table., Add edit_on_player_enabled column to playlist_content.
### Community 14 - "Community 14"
Cohesion: 1.0
Nodes (1): Migrate player_user table to remove player_id and make user_code unique globally
### Community 15 - "Community 15"
Cohesion: 1.0
Nodes (1): Add original_filename column to content table. This preserves the pristine uplo
### Community 16 - "Community 16"
Cohesion: 1.0
Nodes (1): Add url column to content table for weblink support.
### Community 17 - "Community 17"
Cohesion: 1.0
Nodes (1): Add deployment tracking columns to player table.
### Community 18 - "Community 18"
Cohesion: 1.0
Nodes (1): Add email field to https_config table.
### Community 19 - "Community 19"
Cohesion: 1.0
Nodes (1): Flask extensions initialization Centralized extension management for the applica
### Community 20 - "Community 20"
Cohesion: 1.0
Nodes (1): Check if feedback indicates an error.
### Community 21 - "Community 21"
Cohesion: 1.0
Nodes (1): Get age of feedback in seconds.
### Community 22 - "Community 22"
Cohesion: 1.0
Nodes (1): Get most recent feedback for a player. Args: player
### Community 23 - "Community 23"
Cohesion: 1.0
Nodes (1): Get the current HTTPS configuration. Returns: HTTPS
### Community 24 - "Community 24"
Cohesion: 1.0
Nodes (1): Create or update HTTPS configuration. Args: https_e
### Community 25 - "Community 25"
Cohesion: 1.0
Nodes (1): Check if user has admin role.
### Community 26 - "Community 26"
Cohesion: 1.0
Nodes (1): Check if player is online (seen in last 5 minutes).
### Community 27 - "Community 27"
Cohesion: 1.0
Nodes (1): Authenticate a player by hostname and password or quickconnect code.
### Community 28 - "Community 28"
Cohesion: 1.0
Nodes (1): Create an info level log entry. Args: message: Log
### Community 29 - "Community 29"
Cohesion: 1.0
Nodes (1): Create a warning level log entry. Args: message: Lo
### Community 30 - "Community 30"
Cohesion: 1.0
Nodes (1): Create an error level log entry. Args: message: Log
### Community 31 - "Community 31"
Cohesion: 1.0
Nodes (1): Get number of content items in this group.
### Community 32 - "Community 32"
Cohesion: 1.0
Nodes (1): Get file size in megabytes.
### Community 33 - "Community 33"
Cohesion: 1.0
Nodes (1): Get number of groups containing this content.
### Community 34 - "Community 34"
Cohesion: 1.0
Nodes (1): Name of the original (unedited) file for display purposes.
### Community 35 - "Community 35"
Cohesion: 1.0
Nodes (1): Path (relative to uploads/) where the original unedited file lives. Whe
### Community 36 - "Community 36"
Cohesion: 1.0
Nodes (1): Path (relative to uploads/) of the current/latest version to display.
### Community 37 - "Community 37"
Cohesion: 1.0
Nodes (1): Whether this content has been edited on a player.
### Community 38 - "Community 38"
Cohesion: 1.0
Nodes (1): Display name of the original (unedited) file.
### Community 39 - "Community 39"
Cohesion: 1.0
Nodes (1): uploads/-relative path of the pristine original file. After a player ed
### Community 40 - "Community 40"
Cohesion: 1.0
Nodes (0):
## Knowledge Gaps
- **178 isolated node(s):** `Migrate player_user table to remove player_id and make user_code unique globally`, `Add original_filename column to content table. This preserves the pristine uplo`, `Add url column to content table for weblink support.`, `Add deployment tracking columns to player table.`, `Add email field to https_config table.` (+173 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 14`** (2 nodes): `migrate_player_user_global.py`, `Migrate player_user table to remove player_id and make user_code unique globally`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 15`** (2 nodes): `Add original_filename column to content table. This preserves the pristine uplo`, `add_original_filename_to_content.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 16`** (2 nodes): `Add url column to content table for weblink support.`, `add_url_to_content.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 17`** (2 nodes): `Add deployment tracking columns to player table.`, `add_deployment_fields_to_player.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 18`** (2 nodes): `Add email field to https_config table.`, `add_email_to_https_config.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 19`** (2 nodes): `Flask extensions initialization Centralized extension management for the applica`, `extensions.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 20`** (1 nodes): `Check if feedback indicates an error.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 21`** (1 nodes): `Get age of feedback in seconds.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 22`** (1 nodes): `Get most recent feedback for a player. Args: player`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 23`** (1 nodes): `Get the current HTTPS configuration. Returns: HTTPS`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 24`** (1 nodes): `Create or update HTTPS configuration. Args: https_e`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 25`** (1 nodes): `Check if user has admin role.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 26`** (1 nodes): `Check if player is online (seen in last 5 minutes).`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 27`** (1 nodes): `Authenticate a player by hostname and password or quickconnect code.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 28`** (1 nodes): `Create an info level log entry. Args: message: Log`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 29`** (1 nodes): `Create a warning level log entry. Args: message: Lo`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 30`** (1 nodes): `Create an error level log entry. Args: message: Log`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 31`** (1 nodes): `Get number of content items in this group.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 32`** (1 nodes): `Get file size in megabytes.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 33`** (1 nodes): `Get number of groups containing this content.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 34`** (1 nodes): `Name of the original (unedited) file for display purposes.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 35`** (1 nodes): `Path (relative to uploads/) where the original unedited file lives. Whe`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 36`** (1 nodes): `Path (relative to uploads/) of the current/latest version to display.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 37`** (1 nodes): `Whether this content has been edited on a player.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 38`** (1 nodes): `Display name of the original (unedited) file.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 39`** (1 nodes): `uploads/-relative path of the pristine original file. After a player ed`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 40`** (1 nodes): `check_fix_player.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_app_utils_portal_sso_py", "label": "portal_sso.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "portal_sso_init_portal_sso", "label": "init_portal_sso()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L17", "complexity": 4, "loc": 16}, {"id": "portal_sso_get_or_create_user", "label": "_get_or_create_user()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L35", "complexity": 4, "loc": 18}, {"id": "portal_sso_rationale_1", "label": "Portal SSO middleware for DigiServer v2. When the umbrella nginx verifies the p", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L1"}, {"id": "portal_sso_rationale_18", "label": "Register the SSO before_request handler on the given Flask app.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L18"}], "edges": [{"source": "home_scheianu_digiserver_v2_app_utils_portal_sso_py", "target": "secrets", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L12", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_utils_portal_sso_py", "target": "flask", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L13", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_utils_portal_sso_py", "target": "flask_login", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L14", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_utils_portal_sso_py", "target": "portal_sso_init_portal_sso", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L17", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_utils_portal_sso_py", "target": "portal_sso_get_or_create_user", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L35", "weight": 1.0}, {"source": "portal_sso_rationale_1", "target": "home_scheianu_digiserver_v2_app_utils_portal_sso_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L1", "weight": 1.0}, {"source": "portal_sso_rationale_18", "target": "portal_sso_init_portal_sso", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L18", "weight": 1.0}], "raw_calls": [{"caller_nid": "portal_sso_get_or_create_user", "callee": "first", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L40"}, {"caller_nid": "portal_sso_get_or_create_user", "callee": "filter_by", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L40"}, {"caller_nid": "portal_sso_get_or_create_user", "callee": "decode", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L42"}, {"caller_nid": "portal_sso_get_or_create_user", "callee": "generate_password_hash", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L42"}, {"caller_nid": "portal_sso_get_or_create_user", "callee": "token_hex", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L42"}, {"caller_nid": "portal_sso_get_or_create_user", "callee": "User", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L43"}, {"caller_nid": "portal_sso_get_or_create_user", "callee": "add", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L48"}, {"caller_nid": "portal_sso_get_or_create_user", "callee": "commit", "source_file": "/home/scheianu/digiserver-v2/app/utils/portal_sso.py", "source_location": "L49"}]}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_migrations_add_https_config_table_py", "label": "add_https_config_table.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/migrations/add_https_config_table.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "add_https_config_table_rationale_1", "label": "Add https_config table for HTTPS configuration management.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/migrations/add_https_config_table.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_migrations_add_https_config_table_py", "target": "sys", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_https_config_table.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_https_config_table_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_https_config_table.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_https_config_table_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_https_config_table.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_https_config_table_py", "target": "app_models_https_config", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_https_config_table.py", "source_location": "L7", "weight": 1.0}, {"source": "add_https_config_table_rationale_1", "target": "home_scheianu_digiserver_v2_migrations_add_https_config_table_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_https_config_table.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_app_blueprints_init_py", "label": "__init__.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/blueprints/__init__.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "init_rationale_1", "label": "Blueprints package initialization", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/blueprints/__init__.py", "source_location": "L1"}], "edges": [{"source": "init_rationale_1", "target": "home_scheianu_digiserver_v2_app_blueprints_init_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/blueprints/__init__.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_old_code_documentation_check_fix_player_py", "label": "check_fix_player.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/check_fix_player.py", "source_location": "L1", "complexity": 1, "loc": 1}], "edges": [{"source": "home_scheianu_digiserver_v2_old_code_documentation_check_fix_player_py", "target": "app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/check_fix_player.py", "source_location": "L4", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_old_code_documentation_check_fix_player_py", "target": "app_models", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/check_fix_player.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_old_code_documentation_check_fix_player_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/check_fix_player.py", "source_location": "L6", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_app_models_player_edit_py", "label": "player_edit.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "player_edit_playeredit", "label": "PlayerEdit", "file_type": "code", "type": "CLASS", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L8", "complexity": 4, "loc": 53}, {"id": "player_edit_playeredit_repr", "label": ".__repr__()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L42", "complexity": 1, "loc": 3}, {"id": "player_edit_playeredit_to_dict", "label": ".to_dict()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L46", "complexity": 4, "loc": 15}, {"id": "player_edit_rationale_1", "label": "Player edit model for tracking media edited on players.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L1"}, {"id": "player_edit_rationale_9", "label": "Player edit model for tracking media files edited on player devices. At", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L9"}, {"id": "player_edit_rationale_43", "label": "String representation of PlayerEdit.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L43"}, {"id": "player_edit_rationale_47", "label": "Convert to dictionary for API responses.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L47"}], "edges": [{"source": "home_scheianu_digiserver_v2_app_models_player_edit_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_player_edit_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L3", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_player_edit_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_player_edit_py", "target": "player_edit_playeredit", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L8", "weight": 1.0}, {"source": "player_edit_playeredit", "target": "player_edit_playeredit_repr", "relation": "method", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L42", "weight": 1.0}, {"source": "player_edit_playeredit", "target": "player_edit_playeredit_to_dict", "relation": "method", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L46", "weight": 1.0}, {"source": "player_edit_rationale_1", "target": "home_scheianu_digiserver_v2_app_models_player_edit_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L1", "weight": 1.0}, {"source": "player_edit_rationale_9", "target": "player_edit_playeredit", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L9", "weight": 1.0}, {"source": "player_edit_rationale_43", "target": "player_edit_playeredit_repr", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L43", "weight": 1.0}, {"source": "player_edit_rationale_47", "target": "player_edit_playeredit_to_dict", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L47", "weight": 1.0}], "raw_calls": [{"caller_nid": "player_edit_playeredit_to_dict", "callee": "isoformat", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L57"}, {"caller_nid": "player_edit_playeredit_to_dict", "callee": "isoformat", "source_file": "/home/scheianu/digiserver-v2/app/models/player_edit.py", "source_location": "L58"}]}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_migrations_add_deployment_fields_to_player_py", "label": "add_deployment_fields_to_player.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "add_deployment_fields_to_player_rationale_1", "label": "Add deployment tracking columns to player table.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_migrations_add_deployment_fields_to_player_py", "target": "sys", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_deployment_fields_to_player_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_deployment_fields_to_player_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_deployment_fields_to_player_py", "target": "sqlalchemy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py", "source_location": "L7", "weight": 1.0}, {"source": "add_deployment_fields_to_player_rationale_1", "target": "home_scheianu_digiserver_v2_migrations_add_deployment_fields_to_player_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_app_models_init_py", "label": "__init__.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "init_rationale_1", "label": "Models package for digiserver-v2.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_user", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_player", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_group", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L4", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_playlist", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_content", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_server_log", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L7", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_player_feedback", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L8", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_player_edit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L9", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_player_user", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L10", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_init_py", "target": "app_models_https_config", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L11", "weight": 1.0}, {"source": "init_rationale_1", "target": "home_scheianu_digiserver_v2_app_models_init_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/__init__.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_old_code_documentation_add_muted_column_py", "label": "add_muted_column.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "add_muted_column_add_muted_column", "label": "add_muted_column()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L6", "complexity": 3, "loc": 25}, {"id": "add_muted_column_rationale_7", "label": "Add muted column to playlist_content association table.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L7"}], "edges": [{"source": "home_scheianu_digiserver_v2_old_code_documentation_add_muted_column_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L3", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_old_code_documentation_add_muted_column_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L4", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_old_code_documentation_add_muted_column_py", "target": "add_muted_column_add_muted_column", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L6", "weight": 1.0}, {"source": "add_muted_column_rationale_7", "target": "add_muted_column_add_muted_column", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L7", "weight": 1.0}], "raw_calls": [{"caller_nid": "add_muted_column_add_muted_column", "callee": "create_app", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L8"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "app_context", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L10"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "fetchall", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L13"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "execute", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L13"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "text", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L13"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "print", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L17"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "execute", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L21"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "text", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L21"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "commit", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L25"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "print", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L26"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "print", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L27"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "rollback", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L29"}, {"caller_nid": "add_muted_column_add_muted_column", "callee": "print", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/add_muted_column.py", "source_location": "L30"}]}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_app_utils_script_name_fix_py", "label": "script_name_fix.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "script_name_fix_scriptnamefix", "label": "ScriptNameFix", "file_type": "code", "type": "CLASS", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L15", "complexity": 3, "loc": 12}, {"id": "script_name_fix_scriptnamefix_init", "label": ".__init__()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L16", "complexity": 1, "loc": 2}, {"id": "script_name_fix_scriptnamefix_call", "label": ".__call__()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L19", "complexity": 3, "loc": 8}, {"id": "script_name_fix_rationale_1", "label": "ScriptNameFix WSGI middleware. When nginx strips the path prefix before forward", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_app_utils_script_name_fix_py", "target": "script_name_fix_scriptnamefix", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L15", "weight": 1.0}, {"source": "script_name_fix_scriptnamefix", "target": "script_name_fix_scriptnamefix_init", "relation": "method", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L16", "weight": 1.0}, {"source": "script_name_fix_scriptnamefix", "target": "script_name_fix_scriptnamefix_call", "relation": "method", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L19", "weight": 1.0}, {"source": "script_name_fix_rationale_1", "target": "home_scheianu_digiserver_v2_app_utils_script_name_fix_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [{"caller_nid": "script_name_fix_scriptnamefix_call", "callee": "rstrip", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L20"}, {"caller_nid": "script_name_fix_scriptnamefix_call", "callee": "get", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L20"}, {"caller_nid": "script_name_fix_scriptnamefix_call", "callee": "get", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L23"}, {"caller_nid": "script_name_fix_scriptnamefix_call", "callee": "startswith", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L24"}, {"caller_nid": "script_name_fix_scriptnamefix_call", "callee": "len", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L25"}, {"caller_nid": "script_name_fix_scriptnamefix_call", "callee": "app", "source_file": "/home/scheianu/digiserver-v2/app/utils/script_name_fix.py", "source_location": "L26"}]}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_old_code_documentation_fix_player_user_schema_py", "label": "fix_player_user_schema.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "fix_player_user_schema_main", "label": "main()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L10", "complexity": 1, "loc": 13}], "edges": [{"source": "home_scheianu_digiserver_v2_old_code_documentation_fix_player_user_schema_py", "target": "sys", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L3", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_old_code_documentation_fix_player_user_schema_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_old_code_documentation_fix_player_user_schema_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L7", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_old_code_documentation_fix_player_user_schema_py", "target": "app_models_player_user", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L8", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_old_code_documentation_fix_player_user_schema_py", "target": "fix_player_user_schema_main", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L10", "weight": 1.0}], "raw_calls": [{"caller_nid": "fix_player_user_schema_main", "callee": "create_app", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L11"}, {"caller_nid": "fix_player_user_schema_main", "callee": "app_context", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L12"}, {"caller_nid": "fix_player_user_schema_main", "callee": "print", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L14"}, {"caller_nid": "fix_player_user_schema_main", "callee": "execute", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L15"}, {"caller_nid": "fix_player_user_schema_main", "callee": "text", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L15"}, {"caller_nid": "fix_player_user_schema_main", "callee": "commit", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L16"}, {"caller_nid": "fix_player_user_schema_main", "callee": "print", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L19"}, {"caller_nid": "fix_player_user_schema_main", "callee": "create", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L20"}, {"caller_nid": "fix_player_user_schema_main", "callee": "print", "source_file": "/home/scheianu/digiserver-v2/old_code_documentation/fix_player_user_schema.py", "source_location": "L22"}]}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_app_models_player_user_py", "label": "player_user.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "player_user_playeruser", "label": "PlayerUser", "file_type": "code", "type": "CLASS", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L7", "complexity": 3, "loc": 31}, {"id": "player_user_playeruser_repr", "label": ".__repr__()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L25", "complexity": 1, "loc": 3}, {"id": "player_user_playeruser_to_dict", "label": ".to_dict()", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L29", "complexity": 3, "loc": 9}, {"id": "player_user_rationale_1", "label": "Player user model for managing user codes and names.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L1"}, {"id": "player_user_rationale_8", "label": "Player user model for managing user codes and names globally. Attribute", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L8"}, {"id": "player_user_rationale_26", "label": "String representation of PlayerUser.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L26"}, {"id": "player_user_rationale_30", "label": "Convert to dictionary for API responses.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L30"}], "edges": [{"source": "home_scheianu_digiserver_v2_app_models_player_user_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_player_user_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L4", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_models_player_user_py", "target": "player_user_playeruser", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L7", "weight": 1.0}, {"source": "player_user_playeruser", "target": "player_user_playeruser_repr", "relation": "method", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L25", "weight": 1.0}, {"source": "player_user_playeruser", "target": "player_user_playeruser_to_dict", "relation": "method", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L29", "weight": 1.0}, {"source": "player_user_rationale_1", "target": "home_scheianu_digiserver_v2_app_models_player_user_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L1", "weight": 1.0}, {"source": "player_user_rationale_8", "target": "player_user_playeruser", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L8", "weight": 1.0}, {"source": "player_user_rationale_26", "target": "player_user_playeruser_repr", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L26", "weight": 1.0}, {"source": "player_user_rationale_30", "target": "player_user_playeruser_to_dict", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L30", "weight": 1.0}], "raw_calls": [{"caller_nid": "player_user_playeruser_to_dict", "callee": "isoformat", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L35"}, {"caller_nid": "player_user_playeruser_to_dict", "callee": "isoformat", "source_file": "/home/scheianu/digiserver-v2/app/models/player_user.py", "source_location": "L36"}]}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_migrations_migrate_player_user_global_py", "label": "migrate_player_user_global.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "migrate_player_user_global_rationale_1", "label": "Migrate player_user table to remove player_id and make user_code unique globally", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_migrations_migrate_player_user_global_py", "target": "sys", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_migrate_player_user_global_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_migrate_player_user_global_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_migrate_player_user_global_py", "target": "sqlalchemy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py", "source_location": "L7", "weight": 1.0}, {"source": "migrate_player_user_global_rationale_1", "target": "home_scheianu_digiserver_v2_migrations_migrate_player_user_global_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_migrations_add_original_filename_to_content_py", "label": "add_original_filename_to_content.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "add_original_filename_to_content_rationale_1", "label": "Add original_filename column to content table. This preserves the pristine uplo", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_migrations_add_original_filename_to_content_py", "target": "sys", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py", "source_location": "L8", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_original_filename_to_content_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py", "source_location": "L11", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_original_filename_to_content_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py", "source_location": "L12", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_original_filename_to_content_py", "target": "sqlalchemy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py", "source_location": "L13", "weight": 1.0}, {"source": "add_original_filename_to_content_rationale_1", "target": "home_scheianu_digiserver_v2_migrations_add_original_filename_to_content_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_migrations_add_email_to_https_config_py", "label": "add_email_to_https_config.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "add_email_to_https_config_rationale_1", "label": "Add email field to https_config table.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_migrations_add_email_to_https_config_py", "target": "sys", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_email_to_https_config_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_email_to_https_config_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_email_to_https_config_py", "target": "sqlalchemy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py", "source_location": "L7", "weight": 1.0}, {"source": "add_email_to_https_config_rationale_1", "target": "home_scheianu_digiserver_v2_migrations_add_email_to_https_config_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_app_extensions_py", "label": "extensions.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "extensions_rationale_1", "label": "Flask extensions initialization Centralized extension management for the applica", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_app_extensions_py", "target": "flask_sqlalchemy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_extensions_py", "target": "flask_bcrypt", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_extensions_py", "target": "flask_login", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L7", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_extensions_py", "target": "flask_migrate", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L8", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_extensions_py", "target": "flask_caching", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L9", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_extensions_py", "target": "flask_cors", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L10", "weight": 1.0}, {"source": "extensions_rationale_1", "target": "home_scheianu_digiserver_v2_app_extensions_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/extensions.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_migrations_add_url_to_content_py", "label": "add_url_to_content.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/migrations/add_url_to_content.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "add_url_to_content_rationale_1", "label": "Add url column to content table for weblink support.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/migrations/add_url_to_content.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_migrations_add_url_to_content_py", "target": "sys", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_url_to_content.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_url_to_content_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_url_to_content.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_url_to_content_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_url_to_content.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_url_to_content_py", "target": "sqlalchemy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_url_to_content.py", "source_location": "L7", "weight": 1.0}, {"source": "add_url_to_content_rationale_1", "target": "home_scheianu_digiserver_v2_migrations_add_url_to_content_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_url_to_content.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_app_utils_init_py", "label": "__init__.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/app/utils/__init__.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "init_rationale_1", "label": "Utils package for digiserver-v2.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/app/utils/__init__.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_app_utils_init_py", "target": "app_utils_logger", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_utils_init_py", "target": "app_utils_uploads", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_utils_init_py", "target": "app_utils_group_player_management", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/__init__.py", "source_location": "L13", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_app_utils_init_py", "target": "app_utils_pptx_converter", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/__init__.py", "source_location": "L21", "weight": 1.0}, {"source": "init_rationale_1", "target": "home_scheianu_digiserver_v2_app_utils_init_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/app/utils/__init__.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_scheianu_digiserver_v2_migrations_add_player_user_table_py", "label": "add_player_user_table.py", "file_type": "code", "type": "FUNCTION", "source_file": "/home/scheianu/digiserver-v2/migrations/add_player_user_table.py", "source_location": "L1", "complexity": 1, "loc": 1}, {"id": "add_player_user_table_rationale_1", "label": "Add player_user table for user code mappings.", "file_type": "rationale", "source_file": "/home/scheianu/digiserver-v2/migrations/add_player_user_table.py", "source_location": "L1"}], "edges": [{"source": "home_scheianu_digiserver_v2_migrations_add_player_user_table_py", "target": "sys", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_player_user_table.py", "source_location": "L2", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_player_user_table_py", "target": "app_app", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_player_user_table.py", "source_location": "L5", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_player_user_table_py", "target": "app_extensions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_player_user_table.py", "source_location": "L6", "weight": 1.0}, {"source": "home_scheianu_digiserver_v2_migrations_add_player_user_table_py", "target": "app_models_player_user", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_player_user_table.py", "source_location": "L7", "weight": 1.0}, {"source": "add_player_user_table_rationale_1", "target": "home_scheianu_digiserver_v2_migrations_add_player_user_table_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/home/scheianu/digiserver-v2/migrations/add_player_user_table.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+36
View File
@@ -0,0 +1,36 @@
{
"hotspots": [
{
"label": "receive_edited_media()",
"risk": 0.62,
"type": "FUNCTION"
},
{
"label": "deploy_player_to_host()",
"risk": 0.614,
"type": "FUNCTION"
},
{
"label": "add_player()",
"risk": 0.592,
"type": "FUNCTION"
},
{
"label": "upload_media()",
"risk": 0.537,
"type": "FUNCTION"
},
{
"label": "manage_player()",
"risk": 0.429,
"type": "FUNCTION"
}
],
"blast_radius": {
"log_action()": 116,
"PlayerEdit": 99,
"PlayerUser": 74,
"Playlist": 33,
"CaddyConfigGenerator": 31
}
}
+159
View File
@@ -0,0 +1,159 @@
{
"projectName": "digiserver-v2",
"communityLabels": {
"0": "Community 0",
"1": "Community 1",
"2": "Community 2",
"3": "Community 3",
"4": "Community 4",
"5": "Community 5",
"6": "Community 6",
"7": "Community 7",
"8": "Community 8",
"9": "Community 9",
"10": "Community 10",
"11": "Community 11",
"12": "Community 12",
"13": "Community 13",
"14": "Community 14",
"15": "Community 15",
"16": "Community 16",
"17": "Community 17",
"18": "Community 18",
"19": "Community 19",
"20": "Community 20",
"21": "Community 21",
"22": "Community 22",
"23": "Community 23",
"24": "Community 24",
"25": "Community 25",
"26": "Community 26",
"27": "Community 27",
"28": "Community 28",
"29": "Community 29",
"30": "Community 30",
"31": "Community 31",
"32": "Community 32",
"33": "Community 33",
"34": "Community 34",
"35": "Community 35",
"36": "Community 36",
"37": "Community 37",
"38": "Community 38",
"39": "Community 39",
"40": "Community 40"
},
"communitySummaries": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": "",
"7": "",
"8": "",
"9": "",
"10": "",
"11": "",
"12": "",
"13": "",
"14": "",
"15": "",
"16": "",
"17": "",
"18": "",
"19": "",
"20": "",
"21": "",
"22": "",
"23": "",
"24": "",
"25": "",
"26": "",
"27": "",
"28": "",
"29": "",
"30": "",
"31": "",
"32": "",
"33": "",
"34": "",
"35": "",
"36": "",
"37": "",
"38": "",
"39": "",
"40": ""
},
"communityLinks": {
"1": {
"11": 3,
"0": 21,
"4": 1,
"6": 5,
"10": 1,
"3": 27
},
"11": {
"0": 1,
"1": 2,
"3": 1
},
"0": {
"9": 2,
"4": 1,
"10": 1,
"7": 2
},
"4": {},
"6": {
"7": 1,
"5": 1,
"0": 14,
"11": 1,
"1": 16,
"10": 1,
"3": 16
},
"10": {
"6": 1,
"11": 1
},
"3": {
"0": 14,
"5": 5,
"7": 1,
"1": 25,
"10": 1,
"2": 1
},
"9": {
"0": 5,
"5": 1
},
"5": {
"3": 9,
"4": 1,
"7": 1,
"0": 2,
"1": 1,
"6": 3,
"2": 1
},
"2": {
"7": 2,
"3": 25,
"5": 6,
"0": 25
},
"7": {
"0": 3,
"3": 2,
"8": 1,
"6": 1,
"1": 1
},
"8": {}
}
}
+1
View File
@@ -0,0 +1 @@
[]
+243
View File
@@ -0,0 +1,243 @@
# CaddyConfigGenerator
> God node · 31 connections · [/home/scheianu/digiserver-v2/app/utils/caddy_manager.py](file:///home/scheianu/digiserver-v2/app/utils/caddy_manager.py#L36)
## Call Trace Diagram
```mermaid
sequenceDiagram
participant P0 as CaddyConfigGenerator
participant P1 as HTTPSConfig
participant P2 as Models package for digiserver-v2.
participant P3 as PlayerEdit
participant P4 as PlayerUser
participant P5 as Playlist
participant P6 as Content
participant P7 as User
participant P8 as Player
participant P9 as ServerLog
participant P10 as Group
participant P11 as PlayerFeedback
participant P12 as Players blueprint for player management and display.
participant P13 as Display list of all players.
participant P14 as Add a new player with optional SSH deployment.
participant P15 as Regenerate authentication code for a player.
participant P16 as Redirect to manage player page (combined view).
participant P17 as Manage player - edit credentials, assign playlist, view logs.
participant P18 as Display all edited media files from this player.
participant P19 as Display a tabular report of all edited media from this player.
participant P20 as Display player fullscreen view (no authentication required for players).
participant P21 as Get playlist for a player based on their assigned playlist. Args:
participant P22 as Legacy endpoint - Content reordering now handled in playlist management.
participant P23 as Delete multiple players at once.
participant P24 as Assign multiple players to a playlist.
participant P25 as Return deployment status for all players (used by polling JS).
participant P26 as Reorder items in player's playlist.
participant P27 as Remove content from player's playlist.
participant P28 as Add https_config table for HTTPS configuration management.
participant P29 as Caddy configuration generator and manager.
participant P30 as Generate Caddyfile configuration based on HTTPSConfig.
participant P31 as Generate a complete Caddyfile. Behaviour: - HTTPS disabled / no
participant P32 as Write Caddyfile to disk. The default path is /etc/caddy/Caddyfile — the
participant P33 as Push the current Caddyfile to Caddy via its admin API (/load). Caddy ap
participant P34 as Write Caddyfile to disk. Args: caddyfile_content: C
participant P35 as Reload Caddy configuration without restart. Note: Caddy monitor
participant P36 as Admin blueprint for user management and system settings.
participant P37 as Decorator to require admin role for route access.
participant P38 as Display admin panel with system overview.
participant P39 as Create a new user account.
participant P40 as Change user role between user and admin.
participant P41 as Delete a user account.
participant P42 as Change application theme.
participant P43 as Upload custom logo for application.
participant P44 as Clear all server logs.
participant P45 as Display user management page.
participant P46 as Get system information as JSON.
participant P47 as Display leftover media files not assigned to any playlist.
participant P48 as Delete all leftover images that are not part of any playlist
participant P49 as Delete all leftover videos that are not part of any playlist
participant P50 as Delete a single leftover content file
participant P51 as Show system dependencies status.
participant P52 as Install LibreOffice for PPTX conversion.
participant P53 as Install Emoji Fonts for better UI display.
participant P54 as Logo customization page.
participant P55 as Upload login page logo.
participant P56 as Display and manage users that edit images on players.
participant P57 as Update editing user name.
participant P58 as Display HTTPS configuration management page.
participant P59 as Update HTTPS configuration.
participant P60 as Get current HTTPS configuration status as JSON.
participant P61 as Path to the persisted player-build settings (in the instance folder).
participant P62 as Display the 'Build player files for deployment' admin page.
participant P63 as Build/refresh the staged player code and/or write its base config.
P0->>+ P1: uses
P1-->>- P0: return
P1->>+ P0: uses
P0-->>- P1: return
P1->>+ P2: uses
P2-->>- P1: return
P2->>+ P3: uses
P3-->>- P2: return
P2->>+ P4: uses
P4-->>- P2: return
P2->>+ P5: uses
P5-->>- P2: return
P2->>+ P1: uses
P1-->>- P2: return
P2->>+ P6: uses
P6-->>- P2: return
P2->>+ P7: uses
P7-->>- P2: return
P2->>+ P8: uses
P8-->>- P2: return
P2->>+ P9: uses
P9-->>- P2: return
P2->>+ P10: uses
P10-->>- P2: return
P2->>+ P11: uses
P11-->>- P2: return
P1->>+ P12: uses
P12-->>- P1: return
P12->>+ P3: uses
P3-->>- P12: return
P12->>+ P4: uses
P4-->>- P12: return
P12->>+ P1: uses
P1-->>- P12: return
P1->>+ P13: uses
P13-->>- P1: return
P1->>+ P14: uses
P14-->>- P1: return
P1->>+ P15: uses
P15-->>- P1: return
P1->>+ P16: uses
P16-->>- P1: return
P1->>+ P17: uses
P17-->>- P1: return
P1->>+ P18: uses
P18-->>- P1: return
P1->>+ P19: uses
P19-->>- P1: return
P1->>+ P20: uses
P20-->>- P1: return
P1->>+ P21: uses
P21-->>- P1: return
P1->>+ P22: uses
P22-->>- P1: return
P1->>+ P23: uses
P23-->>- P1: return
P1->>+ P24: uses
P24-->>- P1: return
P1->>+ P25: uses
P25-->>- P1: return
P1->>+ P26: uses
P26-->>- P1: return
P1->>+ P27: uses
P27-->>- P1: return
P1->>+ P28: uses
P28-->>- P1: return
P1->>+ P29: uses
P29-->>- P1: return
P1->>+ P30: uses
P30-->>- P1: return
P1->>+ P31: uses
P31-->>- P1: return
P1->>+ P32: uses
P32-->>- P1: return
P1->>+ P33: uses
P33-->>- P1: return
P1->>+ P34: uses
P34-->>- P1: return
P1->>+ P35: uses
P35-->>- P1: return
P0->>+ P36: uses
P36-->>- P0: return
P0->>+ P37: uses
P37-->>- P0: return
P0->>+ P38: uses
P38-->>- P0: return
P0->>+ P39: uses
P39-->>- P0: return
P0->>+ P40: uses
P40-->>- P0: return
P0->>+ P41: uses
P41-->>- P0: return
P0->>+ P42: uses
P42-->>- P0: return
P0->>+ P43: uses
P43-->>- P0: return
P0->>+ P44: uses
P44-->>- P0: return
P0->>+ P45: uses
P45-->>- P0: return
P0->>+ P46: uses
P46-->>- P0: return
P0->>+ P47: uses
P47-->>- P0: return
P0->>+ P48: uses
P48-->>- P0: return
P0->>+ P49: uses
P49-->>- P0: return
P0->>+ P50: uses
P50-->>- P0: return
P0->>+ P51: uses
P51-->>- P0: return
P0->>+ P52: uses
P52-->>- P0: return
P0->>+ P53: uses
P53-->>- P0: return
P0->>+ P54: uses
P54-->>- P0: return
P0->>+ P55: uses
P55-->>- P0: return
P0->>+ P56: uses
P56-->>- P0: return
P0->>+ P57: uses
P57-->>- P0: return
P0->>+ P58: uses
P58-->>- P0: return
P0->>+ P59: uses
P59-->>- P0: return
P0->>+ P60: uses
P60-->>- P0: return
P0->>+ P61: uses
P61-->>- P0: return
P0->>+ P62: uses
P62-->>- P0: return
P0->>+ P63: uses
P63-->>- P0: return
```
## Connections by Relation
### contains
- [[caddy_manager.py]] `EXTRACTED`
### rationale_for
- [[Generate Caddyfile configuration based on HTTPSConfig.]] `EXTRACTED`
### uses
- [[HTTPSConfig]] `INFERRED`
- [[Admin blueprint for user management and system settings.]] `INFERRED`
- [[Decorator to require admin role for route access.]] `INFERRED`
- [[Display admin panel with system overview.]] `INFERRED`
- [[Create a new user account.]] `INFERRED`
- [[Change user role between user and admin.]] `INFERRED`
- [[Delete a user account.]] `INFERRED`
- [[Change application theme.]] `INFERRED`
- [[Upload custom logo for application.]] `INFERRED`
- [[Clear all server logs.]] `INFERRED`
- [[Display user management page.]] `INFERRED`
- [[Get system information as JSON.]] `INFERRED`
- [[Display leftover media files not assigned to any playlist.]] `INFERRED`
- [[Delete all leftover images that are not part of any playlist]] `INFERRED`
- [[Delete all leftover videos that are not part of any playlist]] `INFERRED`
- [[Delete a single leftover content file]] `INFERRED`
- [[Show system dependencies status.]] `INFERRED`
- [[Install LibreOffice for PPTX conversion.]] `INFERRED`
- [[Install Emoji Fonts for better UI display.]] `INFERRED`
- [[Logo customization page.]] `INFERRED`
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+66
View File
@@ -0,0 +1,66 @@
# Community 0
> 90 nodes · cohesion 0.03
## Key Concepts
- [log_action()](file:///home/scheianu/digiserver-v2/app/utils/logger.py#L9) (116 connections)
- [content_old.py](file:///home/scheianu/digiserver-v2/app/blueprints/content_old.py#L1) (13 connections)
- [blueprint_groups.py](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L1) (13 connections)
- [ServerLog](file:///home/scheianu/digiserver-v2/app/models/server_log.py#L8) (12 connections)
- [group_player_management.py](file:///home/scheianu/digiserver-v2/app/utils/group_player_management.py#L1) (8 connections)
- [get_player_status_info()](file:///home/scheianu/digiserver-v2/app/utils/group_player_management.py#L10) (7 connections)
- [logger.py](file:///home/scheianu/digiserver-v2/app/utils/logger.py#L1) (7 connections)
- [auth.py](file:///home/scheianu/digiserver-v2/app/blueprints/auth.py#L1) (5 connections)
- [server_log.py](file:///home/scheianu/digiserver-v2/app/models/server_log.py#L1) (5 connections)
- [group_fullscreen()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L214) (4 connections)
- [group_stats()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L394) (4 connections)
- [groups_list()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L16) (4 connections)
- [manage_group()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L175) (4 connections)
- [get_group_statistics()](file:///home/scheianu/digiserver-v2/app/utils/group_player_management.py#L54) (4 connections)
- [add_content_to_group()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L298) (3 connections)
- [add_player_to_group()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L239) (3 connections)
- [remove_content_from_group()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L335) (3 connections)
- [remove_player_from_group()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L269) (3 connections)
- [reorder_group_content()](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py#L365) (3 connections)
- [bulk_delete_content()](file:///home/scheianu/digiserver-v2/app/blueprints/content_old.py#L337) (3 connections)
- [check_duplicates()](file:///home/scheianu/digiserver-v2/app/blueprints/content_old.py#L453) (3 connections)
- [content_groups_info()](file:///home/scheianu/digiserver-v2/app/blueprints/content_old.py#L478) (3 connections)
- [content_list()](file:///home/scheianu/digiserver-v2/app/blueprints/content_old.py#L30) (3 connections)
- [content_statistics()](file:///home/scheianu/digiserver-v2/app/blueprints/content_old.py#L419) (3 connections)
- [delete_by_filename()](file:///home/scheianu/digiserver-v2/app/blueprints/content_old.py#L289) (3 connections)
- *... and 65 more nodes in this community*
## Class Diagram
```mermaid
classDiagram
class ServerLog {
+server_log.py()
+.__repr__()
}
```
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/blueprints/auth.py](file:///home/scheianu/digiserver-v2/app/blueprints/auth.py)
- [/home/scheianu/digiserver-v2/app/blueprints/content_old.py](file:///home/scheianu/digiserver-v2/app/blueprints/content_old.py)
- [/home/scheianu/digiserver-v2/app/blueprints/main.py](file:///home/scheianu/digiserver-v2/app/blueprints/main.py)
- [/home/scheianu/digiserver-v2/app/models/server_log.py](file:///home/scheianu/digiserver-v2/app/models/server_log.py)
- [/home/scheianu/digiserver-v2/app/utils/group_player_management.py](file:///home/scheianu/digiserver-v2/app/utils/group_player_management.py)
- [/home/scheianu/digiserver-v2/app/utils/logger.py](file:///home/scheianu/digiserver-v2/app/utils/logger.py)
- [/home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py](file:///home/scheianu/digiserver-v2/old_code_documentation/blueprint_groups.py)
## Audit Trail
- EXTRACTED: 180 (52%)
- INFERRED: 165 (48%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+69
View File
@@ -0,0 +1,69 @@
# Community 1
> 80 nodes · cohesion 0.04
## Key Concepts
- [PlayerUser](file:///home/scheianu/digiserver-v2/app/models/player_user.py#L7) (74 connections)
- [CaddyConfigGenerator](file:///home/scheianu/digiserver-v2/app/utils/caddy_manager.py#L36) (31 connections)
- [admin.py](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L1) (31 connections)
- [get_config()](file:///home/scheianu/digiserver-v2/app/models/https_config.py#L43) (8 connections)
- [build_player()](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L1028) (7 connections)
- [update_https_config()](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L886) (7 connections)
- [https_config_status()](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L995) (5 connections)
- [caddy_manager.py](file:///home/scheianu/digiserver-v2/app/utils/caddy_manager.py#L1) (5 connections)
- [create_user()](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L79) (4 connections)
- [https_config()](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L856) (4 connections)
- [_player_build_meta_path()](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L1019) (4 connections)
- [Admin blueprint for user management and system settings.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L1) (4 connections)
- [Path to the persisted player-build settings (in the instance folder).](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L1020) (4 connections)
- [Display the 'Build player files for deployment' admin page.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L1029) (4 connections)
- [Change user role between user and admin.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L126) (4 connections)
- [Delete a user account.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L160) (4 connections)
- [Change application theme.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L188) (4 connections)
- [Decorator to require admin role for route access.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L19) (4 connections)
- [Upload custom logo for application.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L212) (4 connections)
- [Clear all server logs.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L254) (4 connections)
- [Display user management page.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L274) (4 connections)
- [Get system information as JSON.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L323) (4 connections)
- [Display leftover media files not assigned to any playlist.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L355) (4 connections)
- [Display admin panel with system overview.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L36) (4 connections)
- [Delete all leftover images that are not part of any playlist](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L407) (4 connections)
- *... and 55 more nodes in this community*
## Class Diagram
```mermaid
classDiagram
class CaddyConfigGenerator {
+caddy_manager.py()
}
class PlayerUser {
+player_user.py()
+.__repr__()
+.to_dict()
}
```
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/blueprints/admin.py](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py)
- [/home/scheianu/digiserver-v2/app/models/https_config.py](file:///home/scheianu/digiserver-v2/app/models/https_config.py)
- [/home/scheianu/digiserver-v2/app/models/player_edit.py](file:///home/scheianu/digiserver-v2/app/models/player_edit.py)
- [/home/scheianu/digiserver-v2/app/models/player_user.py](file:///home/scheianu/digiserver-v2/app/models/player_user.py)
- [/home/scheianu/digiserver-v2/app/utils/caddy_manager.py](file:///home/scheianu/digiserver-v2/app/utils/caddy_manager.py)
- [/home/scheianu/digiserver-v2/migrations/add_player_user_table.py](file:///home/scheianu/digiserver-v2/migrations/add_player_user_table.py)
## Audit Trail
- EXTRACTED: 156 (41%)
- INFERRED: 229 (59%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+45
View File
@@ -0,0 +1,45 @@
# Community 10
> 20 nodes · cohesion 0.12
## Key Concepts
- [ssh_deploy.py](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L1) (8 connections)
- [deploy_player_to_host()](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L311) (8 connections)
- [background_player_deployment()](file:///home/scheianu/digiserver-v2/app/utils/background_tasks.py#L29) (4 connections)
- [generate_app_config()](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L267) (4 connections)
- [get_local_player_code_status()](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L15) (4 connections)
- [run_background_task()](file:///home/scheianu/digiserver-v2/app/utils/background_tasks.py#L9) (3 connections)
- [background_tasks.py](file:///home/scheianu/digiserver-v2/app/utils/background_tasks.py#L1) (3 connections)
- [parse_server_address()](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L241) (3 connections)
- [test_ssh_connection()](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L85) (3 connections)
- [generate_player_config()](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L149) (2 connections)
- [Background task execution for long-running operations.](file:///home/scheianu/digiserver-v2/app/utils/background_tasks.py#L1) (1 connections)
- [Run a function in a background thread, with a Flask app context pushed.](file:///home/scheianu/digiserver-v2/app/utils/background_tasks.py#L10) (1 connections)
- [Deploy player code to host in background. Args: hostname: SSH h](file:///home/scheianu/digiserver-v2/app/utils/background_tasks.py#L43) (1 connections)
- [SSH deployment utilities for player provisioning.](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L1) (1 connections)
- [Generate player configuration JSON for connecting to DigiServer. Args:](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L156) (1 connections)
- [Check status of pre-staged player code. Args: player_code_dir: Opti](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L16) (1 connections)
- [Derive the values the player needs from a DigiServer URL. The player's conf](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L242) (1 connections)
- [Generate the config/app_config.json the player actually reads. This is what](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L277) (1 connections)
- [Deploy player code to remote host. Args: hostname: Target hostn](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L326) (1 connections)
- [Test SSH connection to a remote host. Args: hostname: Target ho](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py#L86) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/utils/background_tasks.py](file:///home/scheianu/digiserver-v2/app/utils/background_tasks.py)
- [/home/scheianu/digiserver-v2/app/utils/ssh_deploy.py](file:///home/scheianu/digiserver-v2/app/utils/ssh_deploy.py)
## Audit Trail
- EXTRACTED: 45 (87%)
- INFERRED: 7 (13%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+44
View File
@@ -0,0 +1,44 @@
# Community 11
> 19 nodes · cohesion 0.15
## Key Concepts
- [build_player_action()](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L1074) (9 connections)
- [player_build.py](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L1) (9 connections)
- [build_player_files()](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L49) (5 connections)
- [get_short_head()](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L38) (5 connections)
- [Build/refresh the staged player code and/or write its base config.](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py#L1075) (4 connections)
- [get_player_server_settings()](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L188) (4 connections)
- [load_build_settings()](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L165) (4 connections)
- [write_base_config()](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L128) (4 connections)
- [make_build_record()](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L208) (3 connections)
- [_run_git()](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L28) (3 connections)
- [save_build_settings()](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L176) (3 connections)
- [Utilities for building/staging the player files on the server. Admins use the "](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L1) (1 connections)
- [Write a base ``config/app_config.json`` into the staged player code. ``scre](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L137) (1 connections)
- [Load saved build settings from ``meta_path`` (or None if absent/invalid).](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L166) (1 connections)
- [Persist build settings to ``meta_path``.](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L177) (1 connections)
- [Return the saved server address settings for deployment, if available. Retu](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L189) (1 connections)
- [Assemble the metadata record to persist after a build.](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L210) (1 connections)
- [Return the short git commit of the staged code, or 'unknown'.](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L39) (1 connections)
- [Clone or refresh the player source into ``player_code_dir``. If the directo](file:///home/scheianu/digiserver-v2/app/utils/player_build.py#L50) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/blueprints/admin.py](file:///home/scheianu/digiserver-v2/app/blueprints/admin.py)
- [/home/scheianu/digiserver-v2/app/utils/player_build.py](file:///home/scheianu/digiserver-v2/app/utils/player_build.py)
## Audit Trail
- EXTRACTED: 44 (72%)
- INFERRED: 17 (28%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+32
View File
@@ -0,0 +1,32 @@
# Community 12
> 8 nodes · cohesion 0.29
## Key Concepts
- [pptx_converter.py](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py#L1) (4 connections)
- [cleanup_libreoffice_processes()](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py#L11) (3 connections)
- [pptx_to_pdf_libreoffice()](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py#L20) (3 connections)
- [validate_pptx_file()](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py#L86) (2 connections)
- [PowerPoint to PDF converter using LibreOffice.](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py#L1) (1 connections)
- [Clean up any hanging LibreOffice processes.](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py#L12) (1 connections)
- [Convert PPTX to PDF using LibreOffice for highest quality. This functio](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py#L21) (1 connections)
- [Validate if file is a valid PowerPoint file. Args: filepath: Pa](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py#L87) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/utils/pptx_converter.py](file:///home/scheianu/digiserver-v2/app/utils/pptx_converter.py)
## Audit Trail
- EXTRACTED: 16 (100%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+28
View File
@@ -0,0 +1,28 @@
# Community 13
> 4 nodes · cohesion 0.50
## Key Concepts
- [migrate_add_edit_enabled.py](file:///home/scheianu/digiserver-v2/old_code_documentation/migrate_add_edit_enabled.py#L1) (2 connections)
- [migrate()](file:///home/scheianu/digiserver-v2/old_code_documentation/migrate_add_edit_enabled.py#L7) (2 connections)
- [Migration: Add edit_on_player_enabled column to playlist_content table.](file:///home/scheianu/digiserver-v2/old_code_documentation/migrate_add_edit_enabled.py#L1) (1 connections)
- [Add edit_on_player_enabled column to playlist_content.](file:///home/scheianu/digiserver-v2/old_code_documentation/migrate_add_edit_enabled.py#L8) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/old_code_documentation/migrate_add_edit_enabled.py](file:///home/scheianu/digiserver-v2/old_code_documentation/migrate_add_edit_enabled.py)
## Audit Trail
- EXTRACTED: 6 (100%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+26
View File
@@ -0,0 +1,26 @@
# Community 14
> 2 nodes · cohesion 1.00
## Key Concepts
- [migrate_player_user_global.py](file:///home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py#L1) (1 connections)
- [Migrate player_user table to remove player_id and make user_code unique globally](file:///home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py#L1) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py](file:///home/scheianu/digiserver-v2/migrations/migrate_player_user_global.py)
## Audit Trail
- EXTRACTED: 2 (100%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+26
View File
@@ -0,0 +1,26 @@
# Community 15
> 2 nodes · cohesion 1.00
## Key Concepts
- [Add original_filename column to content table. This preserves the pristine uplo](file:///home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py#L1) (1 connections)
- [add_original_filename_to_content.py](file:///home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py#L1) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py](file:///home/scheianu/digiserver-v2/migrations/add_original_filename_to_content.py)
## Audit Trail
- EXTRACTED: 2 (100%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+26
View File
@@ -0,0 +1,26 @@
# Community 16
> 2 nodes · cohesion 1.00
## Key Concepts
- [Add url column to content table for weblink support.](file:///home/scheianu/digiserver-v2/migrations/add_url_to_content.py#L1) (1 connections)
- [add_url_to_content.py](file:///home/scheianu/digiserver-v2/migrations/add_url_to_content.py#L1) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/migrations/add_url_to_content.py](file:///home/scheianu/digiserver-v2/migrations/add_url_to_content.py)
## Audit Trail
- EXTRACTED: 2 (100%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+26
View File
@@ -0,0 +1,26 @@
# Community 17
> 2 nodes · cohesion 1.00
## Key Concepts
- [Add deployment tracking columns to player table.](file:///home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py#L1) (1 connections)
- [add_deployment_fields_to_player.py](file:///home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py#L1) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py](file:///home/scheianu/digiserver-v2/migrations/add_deployment_fields_to_player.py)
## Audit Trail
- EXTRACTED: 2 (100%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+26
View File
@@ -0,0 +1,26 @@
# Community 18
> 2 nodes · cohesion 1.00
## Key Concepts
- [Add email field to https_config table.](file:///home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py#L1) (1 connections)
- [add_email_to_https_config.py](file:///home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py#L1) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py](file:///home/scheianu/digiserver-v2/migrations/add_email_to_https_config.py)
## Audit Trail
- EXTRACTED: 2 (100%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+26
View File
@@ -0,0 +1,26 @@
# Community 19
> 2 nodes · cohesion 1.00
## Key Concepts
- [Flask extensions initialization Centralized extension management for the applica](file:///home/scheianu/digiserver-v2/app/extensions.py#L1) (1 connections)
- [extensions.py](file:///home/scheianu/digiserver-v2/app/extensions.py#L1) (1 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/extensions.py](file:///home/scheianu/digiserver-v2/app/extensions.py)
## Audit Trail
- EXTRACTED: 2 (100%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+52
View File
@@ -0,0 +1,52 @@
# Community 2
> 68 nodes · cohesion 0.04
## Key Concepts
- [content.py](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L1) (27 connections)
- [.increment_version()](file:///home/scheianu/digiserver-v2/app/models/playlist.py#L70) (18 connections)
- [upload_media()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L1220) (9 connections)
- [playlist.py](file:///home/scheianu/digiserver-v2/app/blueprints/playlist.py#L1) (8 connections)
- [process_file_in_background()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L798) (7 connections)
- [process_presentation_file()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L977) (7 connections)
- [add_weblink()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L272) (5 connections)
- [add_weblink_to_playlist()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L352) (5 connections)
- [process_pdf_file()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L727) (5 connections)
- [process_video_file_extended()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L702) (5 connections)
- [playlist.py](file:///home/scheianu/digiserver-v2/app/models/playlist.py#L1) (5 connections)
- [add_content_to_playlist()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L222) (4 connections)
- [bulk_remove_from_playlist()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L455) (4 connections)
- [optimize_image_to_fullhd()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L1203) (4 connections)
- [process_image_file()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L676) (4 connections)
- [remove_content_from_playlist()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L418) (4 connections)
- [reorder_playlist_content()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L497) (4 connections)
- [resize_image_to_fullhd()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L1145) (4 connections)
- [update_playlist_content_duration()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L620) (4 connections)
- [update_playlist_content_edit_enabled()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L579) (4 connections)
- [update_playlist_content_muted()](file:///home/scheianu/digiserver-v2/app/blueprints/content.py#L538) (4 connections)
- [add_to_playlist()](file:///home/scheianu/digiserver-v2/app/blueprints/playlist.py#L33) (4 connections)
- [clear_playlist()](file:///home/scheianu/digiserver-v2/app/blueprints/playlist.py#L278) (4 connections)
- [remove_from_playlist()](file:///home/scheianu/digiserver-v2/app/blueprints/playlist.py#L88) (4 connections)
- [reorder_playlist()](file:///home/scheianu/digiserver-v2/app/blueprints/playlist.py#L143) (4 connections)
- *... and 43 more nodes in this community*
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/blueprints/content.py](file:///home/scheianu/digiserver-v2/app/blueprints/content.py)
- [/home/scheianu/digiserver-v2/app/blueprints/playlist.py](file:///home/scheianu/digiserver-v2/app/blueprints/playlist.py)
- [/home/scheianu/digiserver-v2/app/models/playlist.py](file:///home/scheianu/digiserver-v2/app/models/playlist.py)
## Audit Trail
- EXTRACTED: 154 (64%)
- INFERRED: 86 (36%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 20
> 1 nodes · cohesion 1.00
## Key Concepts
- [Check if feedback indicates an error.](file:///home/scheianu/digiserver-v2/app/models/player_feedback.py#L43) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/player_feedback.py](file:///home/scheianu/digiserver-v2/app/models/player_feedback.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 21
> 1 nodes · cohesion 1.00
## Key Concepts
- [Get age of feedback in seconds.](file:///home/scheianu/digiserver-v2/app/models/player_feedback.py#L48) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/player_feedback.py](file:///home/scheianu/digiserver-v2/app/models/player_feedback.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 22
> 1 nodes · cohesion 1.00
## Key Concepts
- [Get most recent feedback for a player. Args: player](file:///home/scheianu/digiserver-v2/app/models/player_feedback.py#L54) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/player_feedback.py](file:///home/scheianu/digiserver-v2/app/models/player_feedback.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 23
> 1 nodes · cohesion 1.00
## Key Concepts
- [Get the current HTTPS configuration. Returns: HTTPS](file:///home/scheianu/digiserver-v2/app/models/https_config.py#L44) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/https_config.py](file:///home/scheianu/digiserver-v2/app/models/https_config.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 24
> 1 nodes · cohesion 1.00
## Key Concepts
- [Create or update HTTPS configuration. Args: https_e](file:///home/scheianu/digiserver-v2/app/models/https_config.py#L56) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/https_config.py](file:///home/scheianu/digiserver-v2/app/models/https_config.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 25
> 1 nodes · cohesion 1.00
## Key Concepts
- [Check if user has admin role.](file:///home/scheianu/digiserver-v2/app/models/user.py#L38) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/user.py](file:///home/scheianu/digiserver-v2/app/models/user.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 26
> 1 nodes · cohesion 1.00
## Key Concepts
- [Check if player is online (seen in last 5 minutes).](file:///home/scheianu/digiserver-v2/app/models/player.py#L61) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/player.py](file:///home/scheianu/digiserver-v2/app/models/player.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 27
> 1 nodes · cohesion 1.00
## Key Concepts
- [Authenticate a player by hostname and password or quickconnect code.](file:///home/scheianu/digiserver-v2/app/models/player.py#L122) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/player.py](file:///home/scheianu/digiserver-v2/app/models/player.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 28
> 1 nodes · cohesion 1.00
## Key Concepts
- [Create an info level log entry. Args: message: Log](file:///home/scheianu/digiserver-v2/app/models/server_log.py#L31) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/server_log.py](file:///home/scheianu/digiserver-v2/app/models/server_log.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*
+25
View File
@@ -0,0 +1,25 @@
# Community 29
> 1 nodes · cohesion 1.00
## Key Concepts
- [Create a warning level log entry. Args: message: Lo](file:///home/scheianu/digiserver-v2/app/models/server_log.py#L46) (0 connections)
## Relationships
- No strong cross-community connections detected
## Source Files
- [/home/scheianu/digiserver-v2/app/models/server_log.py](file:///home/scheianu/digiserver-v2/app/models/server_log.py)
## Audit Trail
- EXTRACTED: 0 (0%)
- INFERRED: 0 (0%)
- AMBIGUOUS: 0 (0%)
---
*Part of the graphify knowledge wiki. See [[index]] to navigate.*

Some files were not shown because too many files have changed in this diff Show More