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:
175
app/app.py
Normal file
175
app/app.py
Normal 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)
|
||||
Reference in New Issue
Block a user