Initial commit: DigiServer v2 with Blueprint Architecture

Features implemented:
- Application factory pattern with environment-based config
- 7 modular blueprints (main, auth, admin, players, groups, content, api)
- Flask-Caching with Redis support for production
- Flask-Login authentication with bcrypt password hashing
- API endpoints with rate limiting and Bearer token auth
- Comprehensive error handling and logging
- CLI commands (init-db, create-admin, seed-db)

Blueprint Structure:
- main: Dashboard with caching, health check endpoint
- auth: Login, register, logout, password change
- admin: User management, system settings, theme, logo upload
- players: Full CRUD, fullscreen view, bulk operations, playlist management
- groups: Group management, player assignments, content management
- content: Upload with progress tracking, file management, preview/download
- api: RESTful endpoints with authentication, rate limiting, player feedback

Performance Optimizations:
- Dashboard caching (60s timeout)
- Playlist caching (5min timeout)
- Redis caching for production
- Memoized functions for expensive operations
- Cache clearing on data changes

Security Features:
- Bcrypt password hashing
- Flask-Login session management
- admin_required decorator for authorization
- Player authentication via auth codes
- API Bearer token authentication
- Rate limiting on API endpoints (60 req/min default)
- Input validation and sanitization

Documentation:
- README.md: Full project documentation with quick start
- PROGRESS.md: Detailed progress tracking and roadmap
- BLUEPRINT_GUIDE.md: Quick reference for blueprint architecture

Pending work:
- Models migration from v1 with database indexes
- Utils migration from v1 with type hints
- Templates migration with updated route references
- Docker multi-stage build configuration
- Unit tests for all blueprints

Ready for models and utils migration from digiserver v1
This commit is contained in:
ske087
2025-11-12 10:00:30 +02:00
commit 244b44f5e0
17 changed files with 3420 additions and 0 deletions

175
app/app.py Normal file
View File

@@ -0,0 +1,175 @@
"""
DigiServer v2 - Application Factory
Modern Flask application with blueprint architecture
"""
import os
from flask import Flask, render_template
from config import get_config
from extensions import db, bcrypt, login_manager, migrate, cache
def create_app(config_name=None):
"""
Application factory pattern
Args:
config_name: Configuration environment (development, production, testing)
Returns:
Flask application instance
"""
app = Flask(__name__, instance_relative_config=True)
# Load configuration
if config_name is None:
config_name = os.getenv('FLASK_ENV', 'development')
app.config.from_object(get_config(config_name))
# Ensure instance folder exists
os.makedirs(app.instance_path, exist_ok=True)
# Ensure upload folders exist
upload_folder = os.path.join(app.root_path, app.config['UPLOAD_FOLDER'])
logo_folder = os.path.join(app.root_path, app.config['UPLOAD_FOLDERLOGO'])
os.makedirs(upload_folder, exist_ok=True)
os.makedirs(logo_folder, exist_ok=True)
# Initialize extensions
db.init_app(app)
bcrypt.init_app(app)
login_manager.init_app(app)
migrate.init_app(app, db)
cache.init_app(app)
# Register blueprints
register_blueprints(app)
# Register error handlers
register_error_handlers(app)
# Register CLI commands
register_commands(app)
# Context processors
register_context_processors(app)
return app
def register_blueprints(app):
"""Register application blueprints"""
from blueprints.auth import auth_bp
from blueprints.admin import admin_bp
from blueprints.players import players_bp
from blueprints.groups import groups_bp
from blueprints.content import content_bp
from blueprints.api import api_bp
# Register with appropriate URL prefixes
app.register_blueprint(auth_bp)
app.register_blueprint(admin_bp, url_prefix='/admin')
app.register_blueprint(players_bp, url_prefix='/player')
app.register_blueprint(groups_bp, url_prefix='/group')
app.register_blueprint(content_bp, url_prefix='/content')
app.register_blueprint(api_bp, url_prefix='/api')
# Main dashboard route
from blueprints.main import main_bp
app.register_blueprint(main_bp)
def register_error_handlers(app):
"""Register error handlers"""
@app.errorhandler(404)
def not_found(error):
return render_template('errors/404.html'), 404
@app.errorhandler(403)
def forbidden(error):
return render_template('errors/403.html'), 403
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('errors/500.html'), 500
@app.errorhandler(413)
def request_entity_too_large(error):
return render_template('errors/413.html'), 413
@app.errorhandler(408)
def request_timeout(error):
return render_template('errors/408.html'), 408
def register_commands(app):
"""Register CLI commands"""
import click
@app.cli.command('init-db')
def init_db():
"""Initialize the database"""
db.create_all()
click.echo('Database initialized.')
@app.cli.command('create-admin')
@click.option('--username', default='admin', help='Admin username')
@click.option('--password', prompt=True, hide_input=True, help='Admin password')
def create_admin(username, password):
"""Create an admin user"""
from models.user import User
# Check if user exists
if User.query.filter_by(username=username).first():
click.echo(f'User {username} already exists.')
return
# Create admin user
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
admin = User(username=username, password=hashed_password, role='admin')
db.session.add(admin)
db.session.commit()
click.echo(f'Admin user {username} created successfully.')
@app.cli.command('seed-db')
def seed_db():
"""Seed database with sample data (development only)"""
if app.config['ENV'] == 'production':
click.echo('Cannot seed database in production.')
return
click.echo('Seeding database with sample data...')
# Add your seeding logic here
click.echo('Database seeded successfully.')
def register_context_processors(app):
"""Register context processors for templates"""
from flask_login import current_user
@app.context_processor
def inject_config():
"""Inject configuration variables into all templates"""
return {
'server_version': app.config['SERVER_VERSION'],
'build_date': app.config['BUILD_DATE'],
'logo_exists': os.path.exists(
os.path.join(app.root_path, app.config['UPLOAD_FOLDERLOGO'], 'logo.png')
)
}
@app.context_processor
def inject_user_theme():
"""Inject user theme preference"""
theme = 'light'
if current_user.is_authenticated and hasattr(current_user, 'theme'):
theme = current_user.theme
return {'theme': theme}
# For backwards compatibility and direct running
if __name__ == '__main__':
app = create_app()
app.run(host='0.0.0.0', port=5000, debug=True)