117 lines
4.2 KiB
Python
117 lines
4.2 KiB
Python
"""Main Quart application factory."""
|
|
|
|
import asyncio
|
|
import logging
|
|
from pathlib import Path
|
|
from quart import Quart, send_from_directory, send_file
|
|
from quart_cors import cors
|
|
|
|
from app.config import get_settings
|
|
from app.models import init_db
|
|
from app.api import subscriptions, sources, downloads, credentials, settings as settings_api, platforms, websocket
|
|
from app.api.websocket import start_redis_subscriber
|
|
|
|
# Path to static frontend files (built Vue app)
|
|
STATIC_DIR = Path(__file__).parent.parent / "static"
|
|
|
|
|
|
def create_app() -> Quart:
|
|
"""Create and configure the Quart application."""
|
|
app = Quart(__name__, static_folder=None) # Disable default static handling
|
|
config = get_settings()
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=getattr(logging, config.log_level.upper()),
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
)
|
|
|
|
# Enable CORS for API routes only
|
|
app = cors(app, allow_origin="*")
|
|
|
|
# Register API blueprints
|
|
app.register_blueprint(subscriptions.bp, url_prefix="/api/subscriptions")
|
|
app.register_blueprint(sources.bp, url_prefix="/api/sources")
|
|
app.register_blueprint(downloads.bp, url_prefix="/api/downloads")
|
|
app.register_blueprint(credentials.bp, url_prefix="/api/credentials")
|
|
app.register_blueprint(settings_api.bp, url_prefix="/api/settings")
|
|
app.register_blueprint(platforms.bp, url_prefix="/api/platforms")
|
|
app.register_blueprint(websocket.bp, url_prefix="/ws")
|
|
|
|
@app.before_serving
|
|
async def startup():
|
|
"""Initialize database connection on startup."""
|
|
app.db_engine = await init_db(config.async_database_url)
|
|
app.logger.info("Database connection initialized")
|
|
app.logger.info(f"Static files directory: {STATIC_DIR} (exists: {STATIC_DIR.exists()})")
|
|
|
|
# Start Redis subscriber for WebSocket events from Celery tasks
|
|
app.redis_subscriber_task = asyncio.create_task(start_redis_subscriber())
|
|
app.logger.info("Redis subscriber started for WebSocket events")
|
|
|
|
@app.after_serving
|
|
async def shutdown():
|
|
"""Close database connection on shutdown."""
|
|
# Cancel Redis subscriber task
|
|
if hasattr(app, "redis_subscriber_task"):
|
|
app.redis_subscriber_task.cancel()
|
|
try:
|
|
await app.redis_subscriber_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
app.logger.info("Redis subscriber stopped")
|
|
|
|
if hasattr(app, "db_engine"):
|
|
await app.db_engine.dispose()
|
|
app.logger.info("Database connection closed")
|
|
|
|
@app.route("/health")
|
|
async def health_check():
|
|
"""Health check endpoint."""
|
|
return {"status": "healthy"}
|
|
|
|
@app.route("/api")
|
|
async def api_info():
|
|
"""API information endpoint."""
|
|
return {
|
|
"name": "Gallery Subscriber API",
|
|
"version": "1.0.0",
|
|
"endpoints": {
|
|
"subscriptions": "/api/subscriptions",
|
|
"sources": "/api/sources",
|
|
"downloads": "/api/downloads",
|
|
"credentials": "/api/credentials",
|
|
"settings": "/api/settings",
|
|
"platforms": "/api/platforms",
|
|
"websocket": "/ws/events",
|
|
},
|
|
}
|
|
|
|
# Serve static assets (JS, CSS, images, fonts)
|
|
@app.route("/assets/<path:filename>")
|
|
async def serve_assets(filename):
|
|
"""Serve static assets from the frontend build."""
|
|
return await send_from_directory(STATIC_DIR / "assets", filename)
|
|
|
|
# Serve other static files (favicon, etc.)
|
|
@app.route("/<path:filename>")
|
|
async def serve_static(filename):
|
|
"""Serve static files or fall back to index.html for Vue Router."""
|
|
file_path = STATIC_DIR / filename
|
|
if file_path.exists() and file_path.is_file():
|
|
return await send_from_directory(STATIC_DIR, filename)
|
|
# Fall back to index.html for Vue Router (SPA)
|
|
return await send_file(STATIC_DIR / "index.html")
|
|
|
|
# Serve index.html for root path
|
|
@app.route("/")
|
|
async def serve_index():
|
|
"""Serve the frontend index.html."""
|
|
return await send_file(STATIC_DIR / "index.html")
|
|
|
|
return app
|
|
|
|
|
|
# Create app instance for hypercorn
|
|
app = create_app()
|