initial commit
This commit is contained in:
229
lib/storage.ts
Normal file
229
lib/storage.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user