Implement fiscal-style research MVP flows
Some checks failed
PR Checks / typecheck-and-build (push) Has been cancelled
Some checks failed
PR Checks / typecheck-and-build (push) Has been cancelled
This commit is contained in:
109
lib/api.ts
109
lib/api.ts
@@ -4,12 +4,16 @@ import type {
|
||||
CompanyAiReportDetail,
|
||||
CompanyAnalysis,
|
||||
CompanyFinancialStatementsResponse,
|
||||
CoveragePriority,
|
||||
CoverageStatus,
|
||||
Filing,
|
||||
Holding,
|
||||
FinancialHistoryWindow,
|
||||
FinancialStatementKind,
|
||||
PortfolioInsight,
|
||||
PortfolioSummary,
|
||||
ResearchJournalEntry,
|
||||
ResearchJournalEntryType,
|
||||
Task,
|
||||
TaskStatus,
|
||||
TaskTimeline,
|
||||
@@ -99,6 +103,36 @@ async function unwrapData<T>(result: TreatyResult, fallback: string) {
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
async function requestJson<T>(input: {
|
||||
path: string;
|
||||
method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
||||
body?: unknown;
|
||||
}, fallback: string) {
|
||||
const response = await fetch(`${API_BASE}${input.path}`, {
|
||||
method: input.method ?? 'GET',
|
||||
credentials: 'include',
|
||||
cache: 'no-store',
|
||||
headers: input.body === undefined ? undefined : {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: input.body === undefined ? undefined : JSON.stringify(input.body)
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
throw new ApiError(
|
||||
extractErrorMessage({ value: payload }, fallback),
|
||||
response.status
|
||||
);
|
||||
}
|
||||
|
||||
if (payload === null || payload === undefined) {
|
||||
throw new ApiError(fallback, response.status);
|
||||
}
|
||||
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
export async function getMe() {
|
||||
const result = await client.api.me.get();
|
||||
return await unwrapData<{ user: User }>(result, 'Unable to fetch session');
|
||||
@@ -115,16 +149,80 @@ export async function upsertWatchlistItem(input: {
|
||||
sector?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
status?: CoverageStatus;
|
||||
priority?: CoveragePriority;
|
||||
lastReviewedAt?: string;
|
||||
}) {
|
||||
const result = await client.api.watchlist.post(input);
|
||||
return await unwrapData<{ item: WatchlistItem }>(result, 'Unable to save watchlist item');
|
||||
}
|
||||
|
||||
export async function updateWatchlistItem(id: number, input: {
|
||||
companyName?: string;
|
||||
sector?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
status?: CoverageStatus;
|
||||
priority?: CoveragePriority;
|
||||
lastReviewedAt?: string;
|
||||
}) {
|
||||
return await requestJson<{ item: WatchlistItem; statusChangeJournalCreated: boolean }>({
|
||||
path: `/api/watchlist/${id}`,
|
||||
method: 'PATCH',
|
||||
body: input
|
||||
}, 'Unable to update watchlist item');
|
||||
}
|
||||
|
||||
export async function deleteWatchlistItem(id: number) {
|
||||
const result = await client.api.watchlist[id].delete();
|
||||
return await unwrapData<{ success: boolean }>(result, 'Unable to delete watchlist item');
|
||||
}
|
||||
|
||||
export async function listResearchJournal(ticker: string) {
|
||||
const result = await client.api.research.journal.get({
|
||||
$query: {
|
||||
ticker: ticker.trim().toUpperCase()
|
||||
}
|
||||
});
|
||||
|
||||
return await unwrapData<{ entries: ResearchJournalEntry[] }>(result, 'Unable to fetch research journal');
|
||||
}
|
||||
|
||||
export async function createResearchJournalEntry(input: {
|
||||
ticker: string;
|
||||
accessionNumber?: string;
|
||||
entryType: ResearchJournalEntryType;
|
||||
title?: string;
|
||||
bodyMarkdown: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}) {
|
||||
return await requestJson<{ entry: ResearchJournalEntry }>({
|
||||
path: '/api/research/journal',
|
||||
method: 'POST',
|
||||
body: {
|
||||
...input,
|
||||
ticker: input.ticker.trim().toUpperCase()
|
||||
}
|
||||
}, 'Unable to create journal entry');
|
||||
}
|
||||
|
||||
export async function updateResearchJournalEntry(id: number, input: {
|
||||
title?: string;
|
||||
bodyMarkdown?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}) {
|
||||
return await requestJson<{ entry: ResearchJournalEntry }>({
|
||||
path: `/api/research/journal/${id}`,
|
||||
method: 'PATCH',
|
||||
body: input
|
||||
}, 'Unable to update journal entry');
|
||||
}
|
||||
|
||||
export async function deleteResearchJournalEntry(id: number) {
|
||||
const result = await client.api.research.journal[id].delete();
|
||||
return await unwrapData<{ success: boolean }>(result, 'Unable to delete journal entry');
|
||||
}
|
||||
|
||||
export async function listHoldings() {
|
||||
const result = await client.api.portfolio.holdings.get();
|
||||
return await unwrapData<{ holdings: Holding[] }>(result, 'Unable to fetch holdings');
|
||||
@@ -140,11 +238,22 @@ export async function upsertHolding(input: {
|
||||
shares: number;
|
||||
avgCost: number;
|
||||
currentPrice?: number;
|
||||
companyName?: string;
|
||||
}) {
|
||||
const result = await client.api.portfolio.holdings.post(input);
|
||||
return await unwrapData<{ holding: Holding }>(result, 'Unable to save holding');
|
||||
}
|
||||
|
||||
export async function updateHolding(id: number, input: {
|
||||
shares?: number;
|
||||
avgCost?: number;
|
||||
currentPrice?: number;
|
||||
companyName?: string;
|
||||
}) {
|
||||
const result = await client.api.portfolio.holdings[id].patch(input);
|
||||
return await unwrapData<{ holding: Holding }>(result, 'Unable to update holding');
|
||||
}
|
||||
|
||||
export async function deleteHolding(id: number) {
|
||||
const result = await client.api.portfolio.holdings[id].delete();
|
||||
return await unwrapData<{ success: boolean }>(result, 'Unable to delete holding');
|
||||
|
||||
Reference in New Issue
Block a user