- Initialize Prisma with SQLite and Stock model - Create database service layer with singleton client - Add API routes for stock CRUD operations - Integrate database with analyze page to persist ticker entries - Add Playwright tests for stock database functionality
6.0 KiB
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
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
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
npx prisma generate
npx prisma migrate dev --name init
Expected: prisma/dev.db created, client generated
- Step 4: Commit
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
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
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
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
route("api/stocks", "routes/api/stocks/index.ts"),
route("api/stocks/$ticker", "routes/api/stocks/\$ticker.ts"),
- Step 3: Commit
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
// 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
// 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
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
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
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
npm run typecheck
npm run test:e2e
Expected: All commands succeed
- Step 2: Commit
git commit -am "chore: verify prisma integration"