여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다. - ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제 - 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다 - eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다 파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이). site·frontend·admin tsc, site vitest 105 passed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
from uuid import UUID
|
|
from fastapi import APIRouter, Depends, Request, Response, HTTPException, Query
|
|
from fastapi.responses import RedirectResponse
|
|
from common.logger import LOG
|
|
from common.models.gmodel import UserInfo
|
|
from router.v1.validator.dependencies import IsValidAccessToken
|
|
from services import social_account_service as service
|
|
from services.external.social import SocialError
|
|
|
|
router = APIRouter(prefix="/v1/social/oauth", tags=["Social"])
|
|
COOKIE = "social_oauth_browser"
|
|
|
|
|
|
@router.post("/connect")
|
|
async def connect(response: Response, user: UserInfo = Depends(IsValidAccessToken)):
|
|
try:
|
|
url, browser = service.begin(UUID(user.user_id), 2)
|
|
except SocialError as ex:
|
|
raise HTTPException(409, str(ex)) from ex
|
|
response.set_cookie(
|
|
COOKIE,
|
|
browser,
|
|
httponly=True,
|
|
secure=True,
|
|
samesite="lax",
|
|
max_age=600,
|
|
path="/v1/social/oauth",
|
|
)
|
|
response.headers["Cache-Control"] = "no-store"
|
|
return {"url": url}
|
|
|
|
|
|
@router.get("/callback")
|
|
async def oauth_callback(
|
|
request: Request,
|
|
state: str = Query("", max_length=2048),
|
|
code: str = Query("", max_length=4096),
|
|
error: str = Query("", max_length=200),
|
|
):
|
|
ok = False
|
|
if not error and code and state:
|
|
try:
|
|
await service.finish(state, request.cookies.get(COOKIE), code)
|
|
ok = True
|
|
except Exception as ex: # noqa: BLE001
|
|
# 화면에는 원문을 내보내지 않는다 — OAuth 응답·state 에는 자격증명이 들어 있다.
|
|
LOG.w(f"[social] 계정 연결 실패: {type(ex).__name__}: {ex}")
|
|
elif error:
|
|
# 사장님이 Meta 화면에서 취소한 경우도 여기로 온다 — 고장과 구별되게 남긴다.
|
|
LOG.i(f"[social] 계정 연결 중단(제공자 응답): {error[:80]}")
|
|
response = RedirectResponse(
|
|
"/sites?social=" + ("connected" if ok else "failed"), status_code=303
|
|
)
|
|
response.delete_cookie(
|
|
COOKIE, path="/v1/social/oauth", secure=True, httponly=True, samesite="lax"
|
|
)
|
|
response.headers["Cache-Control"] = "no-store"
|
|
response.headers["Referrer-Policy"] = "no-referrer"
|
|
return response
|
|
|
|
|
|
@router.post("/disconnect")
|
|
async def disconnect(user: UserInfo = Depends(IsValidAccessToken)):
|
|
await service.disconnect(UUID(user.user_id), 2)
|
|
return {"disconnected": True}
|