From 87709bab4d2546acd181cd8a8e7627f8345d711c Mon Sep 17 00:00:00 2001 From: DigiServer Developer Date: Sat, 13 Dec 2025 21:51:45 +0200 Subject: [PATCH] updated to get card name --- Caddyfile | 35 ++++++++ Dockerfile | 9 +- app/blueprints/admin.py | 76 ++++++++++++++++ app/blueprints/api.py | 12 ++- app/blueprints/players.py | 1 + app/models/__init__.py | 2 + app/models/player_user.py | 37 ++++++++ app/templates/admin/admin.html | 3 + app/templates/admin/editing_users.html | 105 +++++++++++++++++++++++ docker-compose.yml | 62 ++++++------- migrations/add_player_user_table.py | 14 +++ migrations/migrate_player_user_global.py | 24 ++++++ 12 files changed, 347 insertions(+), 33 deletions(-) create mode 100644 app/models/player_user.py create mode 100644 app/templates/admin/editing_users.html create mode 100644 migrations/add_player_user_table.py create mode 100644 migrations/migrate_player_user_global.py diff --git a/Caddyfile b/Caddyfile index eb53472..96eba80 100644 --- a/Caddyfile +++ b/Caddyfile @@ -8,6 +8,41 @@ {$DOMAIN:localhost} { # Automatic HTTPS (Caddy handles Let's Encrypt automatically) + # Reverse proxy to Flask app + reverse_proxy digiserver:5000 { + header_up Host {host} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + + # Timeouts for large uploads + transport http { + read_timeout 300s + write_timeout 300s + } + } + + # File upload size limit (2GB) + request_body { + max_size 2GB + } + + # Security headers + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains" + X-Frame-Options "SAMEORIGIN" + X-Content-Type-Options "nosniff" + X-XSS-Protection "1; mode=block" + } + + # Logging + log { + output file /var/log/caddy/access.log + } +} + +# Handle IP address access without automatic HTTPS +http://192.168.0.206 { # Reverse proxy to Flask app reverse_proxy digiserver:5000 { # Headers diff --git a/Dockerfile b/Dockerfile index c7ced38..51309f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,15 +5,18 @@ FROM python:3.13-slim WORKDIR /app # Install system dependencies including LibreOffice for PPTX conversion -RUN apt-get update && apt-get install -y \ +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ poppler-utils \ ffmpeg \ libmagic1 \ sudo \ fonts-noto-color-emoji \ - libreoffice \ + libreoffice-core \ libreoffice-impress \ - && rm -rf /var/lib/apt/lists/* + libreoffice-writer \ + && apt-get clean && \ + rm -rf /var/lib/apt/lists/* # Copy requirements first for better caching COPY requirements.txt . diff --git a/app/blueprints/admin.py b/app/blueprints/admin.py index 6e6fbc9..c3f140a 100644 --- a/app/blueprints/admin.py +++ b/app/blueprints/admin.py @@ -772,3 +772,79 @@ def upload_login_logo(): flash(f'Error uploading logo: {str(e)}', 'danger') return redirect(url_for('admin.customize_logos')) + + +@admin_bp.route('/editing-users') +@login_required +@admin_required +def manage_editing_users(): + """Display and manage users that edit images on players.""" + try: + from app.models.player_user import PlayerUser + from app.models.player_edit import PlayerEdit + + # Get all editing users + users = PlayerUser.query.order_by(PlayerUser.created_at.desc()).all() + + # Get edit counts for each user + user_stats = {} + for user in users: + edit_count = PlayerEdit.query.filter_by(user=user.user_code).count() + user_stats[user.user_code] = edit_count + + return render_template('admin/editing_users.html', + users=users, + user_stats=user_stats) + except Exception as e: + log_action('error', f'Error loading editing users: {str(e)}') + flash('Error loading editing users.', 'danger') + return redirect(url_for('admin.admin_panel')) + + +@admin_bp.route('/editing-users//update', methods=['POST']) +@login_required +@admin_required +def update_editing_user(user_id: int): + """Update editing user name.""" + try: + from app.models.player_user import PlayerUser + + user = PlayerUser.query.get_or_404(user_id) + user_name = request.form.get('user_name', '').strip() + + user.user_name = user_name if user_name else None + user.updated_at = datetime.utcnow() + db.session.commit() + + log_action('info', f'Updated editing user {user.user_code} name to: {user_name or "None"}') + flash('User name updated successfully!', 'success') + except Exception as e: + db.session.rollback() + log_action('error', f'Error updating editing user: {str(e)}') + flash(f'Error updating user: {str(e)}', 'danger') + + return redirect(url_for('admin.manage_editing_users')) + + +@admin_bp.route('/editing-users//delete', methods=['POST']) +@login_required +@admin_required +def delete_editing_user(user_id: int): + """Delete editing user.""" + try: + from app.models.player_user import PlayerUser + + user = PlayerUser.query.get_or_404(user_id) + user_code = user.user_code + + db.session.delete(user) + db.session.commit() + + log_action('info', f'Deleted editing user: {user_code}') + flash('User deleted successfully!', 'success') + except Exception as e: + db.session.rollback() + log_action('error', f'Error deleting editing user: {str(e)}') + flash(f'Error deleting user: {str(e)}', 'danger') + + return redirect(url_for('admin.manage_editing_users')) diff --git a/app/blueprints/api.py b/app/blueprints/api.py index 365f40f..e19f213 100644 --- a/app/blueprints/api.py +++ b/app/blueprints/api.py @@ -794,13 +794,23 @@ def receive_edited_media(): except: time_of_mod = datetime.utcnow() + # Auto-create PlayerUser record if user code is provided + user_code = metadata.get('user') + if user_code: + from app.models.player_user import PlayerUser + existing_user = PlayerUser.query.filter_by(user_code=user_code).first() + if not existing_user: + new_user = PlayerUser(user_code=user_code) + db.session.add(new_user) + log_action('info', f'Auto-created PlayerUser record for code: {user_code}') + edit_record = PlayerEdit( player_id=player.id, content_id=content.id, original_name=original_name, new_name=new_filename, version=version, - user=metadata.get('user'), + user=user_code, time_of_modification=time_of_mod, metadata_path=metadata_path, edited_file_path=edited_file_path diff --git a/app/blueprints/players.py b/app/blueprints/players.py index 5e1d787..35703bb 100644 --- a/app/blueprints/players.py +++ b/app/blueprints/players.py @@ -343,6 +343,7 @@ def edited_media(player_id: int): # Get all edited media history from player from app.models.player_edit import PlayerEdit + edited_media = PlayerEdit.query.filter_by(player_id=player_id)\ .order_by(PlayerEdit.created_at.desc())\ .all() diff --git a/app/models/__init__.py b/app/models/__init__.py index 70c84ec..98888f5 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -7,6 +7,7 @@ from app.models.content import Content from app.models.server_log import ServerLog from app.models.player_feedback import PlayerFeedback from app.models.player_edit import PlayerEdit +from app.models.player_user import PlayerUser __all__ = [ 'User', @@ -17,6 +18,7 @@ __all__ = [ 'ServerLog', 'PlayerFeedback', 'PlayerEdit', + 'PlayerUser', 'group_content', 'playlist_content', ] diff --git a/app/models/player_user.py b/app/models/player_user.py new file mode 100644 index 0000000..f55c4f9 --- /dev/null +++ b/app/models/player_user.py @@ -0,0 +1,37 @@ +"""Player user model for managing user codes and names.""" +from datetime import datetime + +from app.extensions import db + + +class PlayerUser(db.Model): + """Player user model for managing user codes and names globally. + + Attributes: + id: Primary key + user_code: User code received from player (unique) + user_name: Display name for the user + created_at: Record creation timestamp + updated_at: Record update timestamp + """ + __tablename__ = 'player_user' + + id = db.Column(db.Integer, primary_key=True) + user_code = db.Column(db.String(255), nullable=False, unique=True, index=True) + user_name = db.Column(db.String(255), nullable=True) + created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) + + def __repr__(self) -> str: + """String representation of PlayerUser.""" + return f' {self.user_name or "Unnamed"}>' + + def to_dict(self) -> dict: + """Convert to dictionary for API responses.""" + return { + 'id': self.id, + 'user_code': self.user_code, + 'user_name': self.user_name, + 'created_at': self.created_at.isoformat() if self.created_at else None, + 'updated_at': self.updated_at.isoformat() if self.updated_at else None, + } diff --git a/app/templates/admin/admin.html b/app/templates/admin/admin.html index d23409a..edd6aab 100644 --- a/app/templates/admin/admin.html +++ b/app/templates/admin/admin.html @@ -56,6 +56,9 @@ Manage Users + + Manage Users That Edited Images + diff --git a/app/templates/admin/editing_users.html b/app/templates/admin/editing_users.html new file mode 100644 index 0000000..0c14ec3 --- /dev/null +++ b/app/templates/admin/editing_users.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} + +{% block title %}Manage Editing Users{% endblock %} + +{% block content %} +
+
+
+ + ← Back to Admin + +

👤 Manage Editing Users

+
+
+

Manage users who edit images on players. User codes are automatically created from player metadata.

+
+ +{% if users %} +
+
+

Editing Users ({{ users|length }})

+
+
+ + + + + + + + + + + + {% for user in users %} + + + + + + + + {% endfor %} + +
User CodeDisplay NameEdits CountCreatedActions
{{ user.user_code }} +
+ + +
+
+ {{ user_stats.get(user.user_code, 0) }} edits + {{ user.created_at | localtime('%Y-%m-%d %H:%M') }} +
+ +
+
+
+
+{% else %} +
+
👤
+

No Editing Users Yet

+

+ User codes will appear here automatically when players edit media files. +

+
+{% endif %} + + + +{% endblock %} diff --git a/docker-compose.yml b/docker-compose.yml index 7480e56..ba8ba8c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,8 @@ services: digiserver: build: . container_name: digiserver-v2 - expose: - - "5000" + ports: + - "8080:5000" # Expose for nginx reverse proxy volumes: - ./instance:/app/instance - ./app/static/uploads:/app/app/static/uploads @@ -21,33 +21,37 @@ services: timeout: 10s retries: 3 start_period: 40s - networks: - - digiserver-network + # Commented out network when using external nginx proxy + # networks: + # - digiserver-network - caddy: - image: caddy:2-alpine - container_name: digiserver-caddy - ports: - - "80:80" - - "443:443" - - "443:443/udp" # HTTP/3 - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile:ro - - caddy-data:/data - - caddy-config:/config - environment: - - DOMAIN=${DOMAIN:-localhost} - - EMAIL=${EMAIL:-admin@localhost} - depends_on: - - digiserver - restart: unless-stopped - networks: - - digiserver-network + # Caddy reverse proxy (commented out when using external nginx) + # Uncomment the section below if you want standalone deployment with automatic HTTPS + # caddy: + # image: caddy:2-alpine + # container_name: digiserver-caddy + # ports: + # - "80:80" + # - "443:443" + # - "443:443/udp" # HTTP/3 + # volumes: + # - ./Caddyfile:/etc/caddy/Caddyfile:ro + # - caddy-data:/data + # - caddy-config:/config + # environment: + # - DOMAIN=${DOMAIN:-localhost} + # - EMAIL=${EMAIL:-admin@localhost} + # depends_on: + # - digiserver + # restart: unless-stopped + # networks: + # - digiserver-network -networks: - digiserver-network: - driver: bridge +# Commented out when using external nginx proxy +# networks: +# digiserver-network: +# driver: bridge -volumes: - caddy-data: - caddy-config: +# volumes: +# caddy-data: +# caddy-config: diff --git a/migrations/add_player_user_table.py b/migrations/add_player_user_table.py new file mode 100644 index 0000000..e8c7202 --- /dev/null +++ b/migrations/add_player_user_table.py @@ -0,0 +1,14 @@ +"""Add player_user table for user code mappings.""" +import sys +sys.path.insert(0, '/app') + +from app.app import create_app +from app.extensions import db +from app.models.player_user import PlayerUser + +app = create_app() + +with app.app_context(): + print("Creating player_user table...") + db.create_all() + print("✓ player_user table created successfully!") diff --git a/migrations/migrate_player_user_global.py b/migrations/migrate_player_user_global.py new file mode 100644 index 0000000..3a1bf23 --- /dev/null +++ b/migrations/migrate_player_user_global.py @@ -0,0 +1,24 @@ +"""Migrate player_user table to remove player_id and make user_code unique globally.""" +import sys +sys.path.insert(0, '/app') + +from app.app import create_app +from app.extensions import db +from sqlalchemy import text + +app = create_app('production') + +with app.app_context(): + print("Migrating player_user table...") + + # Drop existing table and recreate with new schema + db.session.execute(text('DROP TABLE IF EXISTS player_user')) + db.session.commit() + + # Create new table + db.create_all() + + print("✓ player_user table migrated successfully!") + print(" - Removed player_id foreign key") + print(" - Made user_code unique globally") + print(" - user_name is now nullable")