# Stock Portfolio Database Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Store manually added tickers in SQLite database using Prisma ORM with full CRUD operations and tests. **Architecture:** Use Prisma ORM with SQLite to persist stock tickers added via the analyze page. Each stock entry stores ticker symbol, optional notes, and timestamps. The analyze route fetches stored tickers from DB and merges with Alpaca positions. **Tech Stack:** Prisma ORM, SQLite, TypeScript, React Router 7 --- ## Prerequisites Check - [ ] Check if Prisma is already installed in package.json - [ ] Check existing database/schema files --- ### Task 1: Initialize Prisma and Create Stock Model **Files:** - Create: `prisma/schema.prisma` - Create: `prisma/migrations/xxxxxxxxxxxx_init/migration.sql` (generated) - [ ] **Step 1: Install Prisma dependencies** ```bash npm install prisma @prisma/client npx prisma init --datasource-provider sqlite ``` Expected: Creates prisma/ directory with schema.prisma - [ ] **Step 2: Define Stock model in schema.prisma** ```prisma generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = "file:./dev.db" } model Stock { id String @id @default(cuid()) ticker String @unique notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } ``` Expected: Schema file saved - [ ] **Step 3: Generate Prisma client and migrate** ```bash npx prisma generate npx prisma migrate dev --name init ``` Expected: `prisma/dev.db` created, client generated - [ ] **Step 4: Commit** ```bash git add prisma/ git commit -m "feat: initialize prisma with Stock model" ``` --- ### Task 2: Create Database Service Layer **Files:** - Create: `app/lib/db.server.ts` - [ ] **Step 1: Create Prisma client singleton** ```typescript import { PrismaClient } from "@prisma/client"; declare global { var prisma: PrismaClient | undefined; } export const db = global.prisma || new PrismaClient(); if (process.env.NODE_ENV !== "production") { global.prisma = db; } ``` Expected: File created without TypeScript errors - [ ] **Step 2: Commit** ```bash git add app/lib/db.server.ts git commit -m "feat: create prisma client singleton" ``` --- ### Task 3: Create Stock API Routes **Files:** - Create: `app/routes/api/stocks/index.ts` - Create: `app/routes/api/stocks/$ticker.ts` - [ ] **Step 1: Create GET /api/stocks route** ```typescript import { db } from "../../../lib/db.server"; export async function loader() { const stocks = await db.stock.findMany({ orderBy: { ticker: "asc" }, }); return Response.json(stocks); } export async function action({ request }: { request: Request }) { const formData = await request.formData(); const ticker = formData.get("ticker")?.toString().toUpperCase(); if (!ticker) { return Response.json({ error: "Ticker is required" }, { status: 400 }); } const stock = await db.stock.create({ data: { ticker }, }); return Response.json(stock); } ``` - [ ] **Step 2: Register routes in routes.ts** ```typescript route("api/stocks", "routes/api/stocks/index.ts"), route("api/stocks/$ticker", "routes/api/stocks/\$ticker.ts"), ``` - [ ] **Step 3: Commit** ```bash git add app/routes/api/stocks/ git commit -m "feat: add stock CRUD API routes" ``` --- ### Task 4: Modify Analyze Route to Use Database **Files:** - Modify: `app/routes/analyze.tsx` - [ ] **Step 1: Merge Alpaca positions with database stocks on load** ```typescript // In loadPortfolio useEffect, after fetching Alpaca positions: const [alpacaPositions, dbStocks] = await Promise.all([ fetch("/api/alpaca/positions").then(r => r.ok ? r.json() : []), fetch("/api/stocks").then(r => r.ok ? r.json() : []) ]); // Build initial stocks from both sources const initialStocks = await Promise.all([ // Alpaca positions first ...alpacaPositions.map(async (p: { ticker: string; qty: number }) => { // ... existing logic }), // Then DB stocks not in Alpaca ...dbStocks.map(async (s: { ticker: string }) => { const existing = alpacaPositions.find((p: { ticker: string }) => p.ticker === s.ticker); if (existing) return null; return { id: `db-${s.ticker}`, ticker: s.ticker, currentPrice: null, position: 0, rsi: null, analysis: null, loading: false, }; }) ].filter(Boolean)); ``` - [ ] **Step 2: Save new ticker to database when added** ```typescript // In addStock function, after successfully adding ticker: await fetch("/api/stocks", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ticker }), }); ``` - [ ] **Step 3: Commit** ```bash git add app/routes/analyze.tsx git commit -m "feat: integrate stock database with analyze page" ``` --- ### Task 5: Write Playwright Tests **Files:** - Create: `tests/stock-db.spec.ts` - [ ] **Step 1: Write test for stock CRUD API** ```typescript import { test, expect } from "@playwright/test"; test.describe("Stock Database", () => { test("should add and list stocks", async ({ page }) => { // POST to create const createRes = await page.request.post("/api/stocks", { data: { ticker: "TEST" }, headers: { "Content-Type": "application/x-www-form-urlencoded" }, }); expect(createRes.ok()).toBeTruthy(); // GET to list const listRes = await page.request.get("/api/stocks"); const stocks = await listRes.json(); expect(stocks).toContainEqual(expect.objectContaining({ ticker: "TEST" })); }); }); ``` - [ ] **Step 2: Commit** ```bash git add tests/stock-db.spec.ts git commit -m "test: add stock database E2E tests" ``` --- ### Task 6: Verify Installation **Files:** - Check: `package.json` - [ ] **Step 1: Run typecheck and tests** ```bash npm run typecheck npm run test:e2e ``` Expected: All commands succeed - [ ] **Step 2: Commit** ```bash git commit -am "chore: verify prisma integration" ```