Files
Code_of_Conquest/public_web/app/__init__.py

117 lines
3.9 KiB
Python

"""
Public Web Frontend - Flask Application Factory
This is a lightweight web frontend that provides HTML/HTMX UI for the Code of Conquest game.
All business logic is handled by the API backend - this frontend only renders views and
makes HTTP requests to the API.
"""
from flask import Flask
from flask import render_template
import structlog
import yaml
import os
from pathlib import Path
from datetime import datetime, timezone
logger = structlog.get_logger(__name__)
def load_config():
"""Load configuration from YAML file based on environment."""
env = os.getenv("FLASK_ENV", "development")
config_path = Path(__file__).parent.parent / "config" / f"{env}.yaml"
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
logger.info("configuration_loaded", env=env, config_path=str(config_path))
return config
def create_app():
"""Create and configure the Flask application."""
app = Flask(__name__,
template_folder="../templates",
static_folder="../static")
# Load configuration
config = load_config()
app.config.update(config)
# Configure secret key from environment
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
# Context processor to make API config and user available in templates
@app.context_processor
def inject_template_globals():
"""Make API base URL and current user available to all templates."""
from .utils.auth import get_current_user
return {
'api_base_url': app.config.get('api', {}).get('base_url', 'http://localhost:5000'),
'current_user': get_current_user()
}
# Register blueprints
from .views.auth_views import auth_bp
from .views.character_views import character_bp
from .views.combat_views import combat_bp
from .views.game_views import game_bp
from .views.pages import pages_bp
app.register_blueprint(auth_bp)
app.register_blueprint(character_bp)
app.register_blueprint(combat_bp)
app.register_blueprint(game_bp)
app.register_blueprint(pages_bp)
# Register Jinja filters
def format_timestamp(iso_string: str) -> str:
"""Convert ISO timestamp to relative time (e.g., '2 mins ago')"""
if not iso_string:
return ""
try:
timestamp = datetime.fromisoformat(iso_string.replace('Z', '+00:00'))
now = datetime.now(timezone.utc)
diff = now - timestamp
seconds = diff.total_seconds()
if seconds < 60:
return "Just now"
elif seconds < 3600:
mins = int(seconds / 60)
return f"{mins} min{'s' if mins != 1 else ''} ago"
elif seconds < 86400:
hours = int(seconds / 3600)
return f"{hours} hr{'s' if hours != 1 else ''} ago"
else:
days = int(seconds / 86400)
return f"{days} day{'s' if days != 1 else ''} ago"
except Exception as e:
logger.warning("timestamp_format_failed", iso_string=iso_string, error=str(e))
return iso_string
app.jinja_env.filters['format_timestamp'] = format_timestamp
# Register dev blueprint only in development
env = os.getenv("FLASK_ENV", "development")
if env == "development":
from .views.dev import dev_bp
app.register_blueprint(dev_bp)
logger.info("dev_blueprint_registered", message="Dev testing routes available at /dev")
# Error handlers
@app.errorhandler(404)
def not_found(error):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_error(error):
logger.error("internal_server_error", error=str(error))
return render_template('errors/500.html'), 500
logger.info("flask_app_created", blueprints=["auth", "character", "combat", "game", "pages"])
return app