Files
GearBox/.planning/phases/33-currency-system/33-03-PLAN.md
Jean-Luc Makiola 7a696f39a5 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>
2026-04-13 17:58:37 +02:00

10 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
33-currency-system 03 execute 2
01
02
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
true
D-01
D-02
D-06
D-09
D-10
truths artifacts key_links
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
path provides exports
src/server/services/market-price.service.ts CRUD operations for market prices
getMarketPrices
upsertMarketPrice
path provides
src/server/routes/market-prices.ts Market price API endpoints
path provides
src/server/routes/exchange-rates.ts Exchange rate API endpoint
path provides min_lines
tests/services/market-price.service.test.ts Market price service tests 30
from to via pattern
src/server/routes/market-prices.ts src/server/services/market-price.service.ts route handler calls service getMarketPrices|upsertMarketPrice
from to via pattern
src/server/routes/exchange-rates.ts src/server/services/currency.service.ts route handler calls getExchangeRates getExchangeRates
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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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 From src/server/services/currency.service.ts (created in Plan 01): ```typescript export interface ExchangeRates { base: string; date: string; rates: Record; } export function getExchangeRates(): Promise; export function convertPrice(cents: number, from: string, to: string, rates: ExchangeRates): number; export const CURRENCY_MARKET_MAP: Record; export function getMarketForCurrency(currency: string): string; ```

From src/db/schema.ts (updated in Plan 01):

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):

// 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):

app.route("/api/items", itemRoutes);
app.route("/api/threads", threadRoutes);
// etc.
Task 1: Create market price service and API endpoints 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 src/server/services/global-item.service.ts, src/server/routes/global-items.ts, src/server/index.ts - 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 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) <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> cd /home/jlmak/Projects/jlmak/GearBox && bun test tests/services/market-price.service.test.ts Market prices API and exchange rates endpoint working with tests
Task 2: Update item and candidate endpoints with currency context src/server/services/item.service.ts, src/server/services/thread.service.ts src/server/services/item.service.ts, src/server/services/thread.service.ts, src/server/routes/items.ts, src/server/routes/threads.ts 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. <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> cd /home/jlmak/Projects/jlmak/GearBox && bun test Item and candidate services return currency context in all responses, accept new currency fields on create/update

<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>
- `bun test` passes (all existing + new tests) - Exchange rates endpoint returns valid JSON - Market prices endpoint returns array for known global item

<success_criteria>

  • Exchange rates and market prices APIs available
  • Item/candidate responses include currency context
  • All tests pass </success_criteria>
After completion, create `.planning/phases/33-currency-system/33-03-SUMMARY.md`