77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import { DatabaseConnection } from '../shared/db/index';
|
|
import { QueryBuilder } from '../shared/utils/QueryBuilder';
|
|
import type { NewsArticleRow } from '../shared/types';
|
|
|
|
/**
|
|
* Persistence for the free-tier news pipeline (FREE-DATA-STACK §3).
|
|
* Pure data access — all filtering/dedupe decisions live in NewsPipeline.
|
|
*/
|
|
export class NewsRepository {
|
|
constructor(private readonly db: DatabaseConnection) {}
|
|
|
|
/** Returns true if the row was inserted (false = duplicate url_hash). */
|
|
insertArticle(a: {
|
|
urlHash: string;
|
|
titleHash: string;
|
|
tickers: string[];
|
|
headline: string;
|
|
body: string | null;
|
|
source: string;
|
|
catalyst: string | null;
|
|
url: string;
|
|
publishedAt: string;
|
|
}): boolean {
|
|
const qb = new QueryBuilder('NEWS_QUERIES.INSERT_ARTICLE', [
|
|
a.urlHash,
|
|
a.titleHash,
|
|
JSON.stringify(a.tickers),
|
|
a.headline,
|
|
a.body,
|
|
a.source,
|
|
a.catalyst,
|
|
a.url,
|
|
a.publishedAt,
|
|
new Date().toISOString(),
|
|
]);
|
|
return this.db.run(qb) > 0;
|
|
}
|
|
|
|
titleSeenSince(titleHash: string, sinceIso: string): boolean {
|
|
const qb = new QueryBuilder('NEWS_QUERIES.TITLE_SEEN_SINCE', [titleHash, sinceIso]);
|
|
return this.db.get(qb) != null;
|
|
}
|
|
|
|
linkTicker(ticker: string, day: string, urlHash: string): void {
|
|
const qb = new QueryBuilder('NEWS_QUERIES.INSERT_CATALYST_LINK', [ticker, day, urlHash]);
|
|
this.db.run(qb);
|
|
}
|
|
|
|
countTickerDay(ticker: string, day: string): number {
|
|
const qb = new QueryBuilder('NEWS_QUERIES.COUNT_TICKER_DAY', [ticker, day]);
|
|
return this.db.get<{ n: number }>(qb)?.n ?? 0;
|
|
}
|
|
|
|
newsForTicker(ticker: string, sinceDay: string): NewsArticleRow[] {
|
|
const qb = new QueryBuilder('NEWS_QUERIES.SELECT_TICKER_NEWS', [
|
|
ticker.toUpperCase(),
|
|
sinceDay,
|
|
]);
|
|
return this.db.all<NewsArticleRow>(qb);
|
|
}
|
|
|
|
recent(limit: number): NewsArticleRow[] {
|
|
const qb = new QueryBuilder('NEWS_QUERIES.SELECT_RECENT', [limit]);
|
|
return this.db.all<NewsArticleRow>(qb);
|
|
}
|
|
|
|
/** Retention: null out bodies older than cutoff. Returns rows changed. */
|
|
purgeBodiesBefore(cutoffIso: string): number {
|
|
return this.db.run(new QueryBuilder('NEWS_QUERIES.PURGE_BODIES_BEFORE', [cutoffIso]));
|
|
}
|
|
|
|
/** Retention: delete old rows no ticker references. Returns rows deleted. */
|
|
deleteUnreferencedBefore(cutoffIso: string): number {
|
|
return this.db.run(new QueryBuilder('NEWS_QUERIES.DELETE_UNREFERENCED_BEFORE', [cutoffIso]));
|
|
}
|
|
}
|