230 lines
5.3 KiB
TypeScript
230 lines
5.3 KiB
TypeScript
/**
|
|
* Storage utilities for file management
|
|
* Provides high-level functions for common file operations
|
|
*/
|
|
|
|
import {
|
|
uploadFile,
|
|
moveToPermStorage,
|
|
deleteFile,
|
|
getFileUrl,
|
|
validateFileType,
|
|
validateFileSize,
|
|
generateUniqueFilename,
|
|
} from './file-vault'
|
|
|
|
// File type configurations
|
|
export const FILE_CONFIGS = {
|
|
image: {
|
|
allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml'],
|
|
maxSize: 10 * 1024 * 1024, // 10MB
|
|
folder: 'images',
|
|
},
|
|
document: {
|
|
allowedTypes: [
|
|
'application/pdf',
|
|
'application/msword',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'text/plain',
|
|
'text/csv',
|
|
],
|
|
maxSize: 20 * 1024 * 1024, // 20MB
|
|
folder: 'documents',
|
|
},
|
|
avatar: {
|
|
allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
maxSize: 5 * 1024 * 1024, // 5MB
|
|
folder: 'avatars',
|
|
},
|
|
} as const
|
|
|
|
export type FileType = keyof typeof FILE_CONFIGS
|
|
|
|
// File upload result
|
|
export interface UploadResult {
|
|
tempPath: string
|
|
filename: string
|
|
size: number
|
|
type: string
|
|
uploadedAt: string
|
|
}
|
|
|
|
// File confirmation result
|
|
export interface ConfirmResult {
|
|
permanentPath: string
|
|
filename: string
|
|
folder: string
|
|
url: string
|
|
confirmedAt: string
|
|
}
|
|
|
|
/**
|
|
* Upload file with type validation
|
|
*/
|
|
export async function uploadFileWithValidation(
|
|
file: File,
|
|
fileType: FileType
|
|
): Promise<UploadResult> {
|
|
const config = FILE_CONFIGS[fileType]
|
|
|
|
// Validate file type
|
|
if (!validateFileType(file.type, [...config.allowedTypes])) {
|
|
throw new Error(`Invalid file type. Allowed: ${config.allowedTypes.join(', ')}`)
|
|
}
|
|
|
|
// Validate file size
|
|
if (!validateFileSize(file.size, config.maxSize)) {
|
|
throw new Error(`File too large. Max size: ${config.maxSize / (1024 * 1024)}MB`)
|
|
}
|
|
|
|
// Convert file to buffer
|
|
const bytes = await file.arrayBuffer()
|
|
const buffer = Buffer.from(bytes)
|
|
|
|
// Upload to external API (no temporary storage needed)
|
|
const uploadResult = await uploadFile(buffer, file.name, file.type)
|
|
|
|
return {
|
|
tempPath: uploadResult.url, // Use URL as tempPath for compatibility
|
|
filename: uploadResult.filename,
|
|
size: file.size,
|
|
type: file.type,
|
|
uploadedAt: new Date().toISOString(),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Confirm upload and move to permanent storage
|
|
* With the new API, files are already in permanent storage
|
|
*/
|
|
export async function confirmUpload(
|
|
tempPath: string,
|
|
fileType: FileType,
|
|
customFilename?: string
|
|
): Promise<ConfirmResult> {
|
|
const config = FILE_CONFIGS[fileType]
|
|
|
|
// With the new API, tempPath is already the permanent URL
|
|
const url = tempPath
|
|
const filename = url.split('/').pop() || 'file'
|
|
|
|
return {
|
|
permanentPath: url,
|
|
filename: filename,
|
|
folder: config.folder,
|
|
url: url,
|
|
confirmedAt: new Date().toISOString(),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete file from storage
|
|
*/
|
|
export async function removeFile(filePath: string): Promise<void> {
|
|
await deleteFile(filePath)
|
|
}
|
|
|
|
/**
|
|
* Get signed URL for file access
|
|
*/
|
|
export async function getSignedUrl(filePath: string): Promise<string> {
|
|
return await getFileUrl(filePath)
|
|
}
|
|
|
|
/**
|
|
* Validate file before upload
|
|
*/
|
|
export function validateFile(file: File, fileType: FileType): { valid: boolean; error?: string } {
|
|
const config = FILE_CONFIGS[fileType]
|
|
|
|
// Check file type
|
|
if (!validateFileType(file.type, [...config.allowedTypes])) {
|
|
return {
|
|
valid: false,
|
|
error: `Invalid file type. Allowed: ${config.allowedTypes.join(', ')}`,
|
|
}
|
|
}
|
|
|
|
// Check file size
|
|
if (!validateFileSize(file.size, config.maxSize)) {
|
|
return {
|
|
valid: false,
|
|
error: `File too large. Max size: ${config.maxSize / (1024 * 1024)}MB`,
|
|
}
|
|
}
|
|
|
|
return { valid: true }
|
|
}
|
|
|
|
/**
|
|
* Batch file operations
|
|
*/
|
|
export class FileBatch {
|
|
private tempPaths: string[] = []
|
|
|
|
async uploadFile(file: File, fileType: FileType): Promise<UploadResult> {
|
|
const result = await uploadFileWithValidation(file, fileType)
|
|
this.tempPaths.push(result.tempPath)
|
|
return result
|
|
}
|
|
|
|
async confirmAll(fileType: FileType): Promise<ConfirmResult[]> {
|
|
const results = []
|
|
for (const tempPath of this.tempPaths) {
|
|
const result = await confirmUpload(tempPath, fileType)
|
|
results.push(result)
|
|
}
|
|
this.tempPaths = [] // Clear after confirmation
|
|
return results
|
|
}
|
|
|
|
async cleanup(): Promise<void> {
|
|
for (const tempPath of this.tempPaths) {
|
|
try {
|
|
await removeFile(tempPath)
|
|
} catch (error) {
|
|
console.error(`Failed to cleanup ${tempPath}:`, error)
|
|
}
|
|
}
|
|
this.tempPaths = []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* File metadata for database storage
|
|
*/
|
|
export interface FileMetadata {
|
|
id: string
|
|
filename: string
|
|
originalName: string
|
|
path: string
|
|
size: number
|
|
mimeType: string
|
|
fileType: FileType
|
|
uploadedBy: string
|
|
uploadedAt: Date
|
|
url?: string
|
|
}
|
|
|
|
/**
|
|
* Helper to create file metadata
|
|
*/
|
|
export function createFileMetadata(
|
|
uploadResult: UploadResult,
|
|
confirmResult: ConfirmResult,
|
|
fileType: FileType,
|
|
uploadedBy: string
|
|
): Omit<FileMetadata, 'id'> {
|
|
return {
|
|
filename: confirmResult.filename,
|
|
originalName: uploadResult.filename,
|
|
path: confirmResult.permanentPath,
|
|
size: uploadResult.size,
|
|
mimeType: uploadResult.type,
|
|
fileType,
|
|
uploadedBy,
|
|
uploadedAt: new Date(uploadResult.uploadedAt),
|
|
url: confirmResult.url,
|
|
}
|
|
}
|