4d392f44f5
- user.invite_code 列 + invite_relation 表(invitee_user_id 唯一 = 幂等防重复发奖) - GET /api/v1/invite/me(我的码+分享链接+战绩)、POST /api/v1/invite/bind - 复用 wallet.grant_coins 同事务发币;自邀屏蔽 / 无效码 / 新用户闸(注册72h内才发) - alembic 迁移(11a1d08c6f55 -> invite_code_and_relation)+ 8 个测试(钱路/幂等/并发兜底全覆盖) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Reviewed-on: #24
65 lines
2.4 KiB
Python
65 lines
2.4 KiB
Python
"""好友邀请 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),
|
|
)
|