docs(33): create phase plans for currency system
6 plans across 3 waves covering market-aware pricing, exchange rates, community price data, and currency-normalized display. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
226
.planning/phases/33-currency-system/33-03-PLAN.md
Normal file
226
.planning/phases/33-currency-system/33-03-PLAN.md
Normal file
@@ -0,0 +1,226 @@
|
||||
---
|
||||
phase: 33-currency-system
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [01, 02]
|
||||
files_modified:
|
||||
- src/server/services/market-price.service.ts
|
||||
- src/server/routes/market-prices.ts
|
||||
- src/server/routes/exchange-rates.ts
|
||||
- src/server/index.ts
|
||||
- src/server/services/item.service.ts
|
||||
- src/server/services/thread.service.ts
|
||||
- tests/services/market-price.service.test.ts
|
||||
autonomous: true
|
||||
requirements: [D-01, D-02, D-06, D-09, D-10]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "GET /api/exchange-rates returns current exchange rates"
|
||||
- "GET /api/global-items/:id/prices returns market prices for a catalog item"
|
||||
- "POST /api/global-items/:id/prices creates/updates a market price (authenticated)"
|
||||
- "Item and candidate API responses include price currency context"
|
||||
- "Candidate update accepts foundPriceCents, foundPriceCurrency, foundPriceDate fields"
|
||||
artifacts:
|
||||
- path: "src/server/services/market-price.service.ts"
|
||||
provides: "CRUD operations for market prices"
|
||||
exports: ["getMarketPrices", "upsertMarketPrice"]
|
||||
- path: "src/server/routes/market-prices.ts"
|
||||
provides: "Market price API endpoints"
|
||||
- path: "src/server/routes/exchange-rates.ts"
|
||||
provides: "Exchange rate API endpoint"
|
||||
- path: "tests/services/market-price.service.test.ts"
|
||||
provides: "Market price service tests"
|
||||
min_lines: 30
|
||||
key_links:
|
||||
- from: "src/server/routes/market-prices.ts"
|
||||
to: "src/server/services/market-price.service.ts"
|
||||
via: "route handler calls service"
|
||||
pattern: "getMarketPrices|upsertMarketPrice"
|
||||
- from: "src/server/routes/exchange-rates.ts"
|
||||
to: "src/server/services/currency.service.ts"
|
||||
via: "route handler calls getExchangeRates"
|
||||
pattern: "getExchangeRates"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create market prices API, exchange rates endpoint, and update existing item/candidate endpoints with currency context.
|
||||
|
||||
Purpose: Server-side price infrastructure — enables clients and MCP consumers to access market prices and perform currency conversion.
|
||||
Output: New API endpoints for market prices and exchange rates, updated item/candidate responses with currency fields.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/33-currency-system/33-CONTEXT.md
|
||||
@.planning/phases/33-currency-system/33-01-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
From src/server/services/currency.service.ts (created in Plan 01):
|
||||
```typescript
|
||||
export interface ExchangeRates {
|
||||
base: string;
|
||||
date: string;
|
||||
rates: Record<string, number>;
|
||||
}
|
||||
export function getExchangeRates(): Promise<ExchangeRates>;
|
||||
export function convertPrice(cents: number, from: string, to: string, rates: ExchangeRates): number;
|
||||
export const CURRENCY_MARKET_MAP: Record<string, string>;
|
||||
export function getMarketForCurrency(currency: string): string;
|
||||
```
|
||||
|
||||
From src/db/schema.ts (updated in Plan 01):
|
||||
```typescript
|
||||
export const marketPrices = pgTable("market_prices", {
|
||||
id: serial("id").primaryKey(),
|
||||
globalItemId: integer("global_item_id").notNull().references(() => globalItems.id, { onDelete: "cascade" }),
|
||||
market: text("market").notNull(),
|
||||
currency: text("currency").notNull(),
|
||||
priceCents: integer("price_cents").notNull(),
|
||||
source: text("source"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
}, (table) => [unique().on(table.globalItemId, table.market, table.currency)]);
|
||||
```
|
||||
|
||||
From src/server/routes/items.ts (existing pattern):
|
||||
```typescript
|
||||
// Route pattern: Hono routes with zod-validator
|
||||
import { Hono } from "hono";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
```
|
||||
|
||||
From src/server/index.ts (existing route registration pattern):
|
||||
```typescript
|
||||
app.route("/api/items", itemRoutes);
|
||||
app.route("/api/threads", threadRoutes);
|
||||
// etc.
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create market price service and API endpoints</name>
|
||||
<files>src/server/services/market-price.service.ts, src/server/routes/market-prices.ts, src/server/routes/exchange-rates.ts, src/server/index.ts, tests/services/market-price.service.test.ts</files>
|
||||
<read_first>src/server/services/global-item.service.ts, src/server/routes/global-items.ts, src/server/index.ts</read_first>
|
||||
<behavior>
|
||||
- getMarketPrices(db, globalItemId) returns all market prices for a global item
|
||||
- getMarketPricesForMarket(db, globalItemId, market) returns market-specific prices
|
||||
- upsertMarketPrice(db, data) creates or updates a market price (ON CONFLICT update)
|
||||
- GET /api/exchange-rates returns ExchangeRates JSON (public, no auth)
|
||||
- GET /api/global-items/:id/prices returns { marketPrices: [...], communityStats: [...] }
|
||||
- POST /api/global-items/:id/prices requires auth, validates with Zod, calls upsertMarketPrice
|
||||
</behavior>
|
||||
<action>
|
||||
Create `src/server/services/market-price.service.ts`:
|
||||
- `getMarketPrices(db, globalItemId)`: SELECT * FROM market_prices WHERE global_item_id = $1 ORDER BY market
|
||||
- `getMarketPricesForMarket(db, globalItemId, market)`: Same + AND market = $2
|
||||
- `upsertMarketPrice(db, { globalItemId, market, currency, priceCents, source })`: INSERT INTO market_prices ... ON CONFLICT (global_item_id, market, currency) DO UPDATE SET price_cents = EXCLUDED.price_cents, source = EXCLUDED.source
|
||||
- Type `Db` follows existing pattern: `type Db = typeof prodDb`
|
||||
|
||||
Create `src/server/routes/exchange-rates.ts`:
|
||||
- `GET /` (mounted at /api/exchange-rates): Call `getExchangeRates()` from currency.service, return JSON response
|
||||
- Public endpoint (no auth required) — follows existing pattern where GET endpoints are public
|
||||
|
||||
Create `src/server/routes/market-prices.ts`:
|
||||
- `GET /global-items/:id/prices`: Call getMarketPrices(db, id), return { marketPrices }
|
||||
- `POST /global-items/:id/prices`: Require auth (per existing auth middleware pattern), validate body with Zod schema `{ market: z.string(), currency: z.string().max(3), priceCents: z.number().int().nonnegative(), source: z.string().optional() }`, call upsertMarketPrice
|
||||
|
||||
Register routes in `src/server/index.ts`:
|
||||
- `app.route("/api/exchange-rates", exchangeRateRoutes)`
|
||||
- `app.route("/api/market-prices", marketPriceRoutes)`
|
||||
|
||||
Create `tests/services/market-price.service.test.ts`:
|
||||
- Test getMarketPrices returns empty array for unknown item
|
||||
- Test upsertMarketPrice creates a new market price
|
||||
- Test upsertMarketPrice updates existing price on conflict
|
||||
- Test getMarketPricesForMarket filters by market
|
||||
- Use createTestDb() helper (from tests/helpers/db.ts)
|
||||
</action>
|
||||
<acceptance_criteria>
|
||||
- src/server/services/market-price.service.ts exports getMarketPrices, getMarketPricesForMarket, upsertMarketPrice
|
||||
- src/server/routes/exchange-rates.ts exports a Hono app
|
||||
- src/server/routes/market-prices.ts exports a Hono app with GET and POST handlers
|
||||
- src/server/index.ts contains `app.route("/api/exchange-rates"`
|
||||
- src/server/index.ts contains `app.route("/api/market-prices"`
|
||||
- `bun test tests/services/market-price.service.test.ts` passes
|
||||
</acceptance_criteria>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/GearBox && bun test tests/services/market-price.service.test.ts</automated>
|
||||
</verify>
|
||||
<done>Market prices API and exchange rates endpoint working with tests</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Update item and candidate endpoints with currency context</name>
|
||||
<files>src/server/services/item.service.ts, src/server/services/thread.service.ts</files>
|
||||
<read_first>src/server/services/item.service.ts, src/server/services/thread.service.ts, src/server/routes/items.ts, src/server/routes/threads.ts</read_first>
|
||||
<action>
|
||||
Update `src/server/services/item.service.ts`:
|
||||
- In create/update functions: accept and persist `priceCurrency` field from request body
|
||||
- In getAll/getById responses: include `priceCurrency` in the SELECT column list
|
||||
- The existing `priceCents` fields remain unchanged — `priceCurrency` is additive
|
||||
|
||||
Update `src/server/services/thread.service.ts`:
|
||||
- In candidate create/update functions: accept and persist `foundPriceCents`, `foundPriceCurrency`, `foundPriceDate` fields (per D-06, D-07)
|
||||
- In getThreadWithCandidates response: include `foundPriceCents`, `foundPriceCurrency`, `foundPriceDate` in the candidate SELECT
|
||||
- The existing candidate `priceCents` field remains unchanged
|
||||
|
||||
Per D-09, D-10: Do NOT add conversion logic to these endpoints yet — that will be handled by the client formatter evolution in Plan 05. The server returns raw prices with currency metadata; the client handles display formatting.
|
||||
</action>
|
||||
<acceptance_criteria>
|
||||
- src/server/services/item.service.ts create function handles priceCurrency
|
||||
- src/server/services/item.service.ts getAll includes priceCurrency in select
|
||||
- src/server/services/thread.service.ts candidate create handles foundPriceCents, foundPriceCurrency, foundPriceDate
|
||||
- src/server/services/thread.service.ts getThreadWithCandidates includes foundPriceCents, foundPriceCurrency, foundPriceDate
|
||||
- `bun test` passes (existing tests still work)
|
||||
</acceptance_criteria>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/GearBox && bun test</automated>
|
||||
</verify>
|
||||
<done>Item and candidate services return currency context in all responses, accept new currency fields on create/update</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| client→server | Market price submissions (POST) — user input for price, currency, market |
|
||||
| server→database | SQL queries with user-provided market/currency strings |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-33-06 | Tampering | POST /api/market-prices | mitigate | Zod validation on all fields — priceCents must be non-negative integer, currency max 3 chars, market non-empty string |
|
||||
| T-33-07 | Elevation of Privilege | POST /api/market-prices | mitigate | Auth middleware required on POST — only authenticated users can submit prices |
|
||||
| T-33-08 | Injection | market-price.service.ts | mitigate | Use Drizzle ORM parameterized queries — no raw SQL string concatenation |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `bun test` passes (all existing + new tests)
|
||||
- Exchange rates endpoint returns valid JSON
|
||||
- Market prices endpoint returns array for known global item
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Exchange rates and market prices APIs available
|
||||
- Item/candidate responses include currency context
|
||||
- All tests pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/33-currency-system/33-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user