ai-wpa/lib/storage.ts

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,
}
}