129 lines
3.7 KiB
TypeScript
129 lines
3.7 KiB
TypeScript
|
|
import { apiFetch, type PaginatedResponse } from './api';
|
||
|
|
|
||
|
|
export interface FileResponse {
|
||
|
|
id: string;
|
||
|
|
vehicle_id: string;
|
||
|
|
original_filename: string;
|
||
|
|
stored_filename: string;
|
||
|
|
file_path: string;
|
||
|
|
mime_type: string;
|
||
|
|
file_size: number;
|
||
|
|
thumbnail_path: string | null;
|
||
|
|
created_at?: string;
|
||
|
|
updated_at?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface FileListResponse extends PaginatedResponse<FileResponse> {}
|
||
|
|
|
||
|
|
export interface FileDeleteResponse {
|
||
|
|
message: string;
|
||
|
|
id: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const ALLOWED_MIME_TYPES = [
|
||
|
|
'image/jpeg',
|
||
|
|
'image/png',
|
||
|
|
'image/webp',
|
||
|
|
'application/pdf',
|
||
|
|
'application/msword',
|
||
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
|
|
];
|
||
|
|
|
||
|
|
export const MAX_FILE_SIZE_MB = 20;
|
||
|
|
export const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
|
||
|
|
|
||
|
|
export function isImageMime(mime: string): boolean {
|
||
|
|
return mime.startsWith('image/');
|
||
|
|
}
|
||
|
|
|
||
|
|
export function formatFileSize(bytes: number): string {
|
||
|
|
if (bytes < 1024) return `${bytes} B`;
|
||
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function listFiles(
|
||
|
|
vehicleId: string,
|
||
|
|
page = 1,
|
||
|
|
pageSize = 20
|
||
|
|
): Promise<FileListResponse> {
|
||
|
|
return apiFetch<FileListResponse>(
|
||
|
|
`/vehicles/${vehicleId}/files?page=${page}&page_size=${pageSize}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function uploadFile(
|
||
|
|
vehicleId: string,
|
||
|
|
file: File
|
||
|
|
): Promise<FileResponse> {
|
||
|
|
const token = typeof window !== 'undefined'
|
||
|
|
? localStorage.getItem('access_token')
|
||
|
|
: null;
|
||
|
|
|
||
|
|
const formData = new FormData();
|
||
|
|
formData.append('file', file);
|
||
|
|
|
||
|
|
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1';
|
||
|
|
|
||
|
|
const response = await fetch(`${API_BASE_URL}/vehicles/${vehicleId}/files`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||
|
|
},
|
||
|
|
body: formData,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (response.status === 401 && token) {
|
||
|
|
const refreshToken = typeof window !== 'undefined'
|
||
|
|
? localStorage.getItem('refresh_token')
|
||
|
|
: null;
|
||
|
|
if (refreshToken) {
|
||
|
|
const refreshResult = await apiFetch<{ access_token: string; refresh_token: string }>(
|
||
|
|
'/auth/refresh',
|
||
|
|
{ method: 'POST', body: JSON.stringify({ refresh_token: refreshToken }) }
|
||
|
|
);
|
||
|
|
localStorage.setItem('access_token', refreshResult.access_token);
|
||
|
|
localStorage.setItem('refresh_token', refreshResult.refresh_token);
|
||
|
|
|
||
|
|
const retryResponse = await fetch(`${API_BASE_URL}/vehicles/${vehicleId}/files`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { Authorization: `Bearer ${refreshResult.access_token}` },
|
||
|
|
body: formData,
|
||
|
|
});
|
||
|
|
if (!retryResponse.ok) {
|
||
|
|
const err = await retryResponse.json().catch(() => ({ error: { code: 'UNKNOWN', message: 'Upload failed' } }));
|
||
|
|
throw err;
|
||
|
|
}
|
||
|
|
return retryResponse.json();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const err = await response.json().catch(() => ({ error: { code: 'UNKNOWN', message: 'Upload failed' } }));
|
||
|
|
throw err;
|
||
|
|
}
|
||
|
|
|
||
|
|
return response.json();
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteFile(
|
||
|
|
vehicleId: string,
|
||
|
|
fileId: string
|
||
|
|
): Promise<FileDeleteResponse> {
|
||
|
|
return apiFetch<FileDeleteResponse>(
|
||
|
|
`/vehicles/${vehicleId}/files/${fileId}`,
|
||
|
|
{ method: 'DELETE' }
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getFileDownloadUrl(vehicleId: string, fileId: string): string {
|
||
|
|
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1';
|
||
|
|
return `${API_BASE_URL}/vehicles/${vehicleId}/files/${fileId}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getThumbnailUrl(file: FileResponse): string | null {
|
||
|
|
if (!file.thumbnail_path) return null;
|
||
|
|
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1';
|
||
|
|
return `${API_BASE_URL}/vehicles/${file.vehicle_id}/files/${file.id}`;
|
||
|
|
}
|