fix: backfill symbol_demand for sidebar-added symbols + analyst ratings schema fix

- Add await ctx.cache.subscribe() to addSymbol mutation so symbols
  added via the sidebar get registered in symbol_demand and yfinance
  jobs are queued immediately
- Backfill PEP, WYNN, STZ, CELH into symbol_demand + adapter_queue
- Upgrade yahoo-finance2 3.15.3 -> 3.15.4 and pass validateResult:false
  to quoteSummary() to handle Yahoo schema drift
- Add error detail logging for analyst ratings schema failures
- Update .gitignore with common ignores
This commit is contained in:
Investor Flow Build
2026-07-23 18:02:24 -04:00
parent 5ef2b2f060
commit e262187c3c
204 changed files with 25014 additions and 2934 deletions
+22 -1
View File
@@ -7,7 +7,9 @@ import { createCacheRepository, type SourceKind } from './cache/CacheRepository.
import { YFinanceAdapter } from './adapters/YFinanceAdapter.ts';
import { SecFetchAdapter } from './adapters/SecFetchAdapter.ts';
import { SecLintAdapter } from './adapters/SecLintAdapter.ts';
import { XCookieAdapter } from './adapters/XCookieAdapter.ts';
import type { SourceFetch } from './adapters/SourceAdapter.ts';
import cryptoMod from './lib/crypto.ts';
import { AdapterQueue } from './queue/AdapterQueue.ts';
import { makeCreateContext } from './trpc/context.ts';
import { appRouter } from './trpc/router.ts';
@@ -20,6 +22,25 @@ const adapters = new Map<SourceKind, SourceFetch>([
['sec-lint-holders' as const, new SecLintAdapter(() => database, 'sec-lint-holders') as unknown as SourceFetch],
['sec-lint-insiders' as const, new SecLintAdapter(() => database, 'sec-lint-insiders') as unknown as SourceFetch],
]);
// Load X credentials at startup and register XCookieAdapter if available.
let xAdapter: XCookieAdapter | null = null;
(function initXAdapter() {
const row = database.prepare('SELECT ct0_enc, auth_token_enc FROM x_credentials WHERE id=?').get('singleton') as { ct0_enc?: string; auth_token_enc?: string } | undefined;
if (!row?.ct0_enc || !row?.auth_token_enc) return;
let creds: { ct0: string; auth_token: string };
try { creds = { ct0: cryptoMod.decrypt(row.ct0_enc), auth_token: cryptoMod.decrypt(row.auth_token_enc) }; }
catch { return; }
const updateHealth = (status: string, err?: string | null) => {
database.prepare(
`INSERT INTO x_credentials (id, healthy, last_error, updated_at) VALUES ('singleton', ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET healthy=excluded.healthy, last_error=excluded.last_error, updated_at=excluded.updated_at`
).run(status === 'healthy' ? 1 : 0, err ?? null, new Date().toISOString());
};
xAdapter = new XCookieAdapter(creds, (health) => updateHealth(health.sourceStatus, health.lastError), database);
adapters.set('x' as const, xAdapter as unknown as SourceFetch);
console.log('[investor-flow] X adapter registered (credentials configured)');
})();
const queue = new AdapterQueue({ db: database, adapters });
const cache = createCacheRepository({ db: database, scheduler: queue });
queue.cache = cache; // break the cache<->scheduler cycle
@@ -32,7 +53,7 @@ queue.seedDefaultSchedules();
const recovered = database.prepare("UPDATE adapter_queue SET status='pending', error=NULL, retry_count=0 WHERE status='in_flight'").run();
if (Number(recovered.changes) > 0) console.log(`[investor-flow] recovered ${recovered.changes} interrupted in_flight jobs`);
const createContext = makeCreateContext({ db: database, cache, queue });
const createContext = makeCreateContext({ db: database, cache, queue, xAdapter });
// Background drain: stale-while-revalidate refreshes are queued by CacheRepository.get;
// this loop drains them (fetch via adapter -> write to cache), deduped + backed off.