FastAPI + SQLAlchemy/Alembic + MariaDB backend with Jinja2/htmx/Alpine server-rendered frontend. Multi-user via Google OAuth, daily habit tracking, monthly/weekly history views, Web Push reminders via APScheduler, and PWA support (manifest, service worker, offline caching). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
44 lines
1.4 KiB
Python
44 lines
1.4 KiB
Python
from fastapi import Depends, HTTPException, Request, status
|
|
from itsdangerous import BadSignature, SignatureExpired, URLSafeTimedSerializer
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.config import settings
|
|
from app.database import get_db
|
|
from app.models.user import User
|
|
|
|
_serializer = URLSafeTimedSerializer(settings.secret_key, salt="habit-session")
|
|
|
|
SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30 # 30일
|
|
|
|
|
|
def create_session_token(user_id: int) -> str:
|
|
return _serializer.dumps({"user_id": user_id})
|
|
|
|
|
|
def get_session_user_id(request: Request) -> int | None:
|
|
token = request.cookies.get(settings.session_cookie_name)
|
|
if not token:
|
|
return None
|
|
try:
|
|
data = _serializer.loads(token, max_age=SESSION_MAX_AGE_SECONDS)
|
|
except (BadSignature, SignatureExpired):
|
|
return None
|
|
return data.get("user_id")
|
|
|
|
|
|
def require_login(request: Request, db: Session = Depends(get_db)) -> User:
|
|
user_id = get_session_user_id(request)
|
|
user = db.get(User, user_id) if user_id is not None else None
|
|
if user is None:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="로그인이 필요합니다")
|
|
return user
|
|
|
|
|
|
def get_current_user_optional(request: Request, db: Session) -> User | None:
|
|
user_id = get_session_user_id(request)
|
|
return db.get(User, user_id) if user_id is not None else None
|
|
|
|
|
|
def is_logged_in(request: Request) -> bool:
|
|
return get_session_user_id(request) is not None
|