Files
market_screener/server/repositories/PortfolioRepository.ts
T
2026-06-05 23:34:25 -04:00

64 lines
1.7 KiB
TypeScript

import type { Db } from '../db/index';
import type { PortfolioData, PortfolioHolding } from '../types';
interface HoldingRow {
ticker: string;
shares: number;
cost_basis: number;
type: string;
source: string;
}
export class PortfolioRepository {
constructor(private readonly db: Db) {}
exists(): boolean {
const row = this.db.prepare('SELECT COUNT(*) AS n FROM holdings').get() as { n: number };
return row.n > 0;
}
read(): PortfolioData {
const rows = this.db.prepare('SELECT * FROM holdings ORDER BY ticker').all() as HoldingRow[];
return { holdings: rows.map(PortfolioRepository.toHolding) };
}
upsert(entry: PortfolioHolding): PortfolioHolding {
const ticker = entry.ticker.toUpperCase().trim();
this.db
.prepare(
`INSERT INTO holdings (ticker, shares, cost_basis, type, source)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(ticker) DO UPDATE SET
shares = excluded.shares,
cost_basis = excluded.cost_basis,
type = excluded.type,
source = excluded.source`,
)
.run(
ticker,
entry.shares,
entry.costBasis ?? 0,
entry.type ?? 'stock',
entry.source ?? 'Manual',
);
return { ...entry, ticker };
}
remove(ticker: string): boolean {
const result = this.db
.prepare('DELETE FROM holdings WHERE ticker = ?')
.run(ticker.toUpperCase());
return result.changes > 0;
}
private static toHolding(row: HoldingRow): PortfolioHolding {
return {
ticker: row.ticker,
shares: row.shares,
costBasis: row.cost_basis,
type: row.type as PortfolioHolding['type'],
source: row.source,
};
}
}