-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
156 lines (134 loc) · 5 KB
/
app.py
File metadata and controls
156 lines (134 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""Litestar application factory."""
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from pathlib import Path
import structlog
from advanced_alchemy.config.asyncio import AsyncSessionConfig
from litestar import Litestar
from litestar.config.csrf import CSRFConfig
from litestar.contrib.jinja import JinjaTemplateEngine
from litestar.contrib.sqlalchemy.plugins import SQLAlchemyAsyncConfig, SQLAlchemyPlugin
from litestar.middleware.session.server_side import ServerSideSessionConfig
from litestar.openapi import OpenAPIConfig
from litestar.stores.memory import MemoryStore
from litestar.template.config import TemplateConfig
from polar_flow_server import __version__
from polar_flow_server.admin import admin_router
from polar_flow_server.api import api_routers
from polar_flow_server.core.config import settings
from polar_flow_server.core.database import (
async_session_maker,
close_database,
engine,
init_database,
)
from polar_flow_server.middleware import RateLimitHeadersMiddleware
from polar_flow_server.routes import root_redirect
from polar_flow_server.services.scheduler import SyncScheduler, set_scheduler
# Configure structured logging
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_log_level,
structlog.stdlib.add_logger_name,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.stdlib.BoundLogger,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
logger = structlog.get_logger()
@asynccontextmanager
async def lifespan(app: Litestar) -> AsyncIterator[None]:
"""Application lifespan manager.
Handles startup and shutdown tasks:
- Initialize database on startup
- Start background sync scheduler
- Close database connections on shutdown
- Stop scheduler on shutdown
"""
logger.info(
"Starting polar-flow-server",
version=__version__,
mode=settings.deployment_mode.value,
sync_enabled=settings.sync_enabled,
sync_interval=settings.sync_interval_minutes,
)
# Initialize database tables
await init_database()
logger.info("Database initialized")
# Start background sync scheduler
scheduler = SyncScheduler(async_session_maker)
set_scheduler(scheduler)
await scheduler.start()
yield
# Stop scheduler
await scheduler.stop()
# Cleanup database
await close_database()
logger.info("Shutdown complete")
def create_app() -> Litestar:
"""Create Litestar application.
Returns:
Configured Litestar app instance
"""
# Get templates directory path
templates_dir = Path(__file__).parent / "templates"
# Session store for admin authentication
# In production with multiple instances, use Redis instead
session_store = MemoryStore()
# Session middleware config
session_config = ServerSideSessionConfig(
key=settings.get_session_secret(),
store="session_store",
max_age=86400, # 24 hours
)
# CSRF protection config
csrf_config = CSRFConfig(
secret=settings.get_session_secret(),
cookie_name="csrf_token",
header_name="X-CSRF-Token",
exclude=[
"/admin/login", # Login form - entry point, no session yet
"/admin/setup", # Setup flow - entry point, no session yet
"/admin/oauth/callback", # OAuth callback from Polar (admin dashboard)
"/admin/settings", # Settings pages (reset-oauth, etc.)
"/admin/sync", # Sync trigger from dashboard
"/admin/logout", # Logout action
"/admin/api-keys/", # API key management (uses session auth)
"/oauth/", # OAuth endpoints for SaaS (callback, exchange, start)
"/users/", # API routes use API key auth, not sessions
"/health",
],
)
return Litestar(
route_handlers=[root_redirect, *api_routers, admin_router],
lifespan=[lifespan],
openapi_config=OpenAPIConfig(
title="polar-flow-server API",
version=__version__,
description="Self-hosted health analytics server for Polar devices",
),
template_config=TemplateConfig(
directory=templates_dir,
engine=JinjaTemplateEngine,
),
plugins=[
SQLAlchemyPlugin(
config=SQLAlchemyAsyncConfig(
engine_instance=engine,
session_dependency_key="session",
session_config=AsyncSessionConfig(expire_on_commit=False),
),
),
],
middleware=[session_config.middleware, RateLimitHeadersMiddleware],
csrf_config=csrf_config,
stores={"session_store": session_store},
debug=settings.log_level == "DEBUG",
)
# Application instance
app = create_app()