nanochat/services/auth/src/database.py
Manmohan Sharma 4b4aca642a
feat(auth): OAuth2 + JWT auth service with Alembic migrations (#5 #7)
- Alembic async migrations: users, conversations, messages, is_favorited
- FastAPI auth service: Google + GitHub OAuth, RS256 JWT, refresh cookie
- /auth/me, /auth/refresh, /auth/validate (service-to-service)
- rate limiting 10/min on OAuth routes, CORS locked to FRONTEND_URL

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 11:47:00 -07:00

50 lines
1.3 KiB
Python

"""Async SQLAlchemy engine + session factory."""
from __future__ import annotations
from collections.abc import AsyncIterator
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase
from .config import get_settings
class Base(DeclarativeBase):
"""Shared declarative base for all ORM models."""
_engine = None
_session_factory: async_sessionmaker[AsyncSession] | None = None
def _build_engine():
global _engine, _session_factory
settings = get_settings()
_engine = create_async_engine(settings.database_url, pool_pre_ping=True)
_session_factory = async_sessionmaker(_engine, expire_on_commit=False)
def get_engine():
if _engine is None:
_build_engine()
return _engine
def get_session_factory() -> async_sessionmaker[AsyncSession]:
if _session_factory is None:
_build_engine()
assert _session_factory is not None
return _session_factory
async def get_session() -> AsyncIterator[AsyncSession]:
factory = get_session_factory()
async with factory() as session:
yield session
def override_session_factory(factory: async_sessionmaker[AsyncSession]) -> None:
"""Testing hook: swap the session factory for an in-memory engine."""
global _session_factory
_session_factory = factory