jobhunt-platform/apps/web/src/api/index.ts

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
}