[Feature] POST /objectives/claim — Claim Completed Objective Rewards #3

Open
opened 2026-06-26 05:05:28 +00:00 by funman300 · 0 comments
Owner

Overview

Allow the player to claim the reward for a completed objective. Objectives can currently be viewed and progress is tracked, but rewards cannot yet be collected.

Prerequisites

  • GET /objectives endpoint (already implemented)
  • Objectives progress tracking already in the DB

Implementation Steps

1. Verify the objective is complete

In src/services/objectives.rs:

pub async fn is_objective_complete(objective_id: &str, db: &SqlitePool) -> Result<bool>

Query SELECT progress, target FROM objectives WHERE id = ? and check progress >= target.

2. Verify the objective has not already been claimed

Check for an existing claimed_at timestamp. If already claimed → 409 Conflict:

{ "error": "already_claimed", "objective_id": "score_5_goals" }

3. Grant the reward by type

Reward types come from data/objectives.json:

  • coinsUPDATE clubs SET coins = coins + ? WHERE id = 1
  • pack → add pack to player inventory (pack_inventory table, create if needed)
  • card → add a specific card to the collection
  • xp → update player XP and trigger level-up check

4. Mark objective as claimed atomically

BEGIN;
UPDATE objectives
  SET claimed_at = CURRENT_TIMESTAMP
  WHERE id = ? AND claimed_at IS NULL;
-- grant reward here (in same transaction)
COMMIT;

The AND claimed_at IS NULL guard prevents double-claiming even under concurrent requests.

5. Add claimed_at migration

ALTER TABLE objectives ADD COLUMN claimed_at TEXT;

6. Register route in src/routes/objectives.rs

.route("/objectives/:id/claim", post(claim_objective))

7. Request/Response format

Request: POST /objectives/score_5_goals/claim (no body needed)

Response 200:

{
  "objective_id": "score_5_goals",
  "reward": { "type": "coins", "amount": 1000 },
  "claimed_at": "2026-06-25T12:00:00Z"
}

Response 400: Objective not yet complete
Response 409: Already claimed

8. Write integration tests

  • Claim a completed objective → 200, reward granted, coins/XP updated
  • Claim an incomplete objective → 400
  • Claim the same objective twice → 409 (reward not double-granted)

Acceptance Criteria

  • POST /objectives/:id/claim route exists
  • Validates completion before granting reward (400 if not complete)
  • Handles all reward types: coins, pack, card, xp
  • Idempotent: double-claim returns 409, reward is not granted twice
  • claimed_at column added via migration
  • Integration tests pass
  • cargo clippy -- -D warnings passes
## Overview Allow the player to claim the reward for a completed objective. Objectives can currently be viewed and progress is tracked, but rewards cannot yet be collected. ## Prerequisites - `GET /objectives` endpoint (already implemented) - Objectives progress tracking already in the DB ## Implementation Steps ### 1. Verify the objective is complete In `src/services/objectives.rs`: ```rust pub async fn is_objective_complete(objective_id: &str, db: &SqlitePool) -> Result<bool> ``` Query `SELECT progress, target FROM objectives WHERE id = ?` and check `progress >= target`. ### 2. Verify the objective has not already been claimed Check for an existing `claimed_at` timestamp. If already claimed → `409 Conflict`: ```json { "error": "already_claimed", "objective_id": "score_5_goals" } ``` ### 3. Grant the reward by type Reward types come from `data/objectives.json`: - `coins` → `UPDATE clubs SET coins = coins + ? WHERE id = 1` - `pack` → add pack to player inventory (`pack_inventory` table, create if needed) - `card` → add a specific card to the collection - `xp` → update player XP and trigger level-up check ### 4. Mark objective as claimed atomically ```sql BEGIN; UPDATE objectives SET claimed_at = CURRENT_TIMESTAMP WHERE id = ? AND claimed_at IS NULL; -- grant reward here (in same transaction) COMMIT; ``` The `AND claimed_at IS NULL` guard prevents double-claiming even under concurrent requests. ### 5. Add `claimed_at` migration ```sql ALTER TABLE objectives ADD COLUMN claimed_at TEXT; ``` ### 6. Register route in `src/routes/objectives.rs` ```rust .route("/objectives/:id/claim", post(claim_objective)) ``` ### 7. Request/Response format **Request:** `POST /objectives/score_5_goals/claim` (no body needed) **Response 200:** ```json { "objective_id": "score_5_goals", "reward": { "type": "coins", "amount": 1000 }, "claimed_at": "2026-06-25T12:00:00Z" } ``` **Response 400:** Objective not yet complete **Response 409:** Already claimed ### 8. Write integration tests - Claim a completed objective → 200, reward granted, coins/XP updated - Claim an incomplete objective → 400 - Claim the same objective twice → 409 (reward not double-granted) ## Acceptance Criteria - [ ] `POST /objectives/:id/claim` route exists - [ ] Validates completion before granting reward (400 if not complete) - [ ] Handles all reward types: coins, pack, card, xp - [ ] Idempotent: double-claim returns 409, reward is not granted twice - [ ] `claimed_at` column added via migration - [ ] Integration tests pass - [ ] `cargo clippy -- -D warnings` passes
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: funman300/OpenFUT-Core#3