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>
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 |
|
|
true |
|
|
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.
Create src/server/routes/exchange-rates.ts:
GET /(mounted at /api/exchange-rates): CallgetExchangeRates()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.tspasses </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
Update src/server/services/thread.service.ts:
- In candidate create/update functions: accept and persist
foundPriceCents,foundPriceCurrency,foundPriceDatefields (per D-06, D-07) - In getThreadWithCandidates response: include
foundPriceCents,foundPriceCurrency,foundPriceDatein the candidate SELECT - The existing candidate
priceCentsfield 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> |
<success_criteria>
- Exchange rates and market prices APIs available
- Item/candidate responses include currency context
- All tests pass </success_criteria>