- Fix: Add missing 'click' import to app.py to resolve startup error - Fix: Update docker-compose.yml volume mappings to use correct persistent storage paths (/opt/digi-s) - Improve: Enhanced entrypoint.sh for better database initialization - Update: Configuration files for improved deployment This resolves the Docker container startup issues and ensures proper persistent storage.
645 lines
24 KiB
Python
Executable File
645 lines
24 KiB
Python
Executable File
import os
|
|
import click
|
|
from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify, send_from_directory
|
|
from flask_migrate import Migrate
|
|
import subprocess
|
|
from werkzeug.utils import secure_filename
|
|
from functools import wraps
|
|
from extensions import db, bcrypt, login_manager
|
|
from sqlalchemy import text
|
|
|
|
# First import models
|
|
from models import User, Player, Content, Group, ServerLog
|
|
|
|
# Then import utilities that use the models
|
|
from flask_login import login_user, logout_user, login_required, current_user
|
|
from utils.logger import get_recent_logs, log_action, log_upload, log_process, log_user_deleted, log_user_created
|
|
from utils.group_player_management import (
|
|
create_group as create_group_util,
|
|
edit_group as edit_group_util,
|
|
delete_group as delete_group_util,
|
|
add_player as add_player_util,
|
|
edit_player as edit_player_util,
|
|
delete_player as delete_player_util,
|
|
get_group_content,
|
|
get_player_content,
|
|
update_player_content_order,
|
|
update_group_content_order,
|
|
edit_group_media,
|
|
delete_group_media
|
|
)
|
|
|
|
# Finally, import modules that depend on both models and logger
|
|
from utils.uploads import (
|
|
add_image_to_playlist,
|
|
convert_video_and_update_playlist,
|
|
process_pdf,
|
|
process_pptx,
|
|
process_uploaded_files
|
|
)
|
|
|
|
# Define global variables for server version and build date
|
|
SERVER_VERSION = "1.1.0"
|
|
BUILD_DATE = "2025-06-29"
|
|
|
|
app = Flask(__name__, instance_relative_config=True)
|
|
|
|
# Set the secret key from environment variable or use a default value
|
|
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'Ana_Are_Multe_Mere-Si_Nu_Are_Pere')
|
|
|
|
# Configure the database location to be in the instance folder
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.instance_path, 'dashboard.db')
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
|
|
# Set maximum content length to 1GB
|
|
app.config['MAX_CONTENT_LENGTH'] = 2048 * 2048 * 2048 # 2GB, adjust as needed
|
|
|
|
# Ensure the instance folder exists
|
|
os.makedirs(app.instance_path, exist_ok=True)
|
|
|
|
db.init_app(app)
|
|
bcrypt.init_app(app)
|
|
login_manager.init_app(app)
|
|
|
|
UPLOAD_FOLDER = 'static/uploads'
|
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
UPLOAD_FOLDERLOGO = 'static/resurse'
|
|
app.config['UPLOAD_FOLDERLOGO'] = UPLOAD_FOLDERLOGO
|
|
|
|
# Ensure the upload folder exists
|
|
if not os.path.exists(UPLOAD_FOLDER):
|
|
os.makedirs(UPLOAD_FOLDER)
|
|
os.makedirs(UPLOAD_FOLDERLOGO)
|
|
|
|
login_manager.login_view = 'login'
|
|
|
|
migrate = Migrate(app, db)
|
|
|
|
@login_manager.user_loader
|
|
def load_user(user_id):
|
|
return db.session.get(User, int(user_id))
|
|
|
|
def admin_required(f):
|
|
@wraps(f)
|
|
def decorated_function(*args, **kwargs):
|
|
if current_user.role != 'admin':
|
|
return redirect(url_for('dashboard'))
|
|
return f(*args, **kwargs)
|
|
return decorated_function
|
|
|
|
@app.route('/')
|
|
@login_required
|
|
def dashboard():
|
|
players = Player.query.all()
|
|
groups = Group.query.all()
|
|
logo_exists = os.path.exists(os.path.join(app.config['UPLOAD_FOLDERLOGO'], 'logo.png'))
|
|
server_logs = get_recent_logs(20) # Get the 20 most recent logs
|
|
return render_template('dashboard.html', players=players, groups=groups, logo_exists=logo_exists, server_logs=server_logs)
|
|
|
|
@app.route('/register', methods=['GET', 'POST'])
|
|
def register():
|
|
if request.method == 'POST':
|
|
username = request.form['username']
|
|
password = request.form['password']
|
|
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
|
|
new_user = User(username=username, password=hashed_password, role='user')
|
|
db.session.add(new_user)
|
|
db.session.commit()
|
|
return redirect(url_for('login'))
|
|
return render_template('register.html')
|
|
|
|
@app.route('/login', methods=['GET', 'POST'])
|
|
def login():
|
|
if request.method == 'POST':
|
|
username = request.form['username']
|
|
password = request.form['password']
|
|
user = User.query.filter_by(username=username).first()
|
|
if user and bcrypt.check_password_hash(user.password, password):
|
|
login_user(user)
|
|
return redirect(url_for('dashboard'))
|
|
else:
|
|
flash('Login Unsuccessful. Please check username and password', 'danger')
|
|
|
|
login_picture_exists = os.path.exists(os.path.join(app.config['UPLOAD_FOLDERLOGO'], 'login_picture.png'))
|
|
return render_template('login.html', login_picture_exists=login_picture_exists)
|
|
|
|
@app.route('/logout')
|
|
@login_required
|
|
def logout():
|
|
logout_user()
|
|
return redirect(url_for('login'))
|
|
|
|
@app.route('/upload_content', methods=['GET', 'POST'])
|
|
@login_required
|
|
@admin_required
|
|
def upload_content():
|
|
if request.method == 'POST':
|
|
target_type = request.form.get('target_type')
|
|
target_id = request.form.get('target_id')
|
|
files = request.files.getlist('files')
|
|
duration = int(request.form['duration'])
|
|
return_url = request.form.get('return_url')
|
|
media_type = request.form['media_type']
|
|
|
|
print(f"Target Type: {target_type}, Target ID: {target_id}, Media Type: {media_type}")
|
|
|
|
if not target_type or not target_id:
|
|
flash('Please select a target type and target ID.', 'danger')
|
|
return redirect(url_for('upload_content'))
|
|
|
|
# Process uploaded files and get results
|
|
results = process_uploaded_files(app, files, media_type, duration, target_type, target_id)
|
|
|
|
return redirect(return_url)
|
|
|
|
# Handle GET request
|
|
target_type = request.args.get('target_type')
|
|
target_id = request.args.get('target_id')
|
|
return_url = request.args.get('return_url', url_for('dashboard'))
|
|
|
|
players = [{'id': player.id, 'username': player.username} for player in Player.query.all()]
|
|
groups = [{'id': group.id, 'name': group.name} for group in Group.query.all()]
|
|
|
|
return render_template('upload_content.html', target_type=target_type, target_id=target_id,
|
|
players=players, groups=groups, return_url=return_url)
|
|
|
|
@app.route('/admin')
|
|
@login_required
|
|
@admin_required
|
|
def admin():
|
|
logo_exists = os.path.exists(os.path.join(app.config['UPLOAD_FOLDERLOGO'], 'logo.png'))
|
|
login_picture_exists = os.path.exists(os.path.join(app.config['UPLOAD_FOLDERLOGO'], 'login_picture.png'))
|
|
users = User.query.all()
|
|
return render_template(
|
|
'admin.html',
|
|
users=users,
|
|
logo_exists=logo_exists,
|
|
login_picture_exists=login_picture_exists,
|
|
server_version=SERVER_VERSION,
|
|
build_date=BUILD_DATE
|
|
)
|
|
|
|
@app.route('/admin/change_role/<int:user_id>', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def change_role(user_id):
|
|
user = User.query.get_or_404(user_id)
|
|
new_role = request.form['role']
|
|
user.role = new_role
|
|
db.session.commit()
|
|
return redirect(url_for('admin'))
|
|
|
|
@app.route('/admin/delete_user/<int:user_id>', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def delete_user(user_id):
|
|
user = User.query.get_or_404(user_id)
|
|
username = user.username # Store username before deletion for logging
|
|
db.session.delete(user)
|
|
db.session.commit()
|
|
# Add log entry for user deletion
|
|
log_user_deleted(username)
|
|
return redirect(url_for('admin'))
|
|
|
|
@app.route('/admin/create_user', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def create_user():
|
|
username = request.form['username']
|
|
password = request.form['password']
|
|
role = request.form['role']
|
|
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
|
|
new_user = User(username=username, password=hashed_password, role=role)
|
|
db.session.add(new_user)
|
|
db.session.commit()
|
|
# Add log entry for user creation
|
|
log_user_created(username, role)
|
|
return redirect(url_for('admin'))
|
|
|
|
@app.route('/player/<int:player_id>')
|
|
@login_required
|
|
def player_page(player_id):
|
|
player = db.session.get(Player, player_id)
|
|
content = get_player_content(player_id)
|
|
return render_template('player_page.html', player=player, content=content)
|
|
|
|
@app.route('/player/<int:player_id>/upload', methods=['POST'])
|
|
@login_required
|
|
def upload_content_to_player(player_id):
|
|
player = Player.query.get_or_404(player_id)
|
|
files = request.files.getlist('files')
|
|
duration = int(request.form['duration'])
|
|
|
|
for file in files:
|
|
filename = secure_filename(file.filename)
|
|
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
file.save(file_path)
|
|
new_content = Content(file_name=filename, duration=duration, player_id=player_id)
|
|
db.session.add(new_content)
|
|
|
|
db.session.commit()
|
|
return redirect(url_for('player_page', player_id=player_id))
|
|
|
|
@app.route('/content/<int:content_id>/edit', methods=['POST'])
|
|
@login_required
|
|
def edit_content(content_id):
|
|
content = Content.query.get_or_404(content_id)
|
|
new_duration = int(request.form['duration'])
|
|
content.duration = new_duration
|
|
db.session.commit()
|
|
return redirect(url_for('player_page', player_id=content.player_id))
|
|
|
|
@app.route('/content/<int:content_id>/delete', methods=['POST'])
|
|
@login_required
|
|
def delete_content(content_id):
|
|
content = Content.query.get_or_404(content_id)
|
|
player_id = content.player_id
|
|
db.session.delete(content)
|
|
db.session.commit()
|
|
return redirect(url_for('player_page', player_id=player_id))
|
|
|
|
@app.route('/player/<int:player_id>/fullscreen', methods=['GET', 'POST'])
|
|
def player_fullscreen(player_id):
|
|
player = Player.query.get_or_404(player_id)
|
|
|
|
if request.method == 'POST':
|
|
hostname = request.form['hostname']
|
|
password = request.form['password']
|
|
quickconnect_password = request.form.get('quickconnect_password')
|
|
|
|
if quickconnect_password:
|
|
if player.hostname == hostname and bcrypt.check_password_hash(player.quickconnect_password, quickconnect_password):
|
|
authenticated = True
|
|
else:
|
|
authenticated = False
|
|
else:
|
|
if player.hostname == hostname and bcrypt.check_password_hash(player.password, password):
|
|
authenticated = True
|
|
else:
|
|
authenticated = False
|
|
else:
|
|
authenticated = False
|
|
|
|
if authenticated or current_user.is_authenticated:
|
|
content = Content.query.filter_by(player_id=player_id).all()
|
|
return render_template('player_fullscreen.html', player=player, content=content)
|
|
else:
|
|
return render_template('player_auth.html', player_id=player_id)
|
|
|
|
@app.route('/player/<int:player_id>/delete', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def delete_player(player_id):
|
|
delete_player_util(player_id)
|
|
return redirect(url_for('dashboard'))
|
|
|
|
# Update the add_player function
|
|
@app.route('/player/add', methods=['GET', 'POST'])
|
|
@login_required
|
|
@admin_required
|
|
def add_player():
|
|
if request.method == 'POST':
|
|
username = request.form['username']
|
|
hostname = request.form['hostname']
|
|
password = bcrypt.generate_password_hash(request.form['password']).decode('utf-8')
|
|
quickconnect_password = bcrypt.generate_password_hash(request.form['quickconnect_password']).decode('utf-8')
|
|
add_player_util(username, hostname, password, quickconnect_password)
|
|
flash(f'Player "{username}" added successfully.', 'success')
|
|
return redirect(url_for('dashboard'))
|
|
return render_template('add_player.html')
|
|
|
|
@app.route('/player/<int:player_id>/edit', methods=['GET', 'POST'])
|
|
@login_required
|
|
@admin_required
|
|
def edit_player(player_id):
|
|
player = Player.query.get_or_404(player_id)
|
|
if request.method == 'POST':
|
|
username = request.form['username']
|
|
hostname = request.form['hostname']
|
|
password = request.form['password'] if request.form['password'] else None
|
|
quickconnect_password = request.form['quickconnect_password'] if request.form['quickconnect_password'] else None
|
|
edit_player_util(player_id, username, hostname, password, quickconnect_password)
|
|
flash(f'Player "{username}" updated successfully.', 'success')
|
|
return redirect(url_for('player_page', player_id=player.id))
|
|
|
|
return_url = request.args.get('return_url', url_for('player_page', player_id=player.id))
|
|
return render_template('edit_player.html', player=player, return_url=return_url)
|
|
|
|
@app.route('/change_theme', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def change_theme():
|
|
theme = request.form['theme']
|
|
current_user.theme = theme
|
|
db.session.commit()
|
|
return redirect(url_for('admin'))
|
|
|
|
@app.route('/upload_logo', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def upload_logo():
|
|
if 'logo' not in request.files:
|
|
return redirect(url_for('admin'))
|
|
|
|
file = request.files['logo']
|
|
if file.filename == '':
|
|
return redirect(url_for('admin'))
|
|
|
|
if file:
|
|
filename = secure_filename(file.filename)
|
|
file_path = os.path.join(app.config['UPLOAD_FOLDERLOGO'], 'logo.png')
|
|
file.save(file_path)
|
|
return redirect(url_for('admin'))
|
|
|
|
@app.route('/upload_personalization_pictures', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def upload_personalization_pictures():
|
|
logo_file = request.files.get('logo')
|
|
login_picture_file = request.files.get('login_picture')
|
|
|
|
if logo_file and logo_file.filename != '':
|
|
logo_filename = secure_filename(logo_file.filename)
|
|
logo_file_path = os.path.join(app.config['UPLOAD_FOLDERLOGO'], 'logo.png')
|
|
logo_file.save(logo_file_path)
|
|
|
|
if login_picture_file and login_picture_file.filename != '':
|
|
login_picture_filename = secure_filename(login_picture_file.filename)
|
|
login_picture_file_path = os.path.join(app.config['UPLOAD_FOLDERLOGO'], 'login_picture.png')
|
|
login_picture_file.save(login_picture_file_path)
|
|
|
|
return redirect(url_for('admin'))
|
|
|
|
@app.route('/clean_unused_files', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def clean_unused_files():
|
|
# Get all file names from the database
|
|
content_files = {content.file_name for content in Content.query.all()}
|
|
logo_file = 'resurse/logo.png'
|
|
login_picture_file = 'resurse/login_picture.png'
|
|
|
|
# Debugging: Print the content files from the database
|
|
print("Content files from database:", content_files)
|
|
|
|
# Get all files in the upload folder
|
|
all_files = set(os.listdir(app.config['UPLOAD_FOLDER']))
|
|
|
|
# Determine unused files
|
|
used_files = content_files | {logo_file, login_picture_file}
|
|
unused_files = all_files - used_files
|
|
|
|
# Debugging: Print the lists of files
|
|
print("All files:", all_files)
|
|
print("Used files:", used_files)
|
|
print("Unused files:", unused_files)
|
|
|
|
# Delete unused files
|
|
for file_name in unused_files:
|
|
file_path = os.path.join(app.config['UPLOAD_FOLDER'], file_name)
|
|
if os.path.isfile(file_path):
|
|
print(f"Deleting file: {file_path}") # Debugging: Print the file being deleted
|
|
os.remove(file_path)
|
|
|
|
flash('Unused files have been cleaned.', 'success')
|
|
return redirect(url_for('admin'))
|
|
|
|
|
|
@app.route('/api/playlists', methods=['GET'])
|
|
def get_playlists():
|
|
hostname = request.args.get('hostname')
|
|
quickconnect_code = request.args.get('quickconnect_code')
|
|
|
|
# Validate the parameters
|
|
if not hostname or not quickconnect_code:
|
|
return jsonify({'error': 'Hostname and quick connect code are required'}), 400
|
|
|
|
# Find the player by hostname and verify the quickconnect code
|
|
player = Player.query.filter_by(hostname=hostname).first()
|
|
if not player or not bcrypt.check_password_hash(player.quickconnect_password, quickconnect_code):
|
|
return jsonify({'error': 'Invalid hostname or quick connect code'}), 404
|
|
|
|
# Check if player is locked to a group
|
|
if player.locked_to_group_id:
|
|
# Get content for all players in the group to ensure shared content
|
|
group_players = player.locked_to_group.players
|
|
player_ids = [p.id for p in group_players]
|
|
|
|
# Use the first occurrence of each file for the playlist
|
|
content_query = (
|
|
db.session.query(
|
|
Content.file_name,
|
|
db.func.min(Content.id).label('id'),
|
|
db.func.min(Content.duration).label('duration')
|
|
)
|
|
.filter(Content.player_id.in_(player_ids))
|
|
.group_by(Content.file_name)
|
|
)
|
|
|
|
content = db.session.query(Content).filter(
|
|
Content.id.in_([c.id for c in content_query])
|
|
).all()
|
|
else:
|
|
# Get player's individual content
|
|
content = Content.query.filter_by(player_id=player.id).all()
|
|
|
|
playlist = [
|
|
{
|
|
'file_name': media.file_name,
|
|
'url': f"http://{request.host}/media/{media.file_name}",
|
|
'duration': media.duration
|
|
}
|
|
for media in content
|
|
]
|
|
|
|
# Return the playlist, version, and hashed quickconnect code
|
|
return jsonify({
|
|
'playlist': playlist,
|
|
'playlist_version': player.playlist_version,
|
|
'hashed_quickconnect': player.quickconnect_password
|
|
})
|
|
|
|
@app.route('/media/<path:filename>')
|
|
def media(filename):
|
|
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
|
|
|
@app.context_processor
|
|
def inject_theme():
|
|
if current_user.is_authenticated:
|
|
theme = current_user.theme
|
|
else:
|
|
theme = 'light'
|
|
return dict(theme=theme)
|
|
|
|
@app.route('/group/create', methods=['GET', 'POST'])
|
|
@login_required
|
|
@admin_required
|
|
def create_group():
|
|
if request.method == 'POST':
|
|
group_name = request.form['name']
|
|
player_ids = request.form.getlist('players')
|
|
create_group_util(group_name, player_ids)
|
|
flash(f'Group "{group_name}" created successfully.', 'success')
|
|
return redirect(url_for('dashboard'))
|
|
players = Player.query.all()
|
|
return render_template('create_group.html', players=players)
|
|
|
|
@app.route('/group/<int:group_id>/manage')
|
|
@login_required
|
|
@admin_required
|
|
def manage_group(group_id):
|
|
group = Group.query.get_or_404(group_id)
|
|
content = get_group_content(group_id)
|
|
# Debug content ordering
|
|
print("Group content positions before sorting:", [(c.id, c.file_name, c.position) for c in content])
|
|
content = sorted(content, key=lambda c: c.position)
|
|
print("Group content positions after sorting:", [(c.id, c.file_name, c.position) for c in content])
|
|
return render_template('manage_group.html', group=group, content=content)
|
|
|
|
@app.route('/group/<int:group_id>/edit', methods=['GET', 'POST'])
|
|
@login_required
|
|
@admin_required
|
|
def edit_group(group_id):
|
|
group = Group.query.get_or_404(group_id)
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
player_ids = request.form.getlist('players')
|
|
edit_group_util(group_id, name, player_ids)
|
|
flash(f'Group "{name}" updated successfully.', 'success')
|
|
return redirect(url_for('dashboard'))
|
|
players = Player.query.all()
|
|
return render_template('edit_group.html', group=group, players=players)
|
|
|
|
@app.route('/group/<int:group_id>/delete', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def delete_group(group_id):
|
|
group = Group.query.get_or_404(group_id)
|
|
group_name = group.name
|
|
delete_group_util(group_id)
|
|
flash(f'Group "{group_name}" deleted successfully.', 'success')
|
|
return redirect(url_for('dashboard'))
|
|
|
|
@app.route('/group/<int:group_id>/fullscreen', methods=['GET'])
|
|
@login_required
|
|
def group_fullscreen(group_id):
|
|
group = Group.query.get_or_404(group_id)
|
|
content = Content.query.filter(Content.player_id.in_([player.id for player in group.players])).order_by(Content.position).all()
|
|
return render_template('group_fullscreen.html', group=group, content=content)
|
|
|
|
@app.route('/group/<int:group_id>/media/<int:content_id>/edit', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def edit_group_media_route(group_id, content_id):
|
|
new_duration = int(request.form['duration'])
|
|
success = edit_group_media(group_id, content_id, new_duration)
|
|
|
|
if success:
|
|
flash('Media duration updated successfully.', 'success')
|
|
else:
|
|
flash('Error updating media duration.', 'danger')
|
|
|
|
return redirect(url_for('manage_group', group_id=group_id))
|
|
|
|
@app.route('/group/<int:group_id>/media/<int:content_id>/delete', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def delete_group_media_route(group_id, content_id):
|
|
success = delete_group_media(group_id, content_id)
|
|
|
|
if success:
|
|
flash('Media deleted successfully.', 'success')
|
|
else:
|
|
flash('Error deleting media.', 'danger')
|
|
|
|
return redirect(url_for('manage_group', group_id=group_id))
|
|
|
|
@app.route('/api/playlist_version', methods=['GET'])
|
|
def get_playlist_version():
|
|
hostname = request.args.get('hostname')
|
|
quickconnect_code = request.args.get('quickconnect_code')
|
|
|
|
# Validate the parameters
|
|
if not hostname or not quickconnect_code:
|
|
return jsonify({'error': 'Hostname and quick connect code are required'}), 400
|
|
|
|
# Find the player by hostname and verify the quickconnect code
|
|
player = Player.query.filter_by(hostname=hostname).first()
|
|
if not player or not bcrypt.check_password_hash(player.quickconnect_password, quickconnect_code):
|
|
return jsonify({'error': 'Invalid hostname or quick connect code'}), 404
|
|
|
|
# Return the playlist version and hashed quickconnect code
|
|
return jsonify({
|
|
'playlist_version': player.playlist_version,
|
|
'hashed_quickconnect': player.quickconnect_password
|
|
})
|
|
|
|
@app.route('/player/<int:player_id>/update_order', methods=['POST'])
|
|
@login_required
|
|
def update_content_order(player_id):
|
|
if not request.is_json:
|
|
return jsonify({'success': False, 'error': 'Invalid request format'}), 400
|
|
|
|
player = Player.query.get_or_404(player_id)
|
|
if player.groups and current_user.role != 'admin':
|
|
return jsonify({'success': False, 'error': 'Cannot reorder playlist for players in groups'}), 403
|
|
|
|
items = request.json.get('items', [])
|
|
|
|
success, error, new_version = update_player_content_order(player_id, items)
|
|
|
|
if success:
|
|
return jsonify({'success': True, 'new_version': new_version})
|
|
else:
|
|
return jsonify({'success': False, 'error': error}), 500
|
|
|
|
@app.route('/group/<int:group_id>/update_order', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def update_group_content_order_route(group_id):
|
|
if not request.is_json:
|
|
return jsonify({'success': False, 'error': 'Invalid request format'}), 400
|
|
|
|
items = request.json.get('items', [])
|
|
success, error = update_group_content_order(group_id, items)
|
|
|
|
if success:
|
|
return jsonify({'success': True})
|
|
else:
|
|
return jsonify({'success': False, 'error': error}), 500
|
|
|
|
@app.route('/debug/content_positions/<int:group_id>')
|
|
@login_required
|
|
@admin_required
|
|
def debug_content_positions(group_id):
|
|
group = Group.query.get_or_404(group_id)
|
|
player_ids = [p.id for p in group.players]
|
|
|
|
# Query directly with SQL to see positions
|
|
sql = text("SELECT id, file_name, position, player_id FROM content WHERE player_id IN :player_ids ORDER BY position")
|
|
result = db.session.execute(sql, {"player_ids": tuple(player_ids)})
|
|
|
|
content_data = [{"id": row.id, "file_name": row.file_name, "position": row.position, "player_id": row.player_id} for row in result]
|
|
|
|
return jsonify(content_data)
|
|
|
|
@app.cli.command("create-admin")
|
|
@click.option("--username", default="admin", help="Admin username")
|
|
@click.option("--password", help="Admin password")
|
|
def create_admin(username, password):
|
|
"""Create an admin user."""
|
|
from models import User
|
|
from extensions import bcrypt
|
|
|
|
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
|
|
user = User(username=username, password=hashed_password, role='admin')
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
print(f"Admin user '{username}' created successfully.")
|
|
|
|
|
|
# Add this at the end of app.py
|
|
if __name__ == '__main__':
|
|
app.run(debug=True, host='0.0.0.0', port=5000)
|
|
|