This commit is contained in:
Ra
2025-08-24 00:48:41 -07:00
parent 9c20073755
commit f2145edf56
37 changed files with 2621 additions and 2692 deletions

View File

@@ -1,6 +1,6 @@
import { doc, setDoc, getDoc, updateDoc, deleteDoc } from 'firebase/firestore';
import { db } from './firebase';
import { processImage, validateImageFile, generateUniqueFileName, ProcessedImage } from '../utils/imageUtils';
import { secureApi } from './secureApi';
import { useAuth } from '../contexts/AuthContext';
export interface StoredImage {
id: string;
@@ -14,10 +14,12 @@ export interface StoredImage {
}
/**
* Upload and store an image in Firestore
* Upload and store an image through secure API
* @param file - The image file to upload
* @param collectionName - Firestore collection name (e.g., 'company-logos')
* @param collectionName - Collection name (e.g., 'company-logos')
* @param documentId - Document ID (e.g., orgId)
* @param orgId - Organization ID
* @param userId - User ID for authentication
* @param maxWidth - Maximum width for resizing (default: 128)
* @param maxHeight - Maximum height for resizing (default: 128)
* @returns Promise with stored image data
@@ -26,13 +28,11 @@ export const uploadImage = async (
file: File,
collectionName: string,
documentId: string,
orgId: string,
userId: string,
maxWidth: number = 128,
maxHeight: number = 128
): Promise<StoredImage> => {
if (!db) {
throw new Error('Firebase not initialized');
}
// Validate the image file
const validation = validateImageFile(file);
if (!validation.valid) {
@@ -45,135 +45,138 @@ export const uploadImage = async (
// Generate unique filename
const filename = generateUniqueFileName(file.name, 'logo');
// Create image data to store
const imageData: StoredImage = {
id: filename,
// Create image data to upload
const imageData = {
collectionName,
documentId,
dataUrl: processedImage.dataUrl,
filename,
originalSize: processedImage.originalSize,
compressedSize: processedImage.compressedSize,
uploadedAt: Date.now(),
width: processedImage.width,
height: processedImage.height,
};
// Store in Firestore
const docRef = doc(db, collectionName, documentId);
try {
// Get existing document to preserve other data
const existingDoc = await getDoc(docRef);
const result = await secureApi.uploadImage(orgId, userId, imageData);
if (existingDoc.exists()) {
// Update existing document
await updateDoc(docRef, {
logo: imageData,
updatedAt: Date.now(),
});
} else {
// Create new document
await setDoc(docRef, {
logo: imageData,
createdAt: Date.now(),
updatedAt: Date.now(),
});
if (!result.success) {
throw new Error('Failed to upload image');
}
return imageData;
return {
id: result.imageId,
dataUrl: processedImage.dataUrl,
filename,
originalSize: processedImage.originalSize,
compressedSize: processedImage.compressedSize,
uploadedAt: Date.now(),
width: processedImage.width,
height: processedImage.height,
};
} catch (error) {
console.error('Failed to store image in Firestore:', error);
console.error('Failed to upload image through secure API:', error);
throw new Error('Failed to upload image');
}
};
/**
* Retrieve an image from Firestore
* @param collectionName - Firestore collection name
* Retrieve an image through secure API
* @param collectionName - Collection name
* @param documentId - Document ID
* @param orgId - Organization ID
* @param userId - User ID for authentication
* @returns Promise with stored image data or null if not found
*/
export const getImage = async (
collectionName: string,
documentId: string
documentId: string,
orgId: string,
userId: string
): Promise<StoredImage | null> => {
if (!db) {
throw new Error('Firebase not initialized');
}
try {
const docRef = doc(db, collectionName, documentId);
const docSnap = await getDoc(docRef);
const result = await secureApi.getImage(orgId, userId, collectionName, documentId);
if (docSnap.exists()) {
const data = docSnap.data();
return data.logo || null;
}
return null;
return result; // getImage already returns StoredImage | null
} catch (error) {
console.error('Failed to retrieve image from Firestore:', error);
console.error('Failed to retrieve image through secure API:', error);
return null;
}
};
/**
* Delete an image from Firestore
* @param collectionName - Firestore collection name
* Delete an image through secure API
* @param collectionName - Collection name
* @param documentId - Document ID
* @param orgId - Organization ID
* @param userId - User ID for authentication
* @returns Promise indicating success
*/
export const deleteImage = async (
collectionName: string,
documentId: string
documentId: string,
orgId: string,
userId: string
): Promise<boolean> => {
if (!db) {
throw new Error('Firebase not initialized');
}
try {
const docRef = doc(db, collectionName, documentId);
const docSnap = await getDoc(docRef);
const result = await secureApi.deleteImage(orgId, userId, collectionName, documentId);
if (docSnap.exists()) {
const data = docSnap.data();
if (data.logo) {
// Remove only the logo field, keep other data
const updatedData = { ...data };
delete updatedData.logo;
updatedData.updatedAt = Date.now();
await updateDoc(docRef, updatedData);
return true;
}
}
return false;
return result; // deleteImage already returns boolean
} catch (error) {
console.error('Failed to delete image from Firestore:', error);
console.error('Failed to delete image through secure API:', error);
return false;
}
};
/**
* Company-specific image upload (convenience function)
* Requires authentication context to get userId
*/
export const uploadCompanyLogo = async (
file: File,
orgId: string
orgId: string,
userId: string
): Promise<StoredImage> => {
return uploadImage(file, 'company-logos', orgId, 128, 128);
return uploadImage(file, 'company-logos', orgId, orgId, userId, 128, 128);
};
/**
* Get company logo (convenience function)
* Requires authentication context to get userId
*/
export const getCompanyLogo = async (orgId: string): Promise<StoredImage | null> => {
return getImage('company-logos', orgId);
export const getCompanyLogo = async (
orgId: string,
userId: string
): Promise<StoredImage | null> => {
return getImage('company-logos', orgId, orgId, userId);
};
/**
* Delete company logo (convenience function)
* Requires authentication context to get userId
*/
export const deleteCompanyLogo = async (orgId: string): Promise<boolean> => {
return deleteImage('company-logos', orgId);
export const deleteCompanyLogo = async (
orgId: string,
userId: string
): Promise<boolean> => {
return deleteImage('company-logos', orgId, orgId, userId);
};
/**
* Hook-based convenience functions that automatically get userId from auth context
* Use these in React components where useAuth is available
*/
export const useImageStorage = () => {
// Note: This would need to be implemented in a React component context
// where useAuth() is available. For now, we provide the functions that require
// explicit userId parameter.
return {
uploadImage,
getImage,
deleteImage,
uploadCompanyLogo,
getCompanyLogo,
deleteCompanyLogo,
};
};