add gamification characters (Duolingo-style mascot catalog)
New progress.Character model: an admin-managed catalog (same pattern as subscription.Plan) of selectable mascots, each unlocked once the user's XP crosses unlock_xp. No separate ownership table yet — unlocking is a pure function of UserStats.xp, kept intentionally minimal since this is the first of an incremental rollout the senior asked for. - GET /api/characters — catalog with per-user unlocked/selected flags - POST /api/characters/select — picks an active character, rejecting ones the user hasn't unlocked yet - UserStats.selected_character_id tracks the pick - seed_test_data now seeds 4 sample characters (owl/fox/turtle/lion) for local/staging testing Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@ API docs: Swagger `/api/docs/`, Redoc `/api/redoc/`, admin `/admin/`.
|
||||
|---|---|---|
|
||||
| `accounts` | identity, sessions, OTP auth | `User`, `Profile`, `Session`, `OTPCode` |
|
||||
| `content` | lessons/curriculum | `Grade`, `Unit`, `Node`, `Lesson`, `Question`, `Choice`, `NodeProgress` |
|
||||
| `progress` | stats, leaderboard, streaks | `UserStats`, `LeaderboardEntry`, `StreakRecord`, `UserHearts` |
|
||||
| `progress` | stats, leaderboard, streaks, gamification characters | `UserStats`, `LeaderboardEntry`, `StreakRecord`, `UserHearts`, `Character` |
|
||||
| `ai_tutor` | AI assistant chat | `Conversation`, `Message`, `MessageFeedback` |
|
||||
| `subscription` | plans | `Plan`, `Subscription` |
|
||||
| `payment` | orders/transactions (Zarinpal gateway + Cafe Bazaar IAP) | `Order`, `Transaction` |
|
||||
@@ -60,6 +60,8 @@ API docs: Swagger `/api/docs/`, Redoc `/api/redoc/`, admin `/admin/`.
|
||||
|
||||
**SMS** goes through Kavenegar (`KAVENEGAR_API_KEY`/`KAVENEGAR_SENDER`); failures return explicit 503, not a fake success.
|
||||
|
||||
**Gamification characters** (`progress.Character`, Duolingo-mascot-style): an admin-managed catalog (like `subscription.Plan` — created via Django admin or `seed_test_data`, not by users). Unlocking is currently just `UserStats.xp >= Character.unlock_xp` (no separate ownership table); `UserStats.selected_character_id` is the user's active pick, set via `POST /api/characters/select` after that check. First real content beyond the model/plumbing (art assets, more characters, purchase-with-coins, etc.) is expected to land incrementally later.
|
||||
|
||||
**Payment (Zarinpal)**: `create_order`/`CreateOrderView` never call Zarinpal directly — `paymentUrl` points at our own `GET /api/payment/orders/{id}/pay` (`PaymentRedirectView`), which calls Zarinpal's PaymentRequest API on first visit (storing `Order.gateway_authority`) and redirects to StartPay; this keeps order creation from depending on Zarinpal's uptime. Zarinpal redirects the user back to `GET /api/payment/zarinpal/callback` (`ZarinpalCallbackView`), which always re-verifies server-to-server via `payment/zarinpal.py::verify_payment` before marking an order paid — a forged `Status=OK` in the query string alone does nothing. Both `payment/zarinpal.py` and `payment/bazaar.py` fail closed (503 / invalid) when their credentials aren't configured, never a fake success.
|
||||
|
||||
## Known gaps (per README pre-deploy checklist)
|
||||
|
||||
Reference in New Issue
Block a user