d7c29c0883
- rewards: 激励视频实发改按 calculate_ad_reward_coin(eCPM, 当日第N次) 公式;AD_REWARD_COIN/MAX_AD_REWARD_COIN 降为历史兼容口径;新增 SIGNIN_BOOST_COIN=2000 - 签到膨胀: Day1-13 看完激励视频额外发固定金币、Day14 不允许 (signin.py / signin-boost) - ad_session_id 幂等: ad_ecpm/ad_reward/ad_feed_reward 记录加 ad_session_id 列 + 唯一索引(新迁移 coin_reward_phase2 / ad_feed_reward_session) - ad.py + schemas + repositories: ecpm-report / feed-reward / reward-status / test-grant 改造;config_schema 增 signin_boost_coin;admin stats overview 补充 - 同步 docs/api + docs/database + docs/integrations/pangle 及 tests(test_ad_reward / test_welfare) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: OuYingJun1024 <1034284404@qq.com> Reviewed-on: #28 Co-authored-by: ouzhou <ouzhou@wonderable.ai> Co-committed-by: ouzhou <ouzhou@wonderable.ai>
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""签到 endpoint。
|
|
|
|
路由前缀 `/api/v1/signin`:
|
|
GET /status 今日签到状态 + 14 天档位
|
|
POST / 执行今日签到
|
|
POST /boost 签到后看广告膨胀金币
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, HTTPException, status
|
|
|
|
from app.api.deps import CurrentUser, DbSession
|
|
from app.repositories import ad_reward as crud_ad
|
|
from app.repositories import signin as crud_signin
|
|
from app.repositories import wallet as crud_wallet
|
|
from app.schemas.welfare import (
|
|
SigninBoostRequest,
|
|
SigninBoostResultOut,
|
|
SigninResultOut,
|
|
SigninStatusOut,
|
|
)
|
|
|
|
logger = logging.getLogger("shagua.signin")
|
|
|
|
router = APIRouter(prefix="/api/v1/signin", tags=["signin"])
|
|
|
|
|
|
@router.get("/status", response_model=SigninStatusOut, summary="今日签到状态")
|
|
def signin_status(user: CurrentUser, db: DbSession) -> SigninStatusOut:
|
|
st = crud_signin.get_status(db, user.id)
|
|
return SigninStatusOut.model_validate(st, from_attributes=True)
|
|
|
|
|
|
@router.post("", response_model=SigninResultOut, summary="执行今日签到")
|
|
def do_signin(user: CurrentUser, db: DbSession) -> SigninResultOut:
|
|
try:
|
|
record, balance = crud_signin.do_signin(db, user.id)
|
|
except crud_signin.AlreadySignedError as e:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="already signed today") from e
|
|
|
|
logger.info(
|
|
"signin ok user_id=%d day=%d streak=%d coin=%d",
|
|
user.id, record.cycle_day, record.streak, record.coin_awarded,
|
|
)
|
|
return SigninResultOut(
|
|
coin_awarded=record.coin_awarded,
|
|
cycle_day=record.cycle_day,
|
|
streak=record.streak,
|
|
coin_balance=balance,
|
|
)
|
|
|
|
|
|
@router.post("/boost", response_model=SigninBoostResultOut, summary="签到后看广告膨胀金币")
|
|
def boost_signin(
|
|
payload: SigninBoostRequest, user: CurrentUser, db: DbSession
|
|
) -> SigninBoostResultOut:
|
|
if not payload.ad_ref_id:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="ad reward required")
|
|
ad_rec = crud_ad.find_by_trans(db, payload.ad_ref_id)
|
|
if (
|
|
ad_rec is None
|
|
or ad_rec.user_id != user.id
|
|
or ad_rec.reward_scene != "signin_boost"
|
|
or ad_rec.status != "granted"
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="ad reward not verified")
|
|
record = crud_signin.boost_by_ad_ref(db, user.id, payload.ad_ref_id)
|
|
if record is None:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="signin boost not granted")
|
|
acc = crud_wallet.get_or_create_account(db, user.id)
|
|
balance = acc.coin_balance
|
|
|
|
logger.info(
|
|
"signin boost ok user_id=%d date=%s coin=%d",
|
|
user.id, record.signin_date, record.coin_awarded,
|
|
)
|
|
return SigninBoostResultOut(
|
|
coin_awarded=record.coin_awarded,
|
|
coin_balance=balance,
|
|
signin_date=record.signin_date.isoformat(),
|
|
)
|