| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- import asyncio
- import contextlib
- from contextlib import asynccontextmanager
- from pathlib import Path
- from fastapi import FastAPI, Request
- from fastapi.middleware.cors import CORSMiddleware
- from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
- from fastapi.staticfiles import StaticFiles
- from starlette.middleware.base import BaseHTTPMiddleware
- from room_cal.api._routes import services, calc, chat, credentials, prices, reservations, closed, discounts, actions, workflows, health, reload, sms, busy
- import os
- from room_cal.api._auth import router as auth_router, get_user, AUTH_DISABLED
- from room_cal._health import run_health_loop
- _VUE = Path(__file__).parents[1] / 'vue' / 'dist'
- _DEV_HTML = _VUE / 'dev.html'
- _CALENDAR_HTML = _VUE / 'calendar.html'
- _CALC_HTML = _VUE / 'calc.html'
- _API_BASE = os.getenv('APP_BASE_URL', '').rstrip('/')
- def _render(path: Path) -> str:
- return path.read_text().replace("var API_BASE = '';", f"var API_BASE = '{_API_BASE}';", 1)
- _HTML_PATHS = {'/dev', '/calendar', '/calc'}
- _PUBLIC_PREFIXES = ('/auth/', '/calc', '/health', '/busy')
- _OPEN_PATHS = {'/docs', '/openapi.json', '/redoc'}
- class _AuthMiddleware(BaseHTTPMiddleware):
- async def dispatch(self, request: Request, call_next):
- path = request.url.path
- if path in _OPEN_PATHS or any(path.startswith(p) for p in _PUBLIC_PREFIXES):
- return await call_next(request)
- user = get_user(request)
- if not user:
- if path in _HTML_PATHS:
- return RedirectResponse(f'/auth/login?next={path}')
- return JSONResponse({'detail': 'Not authenticated'}, status_code=401)
- request.state.user = user
- return await call_next(request)
- @asynccontextmanager
- async def _lifespan(app: FastAPI):
- task = asyncio.create_task(run_health_loop())
- yield
- task.cancel()
- with contextlib.suppress(asyncio.CancelledError):
- await task
- _log = __import__('logging').getLogger(__name__)
- def create_app() -> FastAPI:
- import sqlite3
- from caldav.lib.error import AuthorizationError
- app = FastAPI(title='room_cal', lifespan=_lifespan)
- @app.exception_handler(AuthorizationError)
- async def _caldav_auth_error(request: Request, exc: AuthorizationError):
- _log.error('caldav: %s', exc)
- return JSONResponse({'detail': str(exc)}, status_code=500)
- @app.exception_handler(sqlite3.OperationalError)
- async def _sqlite_error(request: Request, exc: sqlite3.OperationalError):
- _log.error('sqlite: %s', exc)
- return JSONResponse({'detail': str(exc)}, status_code=500)
- app.add_middleware(CORSMiddleware, allow_origins=['*'], allow_methods=['*'], allow_headers=['*'])
- app.add_middleware(_AuthMiddleware)
- app.include_router(auth_router)
- for mod in (services, calc, chat, credentials, prices, reservations, closed, discounts, actions, workflows, health, reload, sms, busy):
- app.include_router(mod.router)
- app.mount('/assets', StaticFiles(directory=str(_VUE / 'assets')), name='assets')
- @app.get('/dev', response_class=HTMLResponse, include_in_schema=False)
- async def dev_ui():
- return _render(_DEV_HTML)
- @app.get('/calendar', response_class=HTMLResponse, include_in_schema=False)
- async def calendar_ui():
- return _render(_CALENDAR_HTML)
- @app.get('/calc', response_class=HTMLResponse, include_in_schema=False)
- async def calc_ui():
- return _render(_CALC_HTML)
- return app
|