267 lines
7.2 KiB
TypeScript
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();
|