64 lines
1.7 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|