"""好友邀请 endpoint。 路由前缀 /api/v1/invite,需 Bearer 鉴权(用户级数据): GET /me 我的邀请码 + 分享链接 + 已邀人数/已得金币(邀请页展示) POST /bind 把当前登录用户(被邀请人)绑定到某邀请码,注册即生效,双方各发金币 客户端两种调用 /bind: - 自动:首启读剪贴板拿到邀请码 → 登录后调本接口(channel=clipboard) - 手动:用户在邀请页输入好友邀请码(channel=manual) 绑定的真正逻辑(邀请码生成/反查、幂等、自邀屏蔽、发金币)在 repositories/invite.py。 """ from __future__ import annotations import logging from fastapi import APIRouter from app.api.deps import CurrentUser, DbSession from app.core.config import settings from app.repositories import invite as invite_repo from app.schemas.invite import BindInviteIn, BindInviteOut, InviteInfoOut logger = logging.getLogger("shagua.invite") router = APIRouter(prefix="/api/v1/invite", tags=["invite"]) _BIND_MESSAGES = { "success": "邀请绑定成功,金币已到账", "already_bound": "你已绑定过邀请人", "invalid_code": "邀请码无效", "self_invite": "不能填写自己的邀请码", "not_eligible": "邀请仅对新注册用户生效", } @router.get("/me", response_model=InviteInfoOut, summary="我的邀请码 + 分享链接 + 战绩") def my_invite(user: CurrentUser, db: DbSession) -> InviteInfoOut: code = invite_repo.ensure_code(db, user) invited, coins = invite_repo.get_stats(db, user.id) sep = "&" if "?" in settings.INVITE_LANDING_URL else "?" share_url = f"{settings.INVITE_LANDING_URL}{sep}ref={code}" return InviteInfoOut( invite_code=code, share_url=share_url, invited_count=invited, coins_earned=coins, ) @router.post("/bind", response_model=BindInviteOut, summary="绑定邀请人(注册即生效,双方发金币)") def bind_invite(req: BindInviteIn, user: CurrentUser, db: DbSession) -> BindInviteOut: result = invite_repo.bind( db, invitee=user, invite_code=req.invite_code, channel=req.channel, ) logger.info( "invite bind invitee=%d code=%s channel=%s -> %s", user.id, req.invite_code, req.channel, result.status, ) return BindInviteOut( status=result.status, coins_awarded=result.invitee_coin, message=_BIND_MESSAGES.get(result.status, result.status), )