phase-10.5: market screener ui enhancements
This commit is contained in:
@@ -1,34 +1,33 @@
|
||||
import { DatabaseConnection } from '../db/index';
|
||||
import { QueryBuilder } from '../utils/QueryBuilder';
|
||||
import { sanitizeTicker, sanitizeNumber } from '../utils/sanitizer';
|
||||
import type { PortfolioData, PortfolioHolding, HoldingRow } from '../types';
|
||||
import { DatabaseConnection } from '../db/index.js';
|
||||
import { QueryBuilder } from '../utils/QueryBuilder.js';
|
||||
import { sanitizeTicker, sanitizeNumber } from '../utils/sanitizer.js';
|
||||
import type { PortfolioData, PortfolioHolding, HoldingRow } from '../types/index.js';
|
||||
|
||||
export class PortfolioRepository {
|
||||
constructor(private readonly db: DatabaseConnection) {}
|
||||
|
||||
/**
|
||||
* Check if portfolio has any holdings.
|
||||
* Check if a user has any holdings.
|
||||
*/
|
||||
exists(): boolean {
|
||||
const qb = new QueryBuilder('HOLDINGS_QUERIES.EXISTS');
|
||||
exists(userId: string): boolean {
|
||||
const qb = new QueryBuilder('HOLDINGS_QUERIES.EXISTS', [userId]);
|
||||
const row = this.db.get<{ n: number }>(qb);
|
||||
return row ? row.n > 0 : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all holdings.
|
||||
* Read all holdings for a user.
|
||||
*/
|
||||
read(): PortfolioData {
|
||||
const qb = new QueryBuilder('HOLDINGS_QUERIES.SELECT_ALL');
|
||||
read(userId: string): PortfolioData {
|
||||
const qb = new QueryBuilder('HOLDINGS_QUERIES.SELECT_ALL', [userId]);
|
||||
const rows = this.db.all<HoldingRow>(qb);
|
||||
return { holdings: rows.map(PortfolioRepository.toHolding) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update a holding (UPSERT).
|
||||
* Insert or update a holding scoped to a user (UPSERT).
|
||||
*/
|
||||
upsert(entry: PortfolioHolding): PortfolioHolding {
|
||||
// Sanitize inputs
|
||||
upsert(entry: PortfolioHolding, userId: string): PortfolioHolding {
|
||||
const ticker = sanitizeTicker(entry.ticker);
|
||||
const shares = sanitizeNumber(entry.shares, 'shares', { min: 0 });
|
||||
const costBasis = sanitizeNumber(entry.costBasis ?? 0, 'costBasis', { min: 0 });
|
||||
@@ -41,6 +40,7 @@ export class PortfolioRepository {
|
||||
costBasis,
|
||||
type,
|
||||
source,
|
||||
userId,
|
||||
]);
|
||||
|
||||
this.db.run(qb);
|
||||
@@ -48,20 +48,15 @@ export class PortfolioRepository {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a holding by ticker.
|
||||
* Delete a holding by ticker for a specific user.
|
||||
*/
|
||||
remove(ticker: string): boolean {
|
||||
// Sanitize input
|
||||
remove(ticker: string, userId: string): boolean {
|
||||
const sanitizedTicker = sanitizeTicker(ticker);
|
||||
const qb = new QueryBuilder('HOLDINGS_QUERIES.DELETE_BY_TICKER', [sanitizedTicker]);
|
||||
|
||||
const qb = new QueryBuilder('HOLDINGS_QUERIES.DELETE_BY_TICKER', [sanitizedTicker, userId]);
|
||||
const changes = this.db.run(qb);
|
||||
return changes > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert database row to domain object.
|
||||
*/
|
||||
private static toHolding(row: HoldingRow): PortfolioHolding {
|
||||
return {
|
||||
ticker: row.ticker,
|
||||
|
||||
Reference in New Issue
Block a user