| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- import { useState, useCallback, useEffect } from 'react';
- import { useBaaS, useAuth } from '@picobaas/client/react';
- import type { ImageFile, UploadOptions } from '../types';
- import { PUBLIC_BUCKET, USER_BUCKET, generateUUID } from '../config';
- import { getExpiryFromMetadata } from '../utils/expiry';
- import { getSessionToken, trackUpload, untrackUpload } from '../utils/session';
- import type { FileMetadata } from '@picobaas/client';
- export function useImages(bucket: string = PUBLIC_BUCKET) {
- const { client } = useBaaS();
- const { userId, isAuthenticated } = useAuth();
- const [images, setImages] = useState<ImageFile[]>([]);
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState<string | null>(null);
- const fetchImages = useCallback(async () => {
- setIsLoading(true);
- setError(null);
- try {
- const storage = client.storage.from(bucket);
- const prefix = bucket === USER_BUCKET && userId ? `${userId}/` : '';
- // Fetch from storage
- const { data, error: listError } = await storage.list(prefix, {
- limit: 100,
- });
- if (listError) throw listError;
- // Also fetch from image API to get shortCodes using SDK's request method
- const sessionToken = getSessionToken();
- let imageMetadata: Record<string, { shortCode: string; shortUrl: string; downloadAllowed: boolean }> = {};
- try {
- const apiResponse = await client.request<{ items: Array<{ path: string; short_code: string; short_url: string; download_allowed: boolean }> }>(
- '/images',
- {
- headers: { 'X-Session-Token': sessionToken },
- }
- );
- if (!apiResponse.error && apiResponse.data) {
- // Create a lookup by path
- for (const img of apiResponse.data.items || []) {
- imageMetadata[img.path] = {
- shortCode: img.short_code,
- shortUrl: img.short_url,
- downloadAllowed: img.download_allowed,
- };
- }
- }
- } catch (e) {
- // Non-fatal, continue without shortCodes
- console.warn('Failed to fetch image metadata:', e);
- }
- const imageFiles: ImageFile[] = (data || []).map((file: FileMetadata) => {
- const meta = imageMetadata[file.path];
- return {
- id: file.id,
- path: file.path,
- name: file.filename || file.path.split('/').pop() || 'Unknown',
- size: file.size,
- mimeType: file.mime_type,
- url: storage.getPublicUrl(file.path).data.publicUrl,
- createdAt: new Date(file.created_at * 1000),
- expiresAt: getExpiryFromMetadata(file.custom_metadata),
- isPublic: bucket === PUBLIC_BUCKET,
- ownerId: file.owner_id,
- shortCode: meta?.shortCode,
- shortUrl: meta?.shortUrl,
- downloadAllowed: meta?.downloadAllowed,
- };
- });
- // Sort by created date, newest first
- imageFiles.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
- setImages(imageFiles);
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load images');
- } finally {
- setIsLoading(false);
- }
- }, [client, bucket, userId]);
- const uploadImage = useCallback(
- async (file: File, options: UploadOptions): Promise<ImageFile | null> => {
- try {
- const storage = client.storage.from(bucket);
- // Generate unique filename
- const ext = file.name.split('.').pop() || 'jpg';
- const uniqueId = generateUUID();
- const prefix = bucket === USER_BUCKET && userId ? `${userId}/` : '';
- const path = `${prefix}${uniqueId}.${ext}`;
- // Calculate expiry
- const expiresAt =
- options.expirySeconds > 0
- ? new Date(Date.now() + options.expirySeconds * 1000).toISOString()
- : undefined;
- // Upload to storage
- const { data, error: uploadError } = await storage.upload(path, file, {
- contentType: file.type,
- metadata: expiresAt ? { expires_at: expiresAt } : undefined,
- });
- if (uploadError) throw uploadError;
- let shortCode: string | undefined;
- let shortUrl: string | undefined;
- // Only register with image API for anonymous users (auto short link generation)
- // Logged-in users manage share links manually via the dashboard
- if (!isAuthenticated) {
- const sessionToken = getSessionToken();
- const apiResponse = await client.request<{ short_code: string }>(
- '/images',
- {
- method: 'POST',
- headers: { 'X-Session-Token': sessionToken },
- body: JSON.stringify({
- bucket,
- path,
- storage_object_id: data?.id,
- download_allowed: options.downloadAllowed,
- expires_at: expiresAt,
- }),
- }
- );
- if (!apiResponse.error && apiResponse.data) {
- shortCode = apiResponse.data.short_code;
- shortUrl = `/i/${shortCode}`;
- // Track anonymous uploads for deletion capability
- trackUpload(shortCode);
- } else {
- console.warn('Failed to register image with API:', apiResponse.error);
- }
- }
- const newImage: ImageFile = {
- id: data?.id || uniqueId,
- path: path,
- name: file.name,
- size: file.size,
- mimeType: file.type,
- url: storage.getPublicUrl(path).data.publicUrl,
- createdAt: new Date(),
- expiresAt: expiresAt ? new Date(expiresAt) : undefined,
- isPublic: bucket === PUBLIC_BUCKET,
- ownerId: userId || undefined,
- shortCode,
- shortUrl,
- downloadAllowed: options.downloadAllowed,
- };
- setImages((prev) => [newImage, ...prev]);
- return newImage;
- } catch (err) {
- throw new Error(
- err instanceof Error ? err.message : 'Upload failed'
- );
- }
- },
- [client, bucket, userId, isAuthenticated]
- );
- const deleteImage = useCallback(
- async (image: ImageFile): Promise<void> => {
- try {
- // If we have a short code, delete via the image API using SDK
- if (image.shortCode) {
- const sessionToken = getSessionToken();
- const apiResponse = await client.request(
- `/images/${image.shortCode}`,
- {
- method: 'DELETE',
- headers: { 'X-Session-Token': sessionToken },
- }
- );
- if (apiResponse.error) {
- throw new Error(apiResponse.error.message || 'Delete failed');
- }
- // Remove from session tracking
- untrackUpload(image.shortCode);
- } else {
- // Fallback to direct storage deletion
- const storage = client.storage.from(bucket);
- const { error: deleteError } = await storage.remove([image.path]);
- if (deleteError) throw deleteError;
- }
- setImages((prev) => prev.filter((img) => img.id !== image.id));
- } catch (err) {
- throw new Error(
- err instanceof Error ? err.message : 'Delete failed'
- );
- }
- },
- [client, bucket]
- );
- // Auto-fetch when authenticated for user bucket
- useEffect(() => {
- if (bucket === USER_BUCKET && isAuthenticated) {
- fetchImages();
- } else if (bucket === PUBLIC_BUCKET) {
- fetchImages();
- }
- }, [bucket, isAuthenticated, fetchImages]);
- return {
- images,
- isLoading,
- error,
- fetchImages,
- uploadImage,
- deleteImage,
- };
- }
|