"""Resolving the one local user. **There is no authentication in this product.** The module keeps its name so the dependency every router already depends on keeps working, but nothing here authenticates anybody. The Adventure Storyteller is a single-user application that binds to loopback: whoever can reach the API is the person who started it, and there is nobody else to tell them apart from. Upstream had two modes. `AIDND_MULTI_USER` selected a hosted deployment with signed session cookies, guest accounts, registration, login, a shared demo API key with a per-day cap, "power users", and an owner allowlist for the analytics dashboard. M2 removed all of it: this product has no hosted mode to protect, and every one of those surfaces was a way for the application to be reached by someone other than its owner. What is left is the local path that upstream already had. Every request resolves to one automatically created user row. The `users` table and the `user_id` foreign keys on scenarios, adventures and settings stay. They are an **internal ownership detail**, not an account system: nothing creates a second user, nothing logs in, and no request carries an identity. They remain because rewriting them out would mean a migration across most of the schema to delete a column that costs nothing and keeps every existing M1 database readable. """ from datetime import timezone from fastapi import Depends, Request from sqlalchemy.orm import Session from . import models from .database import get_db def local_user(db: Session) -> models.User: """Returns the single implicit user, creating it on first use. A migration gives this user ownership of data written before per-user rows existed, so an older database resolves to the row that already owns its campaigns rather than to a fresh empty one. """ user = ( db.query(models.User) .filter(models.User.email.is_(None), models.User.is_guest.is_(False)) .order_by(models.User.id) .first() ) if user is None: user = models.User(is_guest=False) db.add(user) db.commit() return user def _touch(user: models.User, db: Session) -> None: now = models.utcnow() last = user.last_seen_at if last is not None and last.tzinfo is None: # SQLite returns DateTime columns without a timezone. They were stored # as UTC. last = last.replace(tzinfo=timezone.utc) if last is None or (now - last).total_seconds() > 3600: user.last_seen_at = now db.commit() def get_current_user( request: Request, db: Session = Depends(get_db) ) -> models.User: """The dependency every router uses. It always succeeds. `request` is unused and kept so the signature stays a FastAPI dependency the routers can depend on unchanged. """ user = local_user(db) _touch(user, db) return user