@sonatel-os/strapi-plugin-tcg 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,144 @@
1
- # Temporary Holding Version
1
+ # @sonatel-os/strapi-plugin-tcg
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A complete trading-card-game module for **Strapi v5**. Drop it into any Strapi install and you get the whole game backend plus the admin screens to run it: seasons, card catalog, packs with QR codes, pulls with a deterministic engine, peer-to-peer trades, redemptions validated at a booth, players, audit, dashboard. Everything is gated by Strapi's own RBAC.
4
+
5
+ It was built to move **Jambaar** (the Dakar 2026 Youth Olympics card game) off its standalone API and into the central CMS, but nothing in it is Jambaar-specific: a season carries its own rarity table, limits, rank ladder and card pool, so the same install can run several games or several editions of one game.
6
+
7
+ ---
8
+
9
+ ## What you get
10
+
11
+ | Area | In the admin | For the mini-program (`/api/tcg`) |
12
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
13
+ | **Seasons** | Create, edit, activate, archive. Rarity table with live 100 % check, limits, pity rules, rank ladder, editions. | `GET /season` |
14
+ | **Cards** | Grid with filters, create/edit modal, lore and artist, **supply cap** and mint counter, "only at…" exclusivity badge. | `GET /cards`, `GET /cards/:id` |
15
+ | **Packs & drops** | A pack is a place or a booster; each pack has **drops**: one QR each, **static (printed poster)** or **rotating (booth screen)**, with open/close window, scan caps, cooldown, and its own cards that can be **exclusive to that QR**. Effective-pool preview, dry-run simulator. | `GET /packs`, `GET /packs/:slug` (with drops) |
16
+ | **Live** | **Boosts**: multiply the odds of any rarity for a time window, season-wide or on one pack; presets like "Golden hour". Open/sold-out drops, rewards running low. | `GET /live` |
17
+ | **Pulls** | Dashboard rarity split vs target, audit log with seed, roll, **odds snapshot**, **pity** flag and **mint number**. | `POST /pulls/scan`, `GET /pulls/history`, `GET /pulls/cooldowns` |
18
+ | **Collection** | Per-player view with completion, rank, sets, badges, mint numbers. | `GET /collection`, `GET /collection/stats` |
19
+ | **Trades** | Counts on the dashboard. | `POST /trades/offer`, `POST /trades/scan`, `…/accept`, `…/reject`, `…/cancel`, `GET /trades/active`, `GET /trades/history` |
20
+ | **Rewards** | **Catalog** with tiers, stock, per-player limit, booth, availability window and a **cost builder**: N cards of a rarity (or at least a rarity), a specific card, a complete set (owned or handed in). | `GET /rewards`, `GET /rewards/eligibility` |
21
+ | **Booth** | Type or scan a redemption code, see the cards and the reward, hand it over or cancel and give the cards back (stock released). | `POST /redemptions`, `GET /redemptions/:id`, `GET /redemptions/history` |
22
+ | **Sets & badges** | **Sets** are collection goals and reward costs. **Badges** are achievements with rules (pulls, rarity pulls, packs visited, streak, completion, mint ≤ N, scanned a drop, set complete…), secret or visible; nine starters seeded per season. | `GET /sets`, `GET /badges` |
23
+ | **Players** | Search by code, external id, phone or name; detail with badges, sets, mint numbers; deactivate; grant a card. | `GET /me`, `PATCH /me` |
24
+
25
+ All plugin tables are prefixed `tcg_` and hidden from the Content Manager — the module owns its UI.
26
+
27
+ ### How a pull works
28
+
29
+ 1. The player scans a drop QR. Static tokens are verified by signature; rotating tokens also burn a nonce (single use, survives restarts and pods).
30
+ 2. Window, scan caps, cooldown (printed codes default to a 24 h per-player cooldown), daily limits and collection size are checked.
31
+ 3. The effective pool is built: pack pool, drop pool or both; cards flagged **exclusive** anywhere only drop where flagged; **sold-out** cards (supply cap reached) are removed.
32
+ 4. Live **boosts** reshape the rarity table; **pity** clamps the minimum rarity after too many low pulls.
33
+ 5. The engine draws (seeded, reproducible); the copy is **minted** with a conditional increment so two concurrent pulls never overshoot a cap; drop scans are counted the same way.
34
+ 6. The pull is recorded with its odds snapshot; **badges** are evaluated and newly earned ones come back in the response (`unlocked`), as they do after trades and redemptions.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ yarn add @sonatel-os/strapi-plugin-tcg
40
+ ```
41
+
42
+ ```js
43
+ // config/plugins.js — everything is optional
44
+ tcg: {
45
+ enabled: true,
46
+ config: {
47
+ playerCodePrefix: 'JMB', // the JMB in JMB-A7K2 (2–6 uppercase letters/digits)
48
+ qr: { globalSecret: env('TCG_QR_SECRET') }, // signs trade QR codes and packs without their own secret
49
+ players: {
50
+ idHeader: 'x-player-id', // how the caller names the player (any stable id it owns)
51
+ nameHeader: 'x-player-name', // optional display-name hint
52
+ avatarHeader: 'x-player-avatar', // optional avatar URL hint
53
+ autoProvision: true, // unknown ids create a player; false → 404 PLAYER_NOT_FOUND
54
+ },
55
+ },
56
+ },
57
+ ```
58
+
59
+ Rebuild the admin (`yarn build`), restart, then give roles the **TCG** permissions under _Settings → Roles → Plugins_.
60
+
61
+ ## Protecting the API
62
+
63
+ The plugin has no authentication of its own. Its player routes under `/api/tcg` are ordinary Strapi **content-API** routes, so they are governed exactly like the rest of your API:
64
+
65
+ - **API tokens** (_Settings → API Tokens_): create a token, tick the TCG actions it may call (`game.scan`, `game.collection`, …), and have your backend or host app send it as `Authorization: Bearer <token>`.
66
+ - **Users & Permissions roles**: tick the same actions on a role, and callers authenticate with a users-permissions JWT instead.
67
+
68
+ Any Strapi can install the plugin; any host (a mini-program through its gateway, a mobile app's backend, a kiosk) can drive it with the credential you hand it.
69
+
70
+ ## How players are identified
71
+
72
+ The caller names the player with a header on every player route:
73
+
74
+ ```http
75
+ POST /api/tcg/pulls/scan
76
+ Authorization: Bearer <strapi api token>
77
+ x-player-id: 221770000001 # any stable id the host owns: phone, Max It user id, username…
78
+ x-player-name: Awa Diop # optional, refreshes the profile
79
+ x-player-avatar: https://… # optional
80
+
81
+ { "qrToken": "p.…" }
82
+ ```
83
+
84
+ The first time an id is seen a player is created with a code (`JMB-A7K2`) that they show at the booth; later calls reuse it. `GET /me` returns the player (`isNew` is true on the call that created it). Catalog routes (`/season`, `/packs`, `/cards`, `/live`, `/rewards`) need no player header. Missing header → `400 PLAYER_REQUIRED`; unknown id with `autoProvision: false` → `404 PLAYER_NOT_FOUND`; deactivated in the admin → `403 PLAYER_INACTIVE`.
85
+
86
+ Who the person is behind the id is the host's business: Jambaar, for instance, identifies players through the Max It host SDK in the mini-program and lets its gateway attach the header.
87
+
88
+ ## Error envelope
89
+
90
+ ```json
91
+ {
92
+ "error": {
93
+ "status": 422,
94
+ "code": "COOLDOWN_ACTIVE",
95
+ "message": "Patientez encore 231 s.",
96
+ "details": { "remainingSeconds": 231 }
97
+ }
98
+ }
99
+ ```
100
+
101
+ Clients branch on `code` (see `server/src/constants.js`), never on the French message.
102
+
103
+ ## The engine
104
+
105
+ Pulls are deterministic and auditable: `sha256(player:pack:pullNumber:timestamp)` seeds a Mulberry32 PRNG; the first roll picks the rarity from the season's cumulative table, the second picks a card by weight inside that rarity (falling back to the whole pool). Seed, roll, rarity and a verification hash are stored on every pull, and the Audit screen shows them. The Simulate tab runs the same engine 2 000 times against a pack without writing anything.
106
+
107
+ QR tokens are HMAC-signed (`p.<pack>.<nonce>.<exp>.<sig>` / `t.<offer>.<player>.<nonce>.<exp>.<sig>`), expire, and are single-use: consumed nonces are written to `tcg_nonces` so replay protection works across restarts and pods. Expired nonces and trade offers are swept every minute.
108
+
109
+ ## Permissions
110
+
111
+ | Action | Lets a role… |
112
+ | ------------------------------------------------------------ | ---------------------------------------- |
113
+ | `access` | see the TCG menu entry |
114
+ | `dashboard.read`, `audit.read` | read the dashboard, the pull log |
115
+ | `seasons.read/write`, `cards.read/write`, `packs.read/write` | manage the catalog |
116
+ | `packs.qr` | generate booth QR codes |
117
+ | `players.read/write` | look players up, deactivate, grant cards |
118
+ | `redemptions.read/fulfill` | run the booth |
119
+ | `settings.read/write` | see the configuration page |
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ yarn install
125
+ yarn test # jest, pure logic (engine, QR, identity, season rules)
126
+ yarn build # dist/ for admin + server
127
+ yarn watch:link # live-reload inside a host app (strapi-plugin watch:link)
128
+ ```
129
+
130
+ In a host app, point at your checkout while iterating:
131
+
132
+ ```js
133
+ tcg: { enabled: true, resolve: '../strapi-plugin-tcg', config: { /* … */ } }
134
+ ```
135
+
136
+ Layout: `server/src` (content-types, services, controllers, routes, policies, utils), `admin/src` (pages, components, RTK Query slice, translations EN/FR).
137
+
138
+ ## Roadmap
139
+
140
+ - Card image upload from the Cards screen (today: set `image` through the Media Library relation or the API).
141
+ - Duplicate economy (melt copies into a craft), public live feed, geofenced scans, CSV import, printable sheet of every drop QR.
142
+ - Excel export of pulls / redemptions through `@sonatel/strapi-plugin-excel-export`.
143
+ - Webhooks on `pull.completed`, `trade.accepted`, `redemption.fulfilled`.
144
+ - Per-season leaderboard endpoint for the mini-program.