"""Discord's legacy /discord/* auth routes, plus the DM opt-in toggle. The OAuth flow itself moved to ``accounts/oauth.py`` when Google was added — this module holds no auth logic, only the paths that have to keep answering: * **/discord/link/callback is registered in Discord's developer portal.** Changing it would need an operator to edit the portal before any Discord login worked, so it stays exactly where it is and forwards to the shared handler. * The other /discord/* routes are what the currently-deployed frontend calls. Frontend and backend deploy independently (see the root CLAUDE.md), so a newer backend must keep serving an older frontend. They can go once no deployed frontend predates /oauth/*. ``POST /discord/dm`` is not legacy and is not moving: it toggles alert delivery, which is a notifications concern, not an authentication one. """ from __future__ import annotations from fastapi import APIRouter, Depends, Request from pydantic import BaseModel from sqlalchemy import update from sqlalchemy.ext.asyncio import AsyncSession from accounts import oauth from accounts.db import get_async_session from accounts.models import User from accounts.users import ( current_active_user, current_user_optional, get_access_token_db, get_user_manager, ) router = APIRouter(tags=["discord"]) @router.get("/discord/config") async def discord_config(): """Superseded by /oauth/config, which reports every provider. Kept because the deployed frontend asks this one.""" return {"enabled": oauth.enabled("discord")} @router.get("/discord/link/start") async def link_start(request: Request, user=Depends(current_active_user)): return await oauth.link_start("discord", request, user) @router.get("/discord/login/start") async def login_start(request: Request, user=Depends(current_user_optional)): return await oauth.login_start("discord", request, user) @router.get("/discord/link/callback") async def link_callback( request: Request, user=Depends(current_user_optional), session: AsyncSession = Depends(get_async_session), user_manager=Depends(get_user_manager), access_token_db=Depends(get_access_token_db), ): """The URL registered in Discord's portal. Both Discord flows land here.""" return await oauth._callback("discord", request, user, session, user_manager, access_token_db) @router.post("/discord/unlink", status_code=204) async def unlink( user=Depends(current_active_user), session: AsyncSession = Depends(get_async_session), ): await oauth._unlink("discord", user, session) class DmToggle(BaseModel): enabled: bool @router.post("/discord/dm", status_code=204) async def set_dm( payload: DmToggle, user=Depends(current_active_user), session: AsyncSession = Depends(get_async_session), ): """Turn Discord DM alerts on or off without unlinking. A no-op unless a Discord account is actually linked.""" await session.execute( update(User).where(User.id == user.id, User.discord_id.is_not(None)) .values(discord_dm=payload.enabled) ) await session.commit()