bump project

This commit is contained in:
2026-05-01 21:24:21 +03:30
parent dc4b1a1400
commit 139b3e26da
83 changed files with 2455 additions and 18349 deletions

123
app/__init__.py Normal file
View File

@@ -0,0 +1,123 @@
from __future__ import annotations
import logging
import os
from pathlib import Path
from flask import Flask, render_template
from app.config import ProductionConfig, config_by_name
from app.extensions import csrf, db, login_manager, migrate
def create_app(config_name: str | None = None, config_overrides: dict | None = None) -> Flask:
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
app = Flask(__name__, instance_relative_config=True)
Path(app.instance_path).mkdir(parents=True, exist_ok=True)
selected_config = config_name or os.getenv("APP_ENV", "development")
config_class = config_by_name.get(selected_config, config_by_name["development"])
app.config.from_object(config_class)
if config_overrides:
app.config.update(config_overrides)
if config_class is ProductionConfig:
validate_production_config(app)
configure_logging(app)
init_extensions(app)
register_blueprints(app)
register_error_handlers(app)
register_security_headers(app)
register_template_context(app)
from app.commands import register_commands
register_commands(app)
return app
def init_extensions(app: Flask) -> None:
from app.models import Account
db.init_app(app)
migrate.init_app(app, db)
csrf.init_app(app)
login_manager.login_view = "auth.login"
login_manager.login_message_category = "warning"
login_manager.init_app(app)
@login_manager.user_loader
def load_user(account_id: str) -> Account | None:
if not account_id.isdigit():
return None
return db.session.get(Account, int(account_id))
def register_blueprints(app: Flask) -> None:
from app.admin.routes import bp as admin_bp
from app.attendance.routes import bp as attendance_bp
from app.auth.routes import bp as auth_bp
from app.main.routes import bp as main_bp
from app.people.routes import bp as people_bp
from app.recognition.routes import bp as recognition_bp
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
app.register_blueprint(admin_bp)
app.register_blueprint(people_bp)
app.register_blueprint(attendance_bp)
app.register_blueprint(recognition_bp)
def register_error_handlers(app: Flask) -> None:
@app.errorhandler(403)
def forbidden(error):
return render_template("errors/403.html"), 403
@app.errorhandler(404)
def not_found(error):
return render_template("errors/404.html"), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
app.logger.exception("Unhandled application error: %s", error)
return render_template("errors/500.html"), 500
def register_security_headers(app: Flask) -> None:
@app.after_request
def set_default_security_headers(response):
response.headers.setdefault("X-Content-Type-Options", "nosniff")
response.headers.setdefault("X-Frame-Options", "SAMEORIGIN")
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
response.headers.setdefault("Permissions-Policy", "camera=(self), microphone=()")
return response
def register_template_context(app: Flask) -> None:
from datetime import datetime, timezone
from app.models import Role
@app.context_processor
def inject_globals():
return {"current_year": datetime.now(timezone.utc).year, "Role": Role}
def configure_logging(app: Flask) -> None:
if app.debug or app.testing:
return
logging.basicConfig(level=logging.INFO)
def validate_production_config(app: Flask) -> None:
if app.config["SECRET_KEY"] in {"", "dev-change-me"}:
raise RuntimeError("SECRET_KEY must be set for production.")