updates to digiserver app server
This commit is contained in:
@@ -837,6 +837,24 @@ def receive_edited_media():
|
||||
with open(metadata_path, 'w') as f:
|
||||
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 ────────────────────
|
||||
# This tells the player to download the latest edited version.
|
||||
old_filename = content.filename
|
||||
|
||||
@@ -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_content = Content(
|
||||
filename=page_filename,
|
||||
original_filename=page_filename,
|
||||
content_type='image',
|
||||
duration=duration,
|
||||
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_content = Content(
|
||||
filename=slide_filename,
|
||||
original_filename=slide_filename,
|
||||
content_type='image',
|
||||
duration=duration,
|
||||
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):
|
||||
content = Content(
|
||||
filename=filename,
|
||||
original_filename=filename,
|
||||
content_type=detected_type,
|
||||
duration=duration,
|
||||
file_size=os.path.getsize(filepath)
|
||||
@@ -1313,6 +1316,7 @@ def upload_media():
|
||||
# Create content record for page
|
||||
page_content = Content(
|
||||
filename=page_filename,
|
||||
original_filename=page_filename,
|
||||
content_type='image',
|
||||
duration=duration,
|
||||
file_size=os.path.getsize(page_file)
|
||||
@@ -1373,6 +1377,7 @@ def upload_media():
|
||||
# Create content record for slide
|
||||
slide_content = Content(
|
||||
filename=slide_filename,
|
||||
original_filename=slide_filename,
|
||||
content_type='image',
|
||||
duration=duration,
|
||||
file_size=os.path.getsize(slide_file)
|
||||
@@ -1419,6 +1424,7 @@ def upload_media():
|
||||
if os.path.exists(filepath):
|
||||
content = Content(
|
||||
filename=filename,
|
||||
original_filename=filename,
|
||||
content_type=detected_type,
|
||||
duration=duration,
|
||||
file_size=os.path.getsize(filepath)
|
||||
|
||||
@@ -21,6 +21,10 @@ class Content(db.Model):
|
||||
|
||||
id = db.Column(db.Integer, primary_key=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)
|
||||
duration = db.Column(db.Integer, default=10, nullable=True)
|
||||
file_size = db.Column(db.BigInteger, nullable=True)
|
||||
@@ -53,6 +57,28 @@ class Content(db.Model):
|
||||
"""Get number of groups containing this content."""
|
||||
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:
|
||||
"""Check if content is an image."""
|
||||
return self.content_type == 'image'
|
||||
@@ -68,3 +94,25 @@ class Content(db.Model):
|
||||
def is_weblink(self) -> bool:
|
||||
"""Check if content is a web link (URL) rather than an uploaded file."""
|
||||
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
|
||||
|
||||
@@ -326,11 +326,11 @@
|
||||
<div class="media-library" style="max-height: 350px; overflow-y: auto;">
|
||||
{% if 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' %}
|
||||
<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) }}"
|
||||
alt="{{ media.filename }}"
|
||||
<img src="{{ url_for('static', filename='uploads/' + media.current_media_path) }}"
|
||||
alt="{{ media.original_display_name }}"
|
||||
style="max-width: 100%; max-height: 100%; object-fit: cover;"
|
||||
onerror="this.style.display='none'; this.parentElement.innerHTML='<span style=\'font-size: 48px;\'>📷</span>'">
|
||||
</div>
|
||||
@@ -347,7 +347,7 @@
|
||||
📁
|
||||
</div>
|
||||
{% 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>
|
||||
{% endfor %}
|
||||
|
||||
@@ -318,7 +318,7 @@
|
||||
{% if content.content_type == 'weblink' %}
|
||||
<a href="{{ content.url }}" target="_blank" rel="noopener noreferrer">{{ content.url }}</a>
|
||||
{% else %}
|
||||
{{ content.filename }}
|
||||
{{ content.original_display_name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
@@ -434,7 +434,7 @@
|
||||
{% elif content.content_type == 'video' %}🎥
|
||||
{% elif content.content_type == 'pdf' %}📄
|
||||
{% else %}📁{% endif %}
|
||||
{{ content.filename }}
|
||||
{{ content.original_display_name }}
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #999;">
|
||||
{{ content.file_size_mb }} MB
|
||||
|
||||
@@ -275,11 +275,11 @@
|
||||
<div class="media-card">
|
||||
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }}, {{ media.edit_count }})" title="Delete">🗑️</button>
|
||||
<div class="media-thumbnail">
|
||||
<img src="{{ url_for('static', filename='uploads/' + media.filename) }}"
|
||||
alt="{{ media.filename }}"
|
||||
<img src="{{ url_for('static', filename='uploads/' + media.current_media_path) }}"
|
||||
alt="{{ media.original_display_name }}"
|
||||
onerror="this.style.display='none'; this.parentElement.innerHTML='<span class=\'media-icon\'>📷</span>'">
|
||||
</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">
|
||||
<span class="type-badge image">Image</span>
|
||||
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
||||
@@ -308,7 +308,7 @@
|
||||
<div class="media-thumbnail">
|
||||
<span class="media-icon">🎥</span>
|
||||
</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">
|
||||
<span class="type-badge video">Video</span>
|
||||
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
||||
@@ -337,7 +337,7 @@
|
||||
<div class="media-thumbnail">
|
||||
<span class="media-icon">📄</span>
|
||||
</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">
|
||||
<span class="type-badge pdf">PDF</span>
|
||||
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
||||
@@ -366,7 +366,7 @@
|
||||
<div class="media-thumbnail">
|
||||
<span class="media-icon">📊</span>
|
||||
</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">
|
||||
<span class="type-badge pptx">PPTX</span>
|
||||
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
||||
@@ -395,7 +395,7 @@
|
||||
<div class="media-thumbnail">
|
||||
<span class="media-icon">📁</span>
|
||||
</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">
|
||||
<span class="type-badge">{{ media.content_type }}</span>
|
||||
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
<div class="card-header">
|
||||
<div class="card-header-title">
|
||||
<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;">
|
||||
({{ data.versions|length + 1 }} version{{ 's' if (data.versions|length + 1) > 1 else '' }})
|
||||
</span>
|
||||
@@ -420,10 +420,10 @@
|
||||
{% if original_content %}
|
||||
<div class="version-item"
|
||||
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">
|
||||
{% if original_content.filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')) %}
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ original_content.filename) }}"
|
||||
{% if original_content.original_display_name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')) %}
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ original_content.original_media_path) }}"
|
||||
alt="Original">
|
||||
{% else %}
|
||||
<div style="color: white; font-size: 2rem;">📄</div>
|
||||
|
||||
@@ -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
|
||||
echo -e " • Migrating player_user global settings..."
|
||||
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 ""
|
||||
|
||||
@@ -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 13–40 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)
|
||||
@@ -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` (0–1) 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 13–40** — 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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.
|
||||
@@ -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**:
|
||||
@@ -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 | |
|
||||
@@ -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.
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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"}]}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -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": []}
|
||||
+1
@@ -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": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -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"}]}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -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": []}
|
||||
+1
@@ -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": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -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"}]}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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"}]}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -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"}]}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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"}]}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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": []}
|
||||
+1
@@ -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": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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": []}
|
||||
+1
@@ -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": []}
|
||||
+1
@@ -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": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -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": []}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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
+23241
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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.*
|
||||
@@ -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
Reference in New Issue
Block a user