This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
GallerySubscriber/backend/app/main.py
T
2026-01-24 22:52:51 -05:00

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()