itcloud/frontend/src/services/api.ts

267 lines
7.2 KiB
TypeScript

import axios, { AxiosInstance } from 'axios';
import type {
User,
AuthTokens,
Asset,
AssetListResponse,
CreateUploadRequest,
CreateUploadResponse,
DownloadUrlResponse,
Share,
CreateShareRequest,
ShareWithAssetResponse,
} from '../types';
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
class ApiClient {
private client: AxiosInstance;
constructor() {
this.client = axios.create({
baseURL: `${API_URL}/api/v1`,
headers: {
'Content-Type': 'application/json',
},
});
// Add auth token to requests
this.client.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Handle 401 errors
this.client.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
// Redirect only if not already on login/register page
if (!['/login', '/register'].includes(window.location.pathname)) {
window.location.href = '/login';
}
}
return Promise.reject(error);
}
);
}
// Auth
async register(email: string, password: string): Promise<User> {
const { data } = await this.client.post('/auth/register', { email, password });
return data;
}
async login(email: string, password: string): Promise<AuthTokens> {
const { data } = await this.client.post('/auth/login', { email, password });
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);
return data;
}
async getMe(): Promise<User> {
const { data } = await this.client.get('/auth/me');
return data;
}
logout(): void {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
}
// Assets
async listAssets(params?: {
cursor?: string;
limit?: number;
type?: string;
folder_id?: string | null;
}): Promise<AssetListResponse> {
const { data } = await this.client.get('/assets', { params });
return data;
}
async getAsset(assetId: string): Promise<Asset> {
const { data } = await this.client.get(`/assets/${assetId}`);
return data;
}
async getDownloadUrl(assetId: string, kind: 'original' | 'thumb' = 'original'): Promise<string> {
const { data } = await this.client.get<DownloadUrlResponse>(
`/assets/${assetId}/download-url`,
{ params: { kind } }
);
return data.url;
}
async getMediaBlob(assetId: string, kind: 'original' | 'thumb' = 'original'): Promise<Blob> {
const response = await this.client.get(`/assets/${assetId}/media`, {
params: { kind },
responseType: 'blob',
});
return response.data;
}
async deleteAsset(assetId: string): Promise<void> {
await this.client.delete(`/assets/${assetId}`);
}
// Upload
async createUpload(request: CreateUploadRequest): Promise<CreateUploadResponse> {
const { data } = await this.client.post('/uploads/create', request);
return data;
}
async uploadToS3(url: string, file: File, fields?: Record<string, string>): Promise<void> {
const formData = new FormData();
// Add fields first (for pre-signed POST)
if (fields) {
Object.entries(fields).forEach(([key, value]) => {
formData.append(key, value);
});
}
// Add file last
formData.append('file', file);
await axios.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
async uploadFileToBackend(assetId: string, file: File): Promise<void> {
const formData = new FormData();
formData.append('file', file);
await this.client.post(`/uploads/${assetId}/file`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
async finalizeUpload(assetId: string, etag?: string, sha256?: string): Promise<Asset> {
const { data } = await this.client.post(`/uploads/${assetId}/finalize`, { etag, sha256 });
return data;
}
// Shares
async createShare(request: CreateShareRequest): Promise<Share> {
const { data } = await this.client.post('/shares', request);
return data;
}
async getShare(token: string, password?: string): Promise<ShareWithAssetResponse> {
const { data } = await this.client.get(`/shares/${token}`, {
params: password ? { password } : undefined,
});
return data;
}
async getShareDownloadUrl(
token: string,
assetId: string,
kind: 'original' | 'thumb' = 'original',
password?: string
): Promise<string> {
const { data } = await this.client.get<DownloadUrlResponse>(
`/shares/${token}/download-url`,
{
params: { asset_id: assetId, kind, password },
}
);
return data.url;
}
async revokeShare(token: string): Promise<Share> {
const { data} = await this.client.post(`/shares/${token}/revoke`);
return data;
}
// Folders
async createFolder(name: string, parentFolderId?: string | null): Promise<any> {
const { data } = await this.client.post('/folders', {
name,
parent_folder_id: parentFolderId,
});
return data;
}
async listFolders(parentFolderId?: string | null, all: boolean = false): Promise<any> {
const params: any = {};
if (all) {
params.all = true;
} else if (parentFolderId) {
params.parent_folder_id = parentFolderId;
}
const { data } = await this.client.get('/folders', {
params: Object.keys(params).length > 0 ? params : undefined,
});
return data;
}
async getFolder(folderId: string): Promise<any> {
const { data } = await this.client.get(`/folders/${folderId}`);
return data;
}
async renameFolder(folderId: string, newName: string): Promise<any> {
const { data } = await this.client.patch(`/folders/${folderId}`, {
name: newName,
});
return data;
}
async deleteFolder(folderId: string, recursive: boolean = false): Promise<void> {
await this.client.delete(`/folders/${folderId}`, {
params: { recursive },
});
}
async getFolderBreadcrumbs(folderId: string): Promise<any> {
const { data } = await this.client.get(`/folders/${folderId}/breadcrumbs`);
return data;
}
// Batch Operations
async batchDelete(assetIds: string[]): Promise<any> {
const { data } = await this.client.post('/batch/delete', {
asset_ids: assetIds,
});
return data;
}
async batchMove(assetIds: string[], folderId: string | null): Promise<any> {
const { data } = await this.client.post('/batch/move', {
asset_ids: assetIds,
folder_id: folderId,
});
return data;
}
async batchDownload(assetIds: string[]): Promise<Blob> {
const response = await this.client.post(
'/batch/download',
{ asset_ids: assetIds },
{ responseType: 'blob' }
);
return response.data;
}
async downloadFolder(folderId: string): Promise<Blob> {
const response = await this.client.get(`/batch/folders/${folderId}/download`, {
responseType: 'blob',
});
return response.data;
}
}
export default new ApiClient();