feat: add stock database with prisma for portfolio persistence

- 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
This commit is contained in:
2026-05-14 10:23:56 +02:00
parent f40eec1420
commit 3340fd11ca
29 changed files with 2530 additions and 221 deletions
+23
View File
@@ -0,0 +1,23 @@
import Alpaca from "@alpacahq/alpaca-trade-api";
const alpaca = new Alpaca({
keyId: process.env.ALPACA_API_KEY!,
secretKey: process.env.ALPACA_SECRET_KEY!,
baseUrl: process.env.ALPACA_BASE_URL || "https://paper-api.alpaca.markets",
retryOnError: false,
});
export async function loader() {
try {
const positions = await alpaca.getPositions();
return Response.json(
positions.map((p: { symbol: string; qty: string; avg_entry_price: string; current_price: string }) => ({
ticker: p.symbol,
qty: parseFloat(p.qty),
}))
);
} catch (error) {
console.error("Alpaca positions error:", error);
return Response.json({ error: "Failed to fetch positions" }, { status: 500 });
}
}
+30
View File
@@ -0,0 +1,30 @@
import Alpaca from "@alpacahq/alpaca-trade-api";
const alpaca = new Alpaca({
keyId: process.env.ALPACA_API_KEY!,
secretKey: process.env.ALPACA_SECRET_KEY!,
baseUrl: process.env.ALPACA_BASE_URL || "https://paper-api.alpaca.markets",
retryOnError: false,
});
export async function loader({ params }: { params: { ticker: string } }) {
const ticker = params.ticker?.toUpperCase();
if (!ticker) {
return Response.json({ error: "Ticker is required" }, { status: 400 });
}
try {
// Use latest trade instead of quote for real transaction price
const trade = await alpaca.getLatestTrade(ticker);
// trade has: Price, Size, Exchange, Timestamp, etc.
const price = (trade as { Price?: number }).Price || 0;
return Response.json({
ticker,
price,
timestamp: (trade as { Timestamp?: string }).Timestamp,
});
} catch (error) {
console.error("Alpaca trade error:", error);
return Response.json({ error: "Failed to fetch trade" }, { status: 500 });
}
}