283 lines
No EOL
8.9 KiB
TypeScript
283 lines
No EOL
8.9 KiB
TypeScript
import axios from 'axios';
|
|
import type { AxiosInstance } from 'axios';
|
|
import type {
|
|
LoginRequest,
|
|
LoginResponse,
|
|
RefreshResponse,
|
|
MicrosoftLoginResponse,
|
|
Job,
|
|
JobCreateRequest,
|
|
JobListResponse,
|
|
JobDownloadsResponse,
|
|
VttContentResponse,
|
|
VttUpdateRequest,
|
|
AssetValidationResponse,
|
|
BulkDeleteRequest,
|
|
BulkDeleteResponse,
|
|
JobDeleteResponse,
|
|
User,
|
|
UserListResponse,
|
|
CreateUserRequest,
|
|
UpdateUserRequest,
|
|
ResetPasswordResponse,
|
|
AdminStatsResponse,
|
|
} from '../types/api';
|
|
|
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
|
|
|
|
class ApiClient {
|
|
private client: AxiosInstance;
|
|
private accessToken: string | null = null;
|
|
|
|
constructor() {
|
|
this.client = axios.create({
|
|
baseURL: `${API_BASE_URL}/api/v1`,
|
|
withCredentials: true,
|
|
timeout: 30000,
|
|
});
|
|
|
|
this.setupInterceptors();
|
|
}
|
|
|
|
private setupInterceptors() {
|
|
// Request interceptor to add auth token
|
|
this.client.interceptors.request.use(
|
|
(config) => {
|
|
if (this.accessToken) {
|
|
config.headers.Authorization = `Bearer ${this.accessToken}`;
|
|
}
|
|
return config;
|
|
},
|
|
(error) => Promise.reject(error)
|
|
);
|
|
|
|
// Response interceptor to handle token refresh
|
|
this.client.interceptors.response.use(
|
|
(response) => response,
|
|
async (error) => {
|
|
const originalRequest = error.config;
|
|
|
|
// Don't try to refresh if this is already the refresh endpoint
|
|
if (error.response?.status === 401 &&
|
|
!originalRequest._retry &&
|
|
originalRequest.url !== '/auth/refresh') {
|
|
originalRequest._retry = true;
|
|
|
|
try {
|
|
const refreshResponse = await this.client.post('/auth/refresh');
|
|
this.accessToken = refreshResponse.data.access_token;
|
|
|
|
// Retry original request with new token
|
|
originalRequest.headers.Authorization = `Bearer ${this.accessToken}`;
|
|
return this.client(originalRequest);
|
|
} catch (refreshError) {
|
|
// Refresh failed, clear token and update auth state
|
|
this.accessToken = null;
|
|
// Clear auth state in the store
|
|
const { useAuthStore } = await import('./auth');
|
|
useAuthStore.getState().logout();
|
|
return Promise.reject(refreshError);
|
|
}
|
|
}
|
|
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
}
|
|
|
|
setAccessToken(token: string) {
|
|
this.accessToken = token;
|
|
}
|
|
|
|
clearAccessToken() {
|
|
this.accessToken = null;
|
|
}
|
|
|
|
getAccessToken(): string | null {
|
|
return this.accessToken;
|
|
}
|
|
|
|
// Auth endpoints
|
|
async login(credentials: LoginRequest): Promise<LoginResponse> {
|
|
const response = await this.client.post('/auth/login', credentials);
|
|
this.setAccessToken(response.data.access_token);
|
|
return response.data;
|
|
}
|
|
|
|
async loginWithMicrosoft(idToken: string): Promise<MicrosoftLoginResponse> {
|
|
const response = await this.client.post('/auth/microsoft', { id_token: idToken });
|
|
this.setAccessToken(response.data.access_token);
|
|
return response.data;
|
|
}
|
|
|
|
async refresh(): Promise<RefreshResponse> {
|
|
const response = await this.client.post('/auth/refresh');
|
|
this.setAccessToken(response.data.access_token);
|
|
return response.data;
|
|
}
|
|
|
|
async logout(): Promise<void> {
|
|
await this.client.post('/auth/logout');
|
|
this.clearAccessToken();
|
|
}
|
|
|
|
// Job endpoints
|
|
async getJobs(filters?: { status?: string; mine?: boolean; page?: number; size?: number }): Promise<JobListResponse> {
|
|
const params = new URLSearchParams();
|
|
if (filters?.status) params.append('status', filters.status);
|
|
if (filters?.mine) params.append('mine', 'true');
|
|
if (filters?.page) params.append('page', filters.page.toString());
|
|
if (filters?.size) params.append('size', filters.size.toString());
|
|
|
|
const response = await this.client.get(`/jobs?${params.toString()}`);
|
|
return response.data;
|
|
}
|
|
|
|
async getJob(id: string): Promise<Job> {
|
|
const response = await this.client.get(`/jobs/${id}`);
|
|
return response.data;
|
|
}
|
|
|
|
async createJob(data: JobCreateRequest, file: File, onUploadProgress?: (progressEvent: { loaded: number; total: number }) => void): Promise<Job> {
|
|
const formData = new FormData();
|
|
formData.append('title', data.title);
|
|
formData.append('language', data.language);
|
|
formData.append('requested_outputs', JSON.stringify(data.requested_outputs));
|
|
formData.append('file', file);
|
|
|
|
const response = await this.client.post('/jobs', formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
},
|
|
onUploadProgress: onUploadProgress ? (progressEvent) => {
|
|
if (progressEvent.total) {
|
|
onUploadProgress({
|
|
loaded: progressEvent.loaded,
|
|
total: progressEvent.total
|
|
});
|
|
}
|
|
} : undefined,
|
|
});
|
|
return response.data;
|
|
}
|
|
|
|
async updateJob(id: string, data: Partial<Job>): Promise<Job> {
|
|
const response = await this.client.patch(`/jobs/${id}`, data);
|
|
return response.data;
|
|
}
|
|
|
|
async approveEnglish(id: string, notes?: string): Promise<Job> {
|
|
const response = await this.client.post(`/jobs/${id}/actions/approve_english`, { notes });
|
|
return response.data;
|
|
}
|
|
|
|
async rejectJob(id: string, notes: string): Promise<Job> {
|
|
const response = await this.client.post(`/jobs/${id}/actions/reject`, { notes });
|
|
return response.data;
|
|
}
|
|
|
|
async completeJob(id: string, notes?: string): Promise<Job> {
|
|
const response = await this.client.post(`/jobs/${id}/actions/complete`, { notes });
|
|
return response.data;
|
|
}
|
|
|
|
async rejectFinalReview(id: string, notes: string): Promise<Job> {
|
|
const response = await this.client.post(`/jobs/${id}/actions/reject_final`, { notes });
|
|
return response.data;
|
|
}
|
|
|
|
async getJobDownloads(id: string): Promise<JobDownloadsResponse> {
|
|
const response = await this.client.get(`/jobs/${id}/downloads`);
|
|
return response.data;
|
|
}
|
|
|
|
async getJobVttContent(id: string, language: string = 'en'): Promise<VttContentResponse> {
|
|
const response = await this.client.get(`/jobs/${id}/vtt?language=${language}`);
|
|
return response.data;
|
|
}
|
|
|
|
async updateJobVttContent(id: string, data: VttUpdateRequest): Promise<Job> {
|
|
const response = await this.client.patch(`/jobs/${id}/vtt`, data);
|
|
return response.data;
|
|
}
|
|
|
|
async adjustVttTiming(id: string, data: {
|
|
offset_seconds: number;
|
|
language?: string;
|
|
adjust_captions?: boolean;
|
|
adjust_audio_description?: boolean;
|
|
}): Promise<Job> {
|
|
const response = await this.client.post(`/jobs/${id}/vtt/adjust-timing`, data);
|
|
return response.data;
|
|
}
|
|
|
|
async validateJobAssets(id: string): Promise<AssetValidationResponse> {
|
|
const response = await this.client.get(`/jobs/${id}/validate`);
|
|
return response.data;
|
|
}
|
|
|
|
async deleteJob(id: string): Promise<JobDeleteResponse> {
|
|
const response = await this.client.delete(`/jobs/${id}`);
|
|
return response.data;
|
|
}
|
|
|
|
async bulkDeleteJobs(data: BulkDeleteRequest): Promise<BulkDeleteResponse> {
|
|
const response = await this.client.delete('/jobs/bulk', { data });
|
|
return response.data;
|
|
}
|
|
|
|
async reprocessJob(id: string): Promise<{ message: string }> {
|
|
const response = await this.client.post(`/admin/maintenance/reprocess-job/${id}`);
|
|
return response.data;
|
|
}
|
|
|
|
// User Management endpoints
|
|
async listUsers(filters?: {
|
|
page?: number;
|
|
size?: number;
|
|
role?: string;
|
|
active_only?: boolean;
|
|
}): Promise<UserListResponse> {
|
|
const params = new URLSearchParams();
|
|
if (filters?.page) params.append('page', filters.page.toString());
|
|
if (filters?.size) params.append('size', filters.size.toString());
|
|
if (filters?.role) params.append('role', filters.role);
|
|
if (filters?.active_only !== undefined) params.append('active_only', filters.active_only.toString());
|
|
|
|
const response = await this.client.get(`/admin/users?${params.toString()}`);
|
|
return response.data;
|
|
}
|
|
|
|
async getUser(userId: string): Promise<User> {
|
|
const response = await this.client.get(`/admin/users/${userId}`);
|
|
return response.data;
|
|
}
|
|
|
|
async createUser(data: CreateUserRequest): Promise<User> {
|
|
const response = await this.client.post('/admin/users', data);
|
|
return response.data;
|
|
}
|
|
|
|
async updateUser(userId: string, data: UpdateUserRequest): Promise<User> {
|
|
const response = await this.client.patch(`/admin/users/${userId}`, data);
|
|
return response.data;
|
|
}
|
|
|
|
async deactivateUser(userId: string): Promise<{ message: string }> {
|
|
const response = await this.client.delete(`/admin/users/${userId}`);
|
|
return response.data;
|
|
}
|
|
|
|
async resetUserPassword(userId: string): Promise<ResetPasswordResponse> {
|
|
const response = await this.client.post(`/admin/users/${userId}/password/reset`);
|
|
return response.data;
|
|
}
|
|
|
|
async getAdminStats(): Promise<AdminStatsResponse> {
|
|
const response = await this.client.get('/admin/stats');
|
|
return response.data;
|
|
}
|
|
}
|
|
|
|
export const apiClient = new ApiClient();
|
|
export const api = apiClient; |