238 lines
No EOL
6.6 KiB
TypeScript
238 lines
No EOL
6.6 KiB
TypeScript
// API client module: thin typed fetch wrappers over the contract endpoints.
|
|
// Base URL from VITE_API_BASE, defaulting to http://localhost:8000/api.
|
|
|
|
import type {
|
|
AiAssistResponse,
|
|
Application,
|
|
Approval,
|
|
Artifact,
|
|
BatchScoringResponse,
|
|
CoverLetterResponse,
|
|
CritiqueComment,
|
|
CvImportConfirmResponse,
|
|
CvImportResponse,
|
|
CvSection,
|
|
DemoSeedResponse,
|
|
InterviewPrepResponse,
|
|
JobPosting,
|
|
PostingsFetchResponse,
|
|
Profile,
|
|
RenderCvResponse,
|
|
ScoreResponse,
|
|
TaskRun,
|
|
TodayResponse
|
|
} from '@/types'
|
|
|
|
const API_BASE: string =
|
|
import.meta.env.VITE_API_BASE ?? 'http://localhost:8000/api'
|
|
|
|
export class HttpError extends Error {
|
|
status: number
|
|
body: unknown
|
|
constructor(status: number, body: unknown, message?: string) {
|
|
super(message ?? `HTTP ${status}`)
|
|
this.status = status
|
|
this.body = body
|
|
}
|
|
}
|
|
|
|
async function request<T>(
|
|
path: string,
|
|
options: RequestInit = {}
|
|
): Promise<T> {
|
|
const res = await fetch(`${API_BASE}${path}`, {
|
|
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
...options
|
|
})
|
|
if (!res.ok) {
|
|
let body: unknown = null
|
|
try {
|
|
body = await res.json()
|
|
} catch {
|
|
// non-JSON error body
|
|
}
|
|
throw new HttpError(res.status, body, `HTTP ${res.status}`)
|
|
}
|
|
return res.json() as Promise<T>
|
|
}
|
|
|
|
// --- Profile & CV ---
|
|
|
|
export function getProfile(): Promise<Profile> {
|
|
return request<Profile>('/profile')
|
|
}
|
|
|
|
export function updateProfile(data: Partial<Profile>): Promise<Profile> {
|
|
return request<Profile>('/profile', { method: 'PUT', body: JSON.stringify(data) })
|
|
}
|
|
|
|
export function getSections(): Promise<CvSection[]> {
|
|
return request<CvSection[]>('/profile/sections')
|
|
}
|
|
|
|
export function createSection(data: Partial<CvSection>): Promise<CvSection> {
|
|
return request<CvSection>('/profile/sections', { method: 'POST', body: JSON.stringify(data) })
|
|
}
|
|
|
|
export function updateSection(id: string, data: Partial<CvSection>): Promise<CvSection> {
|
|
return request<CvSection>(`/profile/sections/${id}`, { method: 'PUT', body: JSON.stringify(data) })
|
|
}
|
|
|
|
export function deleteSection(id: string): Promise<void> {
|
|
return request<void>(`/profile/sections/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export function aiAssist(sectionId: string, instruction: string): Promise<AiAssistResponse> {
|
|
return request<AiAssistResponse>(`/profile/sections/${sectionId}/ai-assist`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ instruction })
|
|
})
|
|
}
|
|
|
|
export function renderCv(): Promise<RenderCvResponse> {
|
|
return request<RenderCvResponse>('/profile/render-cv', { method: 'POST' })
|
|
}
|
|
|
|
// --- Job postings ---
|
|
|
|
export function getPostings(): Promise<JobPosting[]> {
|
|
return request<JobPosting[]>('/postings')
|
|
}
|
|
|
|
export function createPosting(url: string): Promise<JobPosting> {
|
|
return request<JobPosting>('/postings', { method: 'POST', body: JSON.stringify({ url }) })
|
|
}
|
|
|
|
export function scorePosting(postingId: string): Promise<ScoreResponse> {
|
|
return request<ScoreResponse>(`/postings/${postingId}/score`, { method: 'POST' })
|
|
}
|
|
|
|
// --- Applications ---
|
|
|
|
export function getApplications(): Promise<Application[]> {
|
|
return request<Application[]>('/applications')
|
|
}
|
|
|
|
export function transitionApplication(id: string, to: string): Promise<Application> {
|
|
return request<Application>(`/applications/${id}/transition`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ to })
|
|
})
|
|
}
|
|
|
|
export function getArtifacts(applicationId: string): Promise<Artifact[]> {
|
|
return request<Artifact[]>(`/applications/${applicationId}/artifacts`)
|
|
}
|
|
|
|
export function createCoverLetter(
|
|
applicationId: string,
|
|
letterText: string
|
|
): Promise<CoverLetterResponse> {
|
|
return request<CoverLetterResponse>(`/applications/${applicationId}/artifacts/cover-letter`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ letter_text: letterText })
|
|
})
|
|
}
|
|
|
|
// --- Approval & outbox ---
|
|
|
|
export function createApproval(
|
|
applicationId: string,
|
|
action: string,
|
|
artifactId: string
|
|
): Promise<Approval> {
|
|
return request<Approval>(`/applications/${applicationId}/approvals`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ action, artifact_id: artifactId })
|
|
})
|
|
}
|
|
|
|
export function confirmApproval(approvalId: string): Promise<Approval> {
|
|
return request<Approval>(`/approvals/${approvalId}/confirm`, { method: 'POST' })
|
|
}
|
|
|
|
export function rejectApproval(approvalId: string): Promise<Approval> {
|
|
return request<Approval>(`/approvals/${approvalId}/reject`, { method: 'POST' })
|
|
}
|
|
|
|
export function outboxSend(approvalId: string, payload: Record<string, unknown>): Promise<unknown> {
|
|
return request<unknown>('/outbox/send', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ approval_id: approvalId, payload })
|
|
})
|
|
}
|
|
|
|
// --- Telemetry ---
|
|
|
|
export function getTelemetryTasks(): Promise<TaskRun[]> {
|
|
return request<TaskRun[]>('/telemetry/tasks')
|
|
}
|
|
|
|
// --- v1.0 additions (api-contract-v2.md) ---
|
|
|
|
export function importCv(filename: string, contentBase64: string): Promise<CvImportResponse> {
|
|
return request<CvImportResponse>('/cv/import', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ filename, content_base64: contentBase64 })
|
|
})
|
|
}
|
|
|
|
export function confirmCvImport(drafts: CvImportResponse['drafts']): Promise<CvImportConfirmResponse> {
|
|
return request<CvImportConfirmResponse>('/cv/import/confirm', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ drafts })
|
|
})
|
|
}
|
|
|
|
export function fetchPostings(query: string, region?: string): Promise<PostingsFetchResponse> {
|
|
const body: Record<string, string> = { query }
|
|
if (region) body.region = region
|
|
return request<PostingsFetchResponse>('/postings/fetch', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body)
|
|
})
|
|
}
|
|
|
|
export function batchScore(applicationIds: string[]): Promise<BatchScoringResponse> {
|
|
return request<BatchScoringResponse>('/scoring/batch', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ application_ids: applicationIds })
|
|
})
|
|
}
|
|
|
|
export function getToday(): Promise<TodayResponse> {
|
|
return request<TodayResponse>('/today')
|
|
}
|
|
|
|
export function interviewPrep(applicationId: string): Promise<InterviewPrepResponse> {
|
|
return request<InterviewPrepResponse>(`/applications/${applicationId}/interview-prep`, {
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function seedDemo(): Promise<DemoSeedResponse> {
|
|
return request<DemoSeedResponse>('/concierge/seed-demo', { method: 'POST' })
|
|
}
|
|
|
|
// Re-export types for convenience
|
|
export type {
|
|
AiAssistResponse,
|
|
Application,
|
|
Approval,
|
|
Artifact,
|
|
BatchScoringResponse,
|
|
CoverLetterResponse,
|
|
CritiqueComment,
|
|
CvImportConfirmResponse,
|
|
CvImportResponse,
|
|
CvSection,
|
|
DemoSeedResponse,
|
|
InterviewPrepResponse,
|
|
JobPosting,
|
|
PostingsFetchResponse,
|
|
Profile,
|
|
RenderCvResponse,
|
|
ScoreResponse,
|
|
TaskRun,
|
|
TodayResponse
|
|
} |