117 lines
3.9 KiB
Python
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
|