fix(api): validate quantity on POST /api/user-cards
Match decks handler parseInt/NaN guard so non-numeric quantities cannot corrupt user_cards row counts from scanner add paths. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
66717c4198
commit
8827d7b20c
3 changed files with 34 additions and 4 deletions
|
|
@ -24,8 +24,8 @@ Reports posted to [PR #44](https://github.com/varutasu/tcg-vault/pull/44#issueco
|
||||||
|
|
||||||
| Slug | Priority | Source |
|
| Slug | Priority | Source |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `scanner-redesign-a11y-fixes` | P1 | **In progress** — PR pending |
|
| `scanner-redesign-a11y-fixes` | P1 | **RESOLVED** — PR #45 |
|
||||||
| `scanner-user-cards-quantity-guard` | P2 | Reviewer — parseInt/NaN guard parity with decks handler |
|
| `scanner-user-cards-quantity-guard` | P2 | **In progress** — PR pending |
|
||||||
| `test-scanner-redesign-surfaces` | P2 | Reviewer — unit tests for new components + upload route |
|
| `test-scanner-redesign-surfaces` | P2 | Reviewer — unit tests for new components + upload route |
|
||||||
| `document-condition-foil-destination-semantics` | P3 | Reviewer — clarify or migrate condition/foil for collection/deck rows |
|
| `document-condition-foil-destination-semantics` | P3 | Reviewer — clarify or migrate condition/foil for collection/deck rows |
|
||||||
| `camera-scanner-token-cleanup` | P3 | Design system — replace hardcoded hex overlay colors in CameraScanner |
|
| `camera-scanner-token-cleanup` | P3 | Design system — replace hardcoded hex overlay colors in CameraScanner |
|
||||||
|
|
|
||||||
25
.convoys/scanner-user-cards-quantity-guard.md
Normal file
25
.convoys/scanner-user-cards-quantity-guard.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
name: scanner-user-cards-quantity-guard
|
||||||
|
classification: fix
|
||||||
|
success_metric: |
|
||||||
|
POST /api/user-cards rejects non-numeric and sub-1 quantity with 400, matching
|
||||||
|
the decks/[id]/cards handler contract.
|
||||||
|
depends_on:
|
||||||
|
- redesign-scanner-flow
|
||||||
|
status: open
|
||||||
|
created: 2026-05-27
|
||||||
|
---
|
||||||
|
|
||||||
|
# Convoy: scanner-user-cards-quantity-guard
|
||||||
|
|
||||||
|
P2 follow-up from `audit-redesign-scanner-flow-44` reviewer report.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- `pages/api/user-cards.js` — `parseInt(quantity, 10)` + NaN / `< 1` guard on POST
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
1. `quantity: "abc"` → 400 `Quantity must be at least 1`
|
||||||
|
2. `quantity: 0` → 400
|
||||||
|
3. Valid integer ≥ 1 uses parsed value for INSERT and UPDATE increment
|
||||||
|
|
@ -21,6 +21,11 @@ export default async function handler(req, res) {
|
||||||
return res.status(400).json({ error: 'Card ID is required' });
|
return res.status(400).json({ error: 'Card ID is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parsedQuantity = parseInt(quantity, 10);
|
||||||
|
if (Number.isNaN(parsedQuantity) || parsedQuantity < 1) {
|
||||||
|
return res.status(400).json({ error: 'Quantity must be at least 1' });
|
||||||
|
}
|
||||||
|
|
||||||
const scanImageUrl =
|
const scanImageUrl =
|
||||||
typeof scanImageUrlRaw === 'string' && scanImageUrlRaw.trim().length > 0
|
typeof scanImageUrlRaw === 'string' && scanImageUrlRaw.trim().length > 0
|
||||||
? scanImageUrlRaw.trim()
|
? scanImageUrlRaw.trim()
|
||||||
|
|
@ -34,7 +39,7 @@ export default async function handler(req, res) {
|
||||||
|
|
||||||
if (existingResult.rows.length > 0) {
|
if (existingResult.rows.length > 0) {
|
||||||
// Update quantity; preserve existing scan image unless a new URL is supplied
|
// Update quantity; preserve existing scan image unless a new URL is supplied
|
||||||
const newQuantity = existingResult.rows[0].quantity + quantity;
|
const newQuantity = existingResult.rows[0].quantity + parsedQuantity;
|
||||||
await sql`
|
await sql`
|
||||||
UPDATE user_cards
|
UPDATE user_cards
|
||||||
SET quantity = ${newQuantity},
|
SET quantity = ${newQuantity},
|
||||||
|
|
@ -46,7 +51,7 @@ export default async function handler(req, res) {
|
||||||
// Insert new record
|
// Insert new record
|
||||||
await sql`
|
await sql`
|
||||||
INSERT INTO user_cards (user_id, card_id, quantity, condition, is_foil, scan_image_url)
|
INSERT INTO user_cards (user_id, card_id, quantity, condition, is_foil, scan_image_url)
|
||||||
VALUES (${user.userId}, ${cardId}, ${quantity}, ${condition}, ${is_foil}, ${scanImageUrl})
|
VALUES (${user.userId}, ${cardId}, ${parsedQuantity}, ${condition}, ${is_foil}, ${scanImageUrl})
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue