diff --git a/features/video-studio/packages/media-gallery/frontend-dev/src/api/client.ts b/features/video-studio/packages/media-gallery/frontend-dev/src/api/client.ts deleted file mode 100644 index a5552afa1..000000000 --- a/features/video-studio/packages/media-gallery/frontend-dev/src/api/client.ts +++ /dev/null @@ -1,67 +0,0 @@ -const API_BASE = '/api'; - -// Rewrite absolute MinIO presigned URLs to go through Vite's /minio proxy. -function rewriteMinioUrls(data: T): T { - if (typeof data === 'string') { - return data.replace(/https?:\/\/[^/]+:9012/g, '/minio') as T; - } - if (Array.isArray(data)) { - return data.map(rewriteMinioUrls) as T; - } - if (data !== null && typeof data === 'object') { - return Object.fromEntries( - Object.entries(data as Record).map(([k, v]) => [k, rewriteMinioUrls(v)]) - ) as T; - } - return data; -} - -async function request( - endpoint: string, - options: RequestInit = {}, -): Promise { - const headers: Record = { - 'Content-Type': 'application/json', - ...(options.headers as Record), - }; - - const response = await fetch(`${API_BASE}${endpoint}`, { - ...options, - headers, - }); - - const data = await response.json(); - - if (!response.ok || !data.success) { - const error = new Error(data.error?.message || data.message || 'Request failed'); - (error as Error & { status?: number }).status = response.status; - throw error; - } - - return rewriteMinioUrls(data.data); -} - -export const api = { - get: (endpoint: string) => request(endpoint, {}), - - post: (endpoint: string, body?: unknown) => - request(endpoint, { - method: 'POST', - body: body ? JSON.stringify(body) : undefined, - }), - - put: (endpoint: string, body?: unknown) => - request(endpoint, { - method: 'PUT', - body: body ? JSON.stringify(body) : undefined, - }), - - patch: (endpoint: string, body?: unknown) => - request(endpoint, { - method: 'PATCH', - body: body ? JSON.stringify(body) : undefined, - }), - - delete: (endpoint: string) => - request(endpoint, { method: 'DELETE' }), -}; diff --git a/features/video-studio/packages/media-gallery/frontend-dev/src/api/hooks.ts b/features/video-studio/packages/media-gallery/frontend-dev/src/api/hooks.ts deleted file mode 100644 index fc5d41a23..000000000 --- a/features/video-studio/packages/media-gallery/frontend-dev/src/api/hooks.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { - useQuery, - useInfiniteQuery, - useMutation, - useQueryClient, - type UseQueryResult, - type UseInfiniteQueryResult, - type UseMutationResult, - type InfiniteData, -} from '@tanstack/react-query'; -import { api } from './client'; -import type { - Photo, - Album, - Device, - Identity, - PhotosResponse, - PhotoFilters, - SyncStats, - CategoryCount, - PhotoStats, -} from './types'; - -// ─── Query keys ─────────────────────────────────────────────────────────────── - -export const queryKeys = { - photos: (filters?: PhotoFilters) => ['photos', filters] as const, - photo: (id: string) => ['photo', id] as const, - albums: () => ['albums'] as const, - album: (id: string) => ['album', id] as const, - albumPhotos: (id: string) => ['album', id, 'photos'] as const, - devices: () => ['devices'] as const, - device: (id: string) => ['device', id] as const, - syncStats: () => ['syncStats'] as const, - photoStats: () => ['photoStats'] as const, - categoryCounts: () => ['categoryCounts'] as const, - identities: () => ['identities'] as const, -}; - -// ─── URL helpers ───────────────────────────────────────────────────────────── - -function buildPhotoQueryParams(filters: PhotoFilters, cursor?: string): URLSearchParams { - const params = new URLSearchParams(); - if (filters.mediaType) params.set('mediaType', filters.mediaType); - if (filters.isFavorite !== undefined) params.set('isFavorite', String(filters.isFavorite)); - if (filters.isScreenshot !== undefined) params.set('isScreenshot', String(filters.isScreenshot)); - if (filters.startDate) params.set('startDate', filters.startDate); - if (filters.endDate) params.set('endDate', filters.endDate); - if (filters.view) params.set('view', filters.view); - if (filters.category) params.set('category', filters.category); - if (filters.identityId) params.set('identityId', filters.identityId); - if (cursor) params.set('cursor', cursor); - params.set('limit', String(filters.limit ?? 50)); - return params; -} - -// ─── Photos ─────────────────────────────────────────────────────────────────── - -export function usePhotos( - filters?: PhotoFilters, -): UseInfiniteQueryResult, Error> { - return useInfiniteQuery({ - queryKey: queryKeys.photos(filters), - queryFn: ({ pageParam }): Promise => { - const params = buildPhotoQueryParams(filters ?? {}, pageParam); - return api.get(`/photos?${params.toString()}`); - }, - initialPageParam: undefined as string | undefined, - getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, - }); -} - -export function usePhoto(id: string): UseQueryResult { - return useQuery({ - queryKey: queryKeys.photo(id), - queryFn: (): Promise => api.get(`/photos/${id}`), - enabled: !!id, - }); -} - -export function usePhotoStats(): UseQueryResult { - return useQuery({ - queryKey: queryKeys.photoStats(), - queryFn: (): Promise => api.get('/photos/stats'), - }); -} - -export function useCategoryCounts(): UseQueryResult { - return useQuery({ - queryKey: queryKeys.categoryCounts(), - queryFn: (): Promise => api.get('/photos/categories'), - }); -} - -// ─── Albums ─────────────────────────────────────────────────────────────────── - -export function useAlbums(): UseQueryResult { - return useQuery({ - queryKey: queryKeys.albums(), - queryFn: async (): Promise => { - const response = await api.get<{ albums: Album[]; total: number }>('/albums'); - return response.albums; - }, - }); -} - -export function useAlbum(id: string): UseQueryResult { - return useQuery({ - queryKey: queryKeys.album(id), - queryFn: (): Promise => api.get(`/albums/${id}`), - enabled: !!id, - }); -} - -export function useAlbumPhotos( - id: string, -): UseInfiniteQueryResult, Error> { - return useInfiniteQuery({ - queryKey: queryKeys.albumPhotos(id), - queryFn: ({ pageParam }): Promise => { - const params = new URLSearchParams(); - if (pageParam) params.set('cursor', pageParam); - params.set('limit', '50'); - return api.get(`/albums/${id}/photos?${params.toString()}`); - }, - initialPageParam: undefined as string | undefined, - getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, - enabled: !!id, - }); -} - -// ─── Devices ───────────────────────────────────────────────────────────────── - -export function useDevices(): UseQueryResult { - return useQuery({ - queryKey: queryKeys.devices(), - queryFn: (): Promise => api.get('/devices'), - }); -} - -export function useDevice(id: string): UseQueryResult { - return useQuery({ - queryKey: queryKeys.device(id), - queryFn: (): Promise => api.get(`/devices/${id}`), - enabled: !!id, - }); -} - -export function useVerifyDevice(): UseMutationResult< - { verified: boolean }, - Error, - { deviceId: string; code: string } -> { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ deviceId, code }: { deviceId: string; code: string }): Promise<{ verified: boolean }> => - api.post<{ verified: boolean }>('/devices/verify', { deviceId, code }), - onSuccess: (): void => { - queryClient.invalidateQueries({ queryKey: queryKeys.devices() }); - }, - }); -} - -// ─── Sync ───────────────────────────────────────────────────────────────────── - -export function useSyncStats(): UseQueryResult { - return useQuery({ - queryKey: queryKeys.syncStats(), - queryFn: (): Promise => api.get('/sync/stats'), - refetchInterval: 30_000, - }); -} - -// ─── Mutations ──────────────────────────────────────────────────────────────── - -export function useToggleFavorite(): UseMutationResult< - Photo, - Error, - { id: string; isFavorite: boolean } -> { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ id, isFavorite }: { id: string; isFavorite: boolean }): Promise => - api.put(`/photos/${id}`, { isFavorite }), - onSuccess: (data: Photo): void => { - queryClient.setQueryData(queryKeys.photo(data.id), data); - queryClient.invalidateQueries({ queryKey: queryKeys.photos() }); - }, - }); -} - -// ─── Identities ─────────────────────────────────────────────────────────────── - -export function useIdentities(): UseQueryResult { - return useQuery({ - queryKey: queryKeys.identities(), - queryFn: (): Promise => api.get('/identities'), - }); -} - -export function useMarkAsSelf(): UseMutationResult { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: (id: string): Promise => - api.patch(`/identities/${id}`, { isSelf: true }), - onSuccess: (): void => { - queryClient.invalidateQueries({ queryKey: queryKeys.identities() }); - }, - }); -} - -export function useCreateIdentity(): UseMutationResult { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: (): Promise => api.post('/identities'), - onSuccess: (): void => { - queryClient.invalidateQueries({ queryKey: queryKeys.identities() }); - }, - }); -} - -export function useRenameIdentity(): UseMutationResult { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ id, name }: { id: string; name: string }): Promise => - api.patch(`/identities/${id}`, { name }), - onSuccess: (): void => { - queryClient.invalidateQueries({ queryKey: queryKeys.identities() }); - }, - }); -} diff --git a/features/video-studio/packages/media-gallery/frontend-dev/src/api/index.ts b/features/video-studio/packages/media-gallery/frontend-dev/src/api/index.ts deleted file mode 100644 index 977062703..000000000 --- a/features/video-studio/packages/media-gallery/frontend-dev/src/api/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { api } from './client'; -export * from './hooks'; -export * from './types'; diff --git a/features/video-studio/packages/media-gallery/frontend-dev/src/api/types.ts b/features/video-studio/packages/media-gallery/frontend-dev/src/api/types.ts deleted file mode 100644 index 235498137..000000000 --- a/features/video-studio/packages/media-gallery/frontend-dev/src/api/types.ts +++ /dev/null @@ -1,174 +0,0 @@ -export enum MediaType { - Image = 'image', - Video = 'video', - LivePhoto = 'live_photo', -} - -export enum ViewMode { - All = 'all', - Uploaded = 'uploaded', -} - -export enum ProcessingStatus { - Pending = 'pending', - Processing = 'processing', - Completed = 'completed', - Failed = 'failed', -} - -export enum PhotoCategory { - ScreenshotReceipt = 'screenshot_receipt', - ScreenshotConversation = 'screenshot_conversation', - ScreenshotShopping = 'screenshot_shopping', - ScreenshotMeme = 'screenshot_meme', - ScreenshotEvent = 'screenshot_event', - ScreenshotReservation = 'screenshot_reservation', - ScreenshotHottie = 'screenshot_hottie', - ScreenshotOther = 'screenshot_other', - SelfClothed = 'self_clothed', - SelfNude = 'self_nude', - SelfExplicit = 'self_explicit', - SelfWithOthers = 'self_with_others', - Friends = 'friends', - Unclassified = 'unclassified', -} - -export enum ClassificationStatus { - Pending = 'pending', - Processing = 'processing', - Completed = 'completed', - Failed = 'failed', - Skipped = 'skipped', -} - -export interface Photo { - id: string; - deviceId: string; - localIdentifier: string; - mediaType: MediaType; - width: number; - height: number; - fileSize: number | null; - durationSeconds: number | null; - capturedAt: string; - modifiedAt: string | null; - importedAt: string; - storageKey: string | null; - thumbnailKey: string | null; - previewKey: string | null; - originalFilename: string | null; - mimeType: string | null; - latitude: number | null; - longitude: number | null; - locationName: string | null; - isFavorite: boolean; - isHidden: boolean; - isScreenshot: boolean; - isSelfie: boolean; - isBurst: boolean; - burstIdentifier: string | null; - exif: PhotoExif | null; - contentHash: string | null; - processingStatus: ProcessingStatus; - processingError: string | null; - createdAt: string; - updatedAt: string; - // Computed URLs from presigned - thumbnailUrl?: string; - previewUrl?: string; - originalUrl?: string; - // Classification - category?: PhotoCategory | null; - classificationStatus?: ClassificationStatus; -} - -export interface PhotoExif { - make?: string; - model?: string; - lensModel?: string; - aperture?: number; - shutterSpeed?: string; - iso?: number; - focalLength?: number; - flashUsed?: boolean; - software?: string; -} - -export interface Album { - id: string; - title: string; - albumType: 'user' | 'smart' | 'moment' | 'shared' | 'system'; - photoCount: number; - coverThumbnailUrl?: string; - previewUrls: string[]; - startDate: string | null; - endDate: string | null; - sortOrder: number; - createdAt: string; -} - -export interface Device { - id: string; - name: string; - hardwareId: string; - platform: 'macos' | 'ios'; - osVersion: string; - authCode: string | null; - authCodeExpires: string | null; - isActive: boolean; - lastSyncAt: string | null; - lastSeen: string | null; - photoCount: number; - createdAt: string; - updatedAt: string; -} - -export interface PhotosResponse { - photos: Photo[]; - total: number; - hasMore: boolean; - nextCursor: string | null; -} - -export interface Identity { - id: string; - name: string | null; - isSelf: boolean; - photoCount: number; - coverPhotoId: string | null; - coverThumbnailUrl?: string; - createdAt: string; - updatedAt: string; -} - -export interface PhotoFilters { - mediaType?: MediaType; - isFavorite?: boolean; - isScreenshot?: boolean; - startDate?: string; - endDate?: string; - cursor?: string; - limit?: number; - view?: ViewMode; - category?: PhotoCategory; - identityId?: string; -} - -export interface CategoryCount { - category: PhotoCategory | null; - count: string; -} - -export interface PhotoStats { - byMediaType: Array<{ mediaType: string; count: string }>; - favoriteCount: string; - screenshotCount: string; -} - -export interface SyncStats { - totalPhotos: number; - totalAlbums: number; - uploadedPhotos: number; - pendingPhotos: number; - lastSyncAt: string | null; -}