import os from pathlib import Path from flask import Flask, redirect, send_from_directory from flask_cors import CORS from app.config import Config from app.extensions import db, bcrypt, migrate def create_app(config_class=Config): # Check if static frontend build exists (Docker production mode) static_dir = Path(__file__).resolve().parent.parent / 'static' if static_dir.exists(): app = Flask(__name__, static_folder=str(static_dir), static_url_path='') else: app = Flask(__name__) app.config.from_object(config_class) # Ensure data directories exist Path(app.config['UPLOAD_PATH']).mkdir(parents=True, exist_ok=True) db_dir = Path(app.config['SQLALCHEMY_DATABASE_URI'].replace('sqlite:///', '')).parent db_dir.mkdir(parents=True, exist_ok=True) # Initialize extensions db.init_app(app) bcrypt.init_app(app) migrate.init_app(app, db) # CORS CORS(app, resources={r'/api/*': {'origins': app.config['FRONTEND_URL']}}, supports_credentials=True) # Register blueprints from app.api import api_bp app.register_blueprint(api_bp) # Well-known URLs for CalDAV/CardDAV auto-discovery (iOS, DAVx5, etc.) @app.route('/.well-known/caldav') def wellknown_caldav(): return redirect('/dav/', code=301) @app.route('/.well-known/carddav') def wellknown_carddav(): return redirect('/dav/', code=301) # iCal export (public, no auth) @app.route('/ical/') def ical_export_route(token): from app.api.calendar import ical_export as _ical_export return _ical_export(token) # Serve frontend SPA for all non-API routes (production/Docker) if static_dir.exists(): @app.route('/') def serve_index(): return send_from_directory(str(static_dir), 'index.html') @app.errorhandler(404) def serve_spa(e): return send_from_directory(str(static_dir), 'index.html') # Create tables with app.app_context(): from app import models # noqa: F401 db.create_all() # Enable WAL mode for SQLite with db.engine.connect() as conn: conn.execute(db.text('PRAGMA journal_mode=WAL')) conn.commit() # Start backup scheduler (only in main process, not reloader) if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true': from app.services.backup_scheduler import start_backup_scheduler start_backup_scheduler(app) return app