|
5 | 5 | GET /me/collection — registered users only (guests have no persistent collection) |
6 | 6 | """ |
7 | 7 |
|
8 | | -from fastapi import APIRouter, Depends |
| 8 | +import time |
| 9 | + |
| 10 | +from fastapi import APIRouter, Depends, HTTPException |
9 | 11 | from sqlalchemy.orm import Session |
10 | 12 |
|
| 13 | +from app.config import settings |
11 | 14 | from app.database import get_db |
12 | 15 | from app.dependencies import get_current_user, require_registered |
13 | 16 | from app.models.db import User |
|
16 | 19 | from app.schemas.progress import ( |
17 | 20 | CollectionUnlockRequest, |
18 | 21 | CollectionUnlockResponse, |
19 | | - CurrencyUpdateRequest, |
20 | | - CurrencyUpdateResponse, |
21 | 22 | ProgressMergeRequest, |
22 | 23 | ProgressMergeResponse, |
| 24 | + RewardClaimRequest, |
| 25 | + RewardClaimResponse, |
23 | 26 | ) |
24 | 27 |
|
25 | 28 | router = APIRouter(prefix="/me", tags=["me"]) |
@@ -107,17 +110,35 @@ def merge_progress( |
107 | 110 | ) |
108 | 111 |
|
109 | 112 |
|
110 | | -@router.put("/currency", response_model=CurrencyUpdateResponse) |
111 | | -def update_currency( |
112 | | - req: CurrencyUpdateRequest, |
| 113 | +# Per-user rate-limit state for single-play reward claims. |
| 114 | +# Key: user_id, Value: last claim timestamp. |
| 115 | +_reward_last_claim: dict[str, float] = {} |
| 116 | + |
| 117 | + |
| 118 | +@router.post("/reward", response_model=RewardClaimResponse) |
| 119 | +def claim_reward( |
| 120 | + req: RewardClaimRequest, |
113 | 121 | user: User = Depends(require_registered), |
114 | 122 | db: Session = Depends(get_db), |
115 | | -) -> CurrencyUpdateResponse: |
| 123 | +) -> RewardClaimResponse: |
116 | 124 | """ |
117 | | - Persist the exact wallet amount for the authenticated registered user. |
| 125 | + Claim a delta-based currency reward from a single-player game. |
| 126 | +
|
| 127 | + The server validates: |
| 128 | + - amount does not exceed REWARD_SINGLE_PLAY_MAX |
| 129 | + - minimum cooldown between claims (rate-limit) |
118 | 130 | """ |
119 | | - profile_repo.set_currency(db, user.profile, req.currency) |
120 | | - return CurrencyUpdateResponse(currency=req.currency) |
| 131 | + if req.amount > settings.REWARD_SINGLE_PLAY_MAX: |
| 132 | + raise HTTPException(status_code=422, detail="Reward amount exceeds maximum") |
| 133 | + |
| 134 | + now = time.monotonic() |
| 135 | + last = _reward_last_claim.get(user.id, 0.0) |
| 136 | + if now - last < settings.REWARD_SINGLE_PLAY_COOLDOWN_SECONDS: |
| 137 | + raise HTTPException(status_code=429, detail="Reward claim too frequent") |
| 138 | + _reward_last_claim[user.id] = now |
| 139 | + |
| 140 | + profile_repo.add_currency(db, user.profile, req.amount) |
| 141 | + return RewardClaimResponse(granted=req.amount, currency=user.profile.currency) |
121 | 142 |
|
122 | 143 |
|
123 | 144 | @router.post("/collection/unlock", response_model=CollectionUnlockResponse) |
|
0 commit comments