import { Project, Package, Tag, TagDetail, Artifact, ArtifactDetail, UploadResponse, PaginatedResponse, ListParams, TagListParams, PackageListParams, ArtifactListParams, ProjectListParams, GlobalSearchResponse, Stats, DeduplicationStats, TimelineStats, CrossProjectStats, User, LoginCredentials, APIKey, APIKeyCreate, APIKeyCreateResponse, AdminUser, UserCreate, UserUpdate, AccessPermission, AccessPermissionCreate, AccessPermissionUpdate, AccessLevel, OIDCConfig, OIDCConfigUpdate, OIDCStatus, PackageVersion, ArtifactDependenciesResponse, ReverseDependenciesResponse, DependencyResolutionResponse, TeamDetail, TeamMember, TeamCreate, TeamUpdate, TeamMemberCreate, TeamMemberUpdate, UpstreamSource, UpstreamSourceCreate, UpstreamSourceUpdate, UpstreamSourceTestResult, } from './types'; const API_BASE = '/api/v1'; // Custom error classes for better error handling export class ApiError extends Error { status: number; constructor(message: string, status: number) { super(message); this.name = 'ApiError'; this.status = status; } } export class UnauthorizedError extends ApiError { constructor(message: string = 'Not authenticated') { super(message, 401); this.name = 'UnauthorizedError'; } } export class ForbiddenError extends ApiError { constructor(message: string = 'Access denied') { super(message, 403); this.name = 'ForbiddenError'; } } async function handleResponse(response: Response): Promise { if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); // Handle detail as string or object (backend may return structured errors) let message: string; if (typeof error.detail === 'object') { message = JSON.stringify(error.detail); } else { message = error.detail || `HTTP ${response.status}`; } if (response.status === 401) { throw new UnauthorizedError(message); } if (response.status === 403) { throw new ForbiddenError(message); } throw new ApiError(message, response.status); } return response.json(); } function buildQueryString(params: Record): string { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { searchParams.append(key, String(value)); } }); const query = searchParams.toString(); return query ? `?${query}` : ''; } // Auth API export async function login(credentials: LoginCredentials): Promise { const response = await fetch(`${API_BASE}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials), credentials: 'include', }); return handleResponse(response); } export async function logout(): Promise { const response = await fetch(`${API_BASE}/auth/logout`, { method: 'POST', credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new Error(error.detail || `HTTP ${response.status}`); } } export async function changePassword(currentPassword: string, newPassword: string): Promise { const response = await fetch(`${API_BASE}/auth/change-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }), credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new Error(error.detail || `HTTP ${response.status}`); } } export async function getCurrentUser(): Promise { try { const response = await fetch(`${API_BASE}/auth/me`, { credentials: 'include', }); if (response.status === 401) { return null; } return handleResponse(response); } catch { return null; } } // Global Search API export async function globalSearch(query: string, limit: number = 5): Promise { const params = buildQueryString({ q: query, limit }); const response = await fetch(`${API_BASE}/search${params}`); return handleResponse(response); } // Project API export async function listProjects(params: ProjectListParams = {}): Promise> { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/projects${query}`); return handleResponse>(response); } export async function listProjectsSimple(params: ListParams = {}): Promise { const data = await listProjects(params); return data.items; } export async function createProject(data: { name: string; description?: string; is_public?: boolean; team_id?: string }): Promise { const response = await fetch(`${API_BASE}/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); return handleResponse(response); } export async function getProject(name: string): Promise { const response = await fetch(`${API_BASE}/projects/${name}`); return handleResponse(response); } export async function updateProject( projectName: string, data: { description?: string; is_public?: boolean } ): Promise { const response = await fetch(`${API_BASE}/projects/${projectName}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function deleteProject(projectName: string): Promise { const response = await fetch(`${API_BASE}/projects/${projectName}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new Error(error.detail || `HTTP ${response.status}`); } } // Package API export async function listPackages(projectName: string, params: PackageListParams = {}): Promise> { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/project/${projectName}/packages${query}`); return handleResponse>(response); } export async function listPackagesSimple(projectName: string, params: PackageListParams = {}): Promise { const data = await listPackages(projectName, params); return data.items; } export async function getPackage(projectName: string, packageName: string): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/packages/${packageName}`); return handleResponse(response); } export async function createPackage(projectName: string, data: { name: string; description?: string }): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/packages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); return handleResponse(response); } // Tag API export async function listTags(projectName: string, packageName: string, params: TagListParams = {}): Promise> { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/tags${query}`); return handleResponse>(response); } export async function listTagsSimple(projectName: string, packageName: string, params: TagListParams = {}): Promise { const data = await listTags(projectName, packageName, params); return data.items; } export async function getTag(projectName: string, packageName: string, tagName: string): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/tags/${tagName}`); return handleResponse(response); } export async function createTag(projectName: string, packageName: string, data: { name: string; artifact_id: string }): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/tags`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); return handleResponse(response); } // Artifact API export async function getArtifact(artifactId: string): Promise { const response = await fetch(`${API_BASE}/artifact/${artifactId}`); return handleResponse(response); } export async function listPackageArtifacts( projectName: string, packageName: string, params: ArtifactListParams = {} ): Promise> { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/artifacts${query}`); return handleResponse>(response); } // Upload export async function uploadArtifact( projectName: string, packageName: string, file: File, tag?: string, version?: string ): Promise { const formData = new FormData(); formData.append('file', file); if (tag) { formData.append('tag', tag); } if (version) { formData.append('version', version); } const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/upload`, { method: 'POST', body: formData, }); return handleResponse(response); } // Download URL export function getDownloadUrl(projectName: string, packageName: string, ref: string): string { return `${API_BASE}/project/${projectName}/${packageName}/+/${ref}`; } // Stats API export async function getStats(): Promise { const response = await fetch(`${API_BASE}/stats`); return handleResponse(response); } export async function getDeduplicationStats(): Promise { const response = await fetch(`${API_BASE}/stats/deduplication`); return handleResponse(response); } export async function getTimelineStats( period: 'day' | 'week' | 'month' = 'day', fromDate?: string, toDate?: string ): Promise { const params = buildQueryString({ period, from_date: fromDate, to_date: toDate }); const response = await fetch(`${API_BASE}/stats/timeline${params}`); return handleResponse(response); } export async function getCrossProjectStats(): Promise { const response = await fetch(`${API_BASE}/stats/cross-project`); return handleResponse(response); } export async function listAPIKeys(): Promise { const response = await fetch(`${API_BASE}/auth/keys`, { credentials: 'include', }); return handleResponse(response); } export async function createAPIKey(data: APIKeyCreate): Promise { const response = await fetch(`${API_BASE}/auth/keys`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function deleteAPIKey(id: string): Promise { const response = await fetch(`${API_BASE}/auth/keys/${id}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new Error(error.detail || `HTTP ${response.status}`); } } // Admin User Management API export async function listUsers(): Promise { const response = await fetch(`${API_BASE}/admin/users`, { credentials: 'include', }); return handleResponse(response); } export async function createUser(data: UserCreate): Promise { const response = await fetch(`${API_BASE}/admin/users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function updateUser(username: string, data: UserUpdate): Promise { const response = await fetch(`${API_BASE}/admin/users/${username}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function resetUserPassword(username: string, newPassword: string): Promise { const response = await fetch(`${API_BASE}/admin/users/${username}/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ new_password: newPassword }), credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new Error(error.detail || `HTTP ${response.status}`); } } // Access Permission API export interface MyAccessResponse { project: string; access_level: AccessLevel | null; is_owner: boolean; } export async function getMyProjectAccess(projectName: string): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/my-access`, { credentials: 'include', }); return handleResponse(response); } export async function listProjectPermissions(projectName: string): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/permissions`, { credentials: 'include', }); return handleResponse(response); } export async function grantProjectAccess( projectName: string, data: AccessPermissionCreate ): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/permissions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function updateProjectAccess( projectName: string, username: string, data: AccessPermissionUpdate ): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/permissions/${username}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function revokeProjectAccess(projectName: string, username: string): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/permissions/${username}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new Error(error.detail || `HTTP ${response.status}`); } } // OIDC API export async function getOIDCStatus(): Promise { const response = await fetch(`${API_BASE}/auth/oidc/status`); return handleResponse(response); } export async function getOIDCConfig(): Promise { const response = await fetch(`${API_BASE}/auth/oidc/config`, { credentials: 'include', }); return handleResponse(response); } export async function updateOIDCConfig(data: OIDCConfigUpdate): Promise { const response = await fetch(`${API_BASE}/auth/oidc/config`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export function getOIDCLoginUrl(returnTo?: string): string { const params = new URLSearchParams(); if (returnTo) { params.set('return_to', returnTo); } const query = params.toString(); return `${API_BASE}/auth/oidc/login${query ? `?${query}` : ''}`; } // Version API export async function listVersions( projectName: string, packageName: string, params: ListParams = {} ): Promise> { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/versions${query}`); return handleResponse>(response); } export async function getVersion( projectName: string, packageName: string, version: string ): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/versions/${version}`); return handleResponse(response); } export async function deleteVersion( projectName: string, packageName: string, version: string ): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/versions/${version}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new Error(error.detail || `HTTP ${response.status}`); } } // Dependency API export async function getArtifactDependencies(artifactId: string): Promise { const response = await fetch(`${API_BASE}/artifact/${artifactId}/dependencies`); return handleResponse(response); } export async function getDependenciesByRef( projectName: string, packageName: string, ref: string ): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/+/${ref}/dependencies`); return handleResponse(response); } export async function getReverseDependencies( projectName: string, packageName: string, params: { page?: number; limit?: number } = {} ): Promise { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/reverse-dependencies${query}`); return handleResponse(response); } export async function resolveDependencies( projectName: string, packageName: string, ref: string ): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/+/${ref}/resolve`); return handleResponse(response); } export async function getEnsureFile( projectName: string, packageName: string, ref: string ): Promise { const response = await fetch(`${API_BASE}/project/${projectName}/${packageName}/+/${ref}/ensure`); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new ApiError(error.detail || `HTTP ${response.status}`, response.status); } return response.text(); } // Team API export async function listTeams(params: ListParams = {}): Promise> { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/teams${query}`, { credentials: 'include', }); return handleResponse>(response); } export async function createTeam(data: TeamCreate): Promise { const response = await fetch(`${API_BASE}/teams`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function getTeam(slug: string): Promise { const response = await fetch(`${API_BASE}/teams/${slug}`, { credentials: 'include', }); return handleResponse(response); } export async function updateTeam(slug: string, data: TeamUpdate): Promise { const response = await fetch(`${API_BASE}/teams/${slug}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function deleteTeam(slug: string): Promise { const response = await fetch(`${API_BASE}/teams/${slug}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new ApiError(error.detail || `HTTP ${response.status}`, response.status); } } export async function listTeamMembers(slug: string): Promise { const response = await fetch(`${API_BASE}/teams/${slug}/members`, { credentials: 'include', }); return handleResponse(response); } export async function addTeamMember(slug: string, data: TeamMemberCreate): Promise { const response = await fetch(`${API_BASE}/teams/${slug}/members`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function updateTeamMember( slug: string, username: string, data: TeamMemberUpdate ): Promise { const response = await fetch(`${API_BASE}/teams/${slug}/members/${username}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function removeTeamMember(slug: string, username: string): Promise { const response = await fetch(`${API_BASE}/teams/${slug}/members/${username}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new ApiError(error.detail || `HTTP ${response.status}`, response.status); } } export async function listTeamProjects( slug: string, params: ProjectListParams = {} ): Promise> { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/teams/${slug}/projects${query}`, { credentials: 'include', }); return handleResponse>(response); } // User search (for autocomplete) export interface UserSearchResult { id: string; username: string; is_admin: boolean; } export async function searchUsers(query: string, limit: number = 10): Promise { const response = await fetch(`${API_BASE}/users/search?q=${encodeURIComponent(query)}&limit=${limit}`, { credentials: 'include', }); return handleResponse(response); } // Upstream Sources Admin API export interface UpstreamSourceListParams { enabled?: boolean; source_type?: string; } export async function listUpstreamSources(params: UpstreamSourceListParams = {}): Promise { const query = buildQueryString(params as Record); const response = await fetch(`${API_BASE}/admin/upstream-sources${query}`, { credentials: 'include', }); return handleResponse(response); } export async function createUpstreamSource(data: UpstreamSourceCreate): Promise { const response = await fetch(`${API_BASE}/admin/upstream-sources`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function getUpstreamSource(id: string): Promise { const response = await fetch(`${API_BASE}/admin/upstream-sources/${id}`, { credentials: 'include', }); return handleResponse(response); } export async function updateUpstreamSource(id: string, data: UpstreamSourceUpdate): Promise { const response = await fetch(`${API_BASE}/admin/upstream-sources/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), credentials: 'include', }); return handleResponse(response); } export async function deleteUpstreamSource(id: string): Promise { const response = await fetch(`${API_BASE}/admin/upstream-sources/${id}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })); throw new ApiError(error.detail || `HTTP ${response.status}`, response.status); } } export async function testUpstreamSource(id: string): Promise { const response = await fetch(`${API_BASE}/admin/upstream-sources/${id}/test`, { method: 'POST', credentials: 'include', }); return handleResponse(response); }