153 lines
4.1 KiB
TypeScript
153 lines
4.1 KiB
TypeScript
/**
|
||
* File upload service using external API
|
||
* Replaces MINIO with custom file upload API
|
||
*/
|
||
|
||
import { env } from './env'
|
||
|
||
// File upload result from the API
|
||
export interface FileUploadResponse {
|
||
success: boolean
|
||
url: string
|
||
filename: string
|
||
}
|
||
|
||
// File upload result for internal use
|
||
export interface UploadResult {
|
||
url: string
|
||
filename: string
|
||
originalName: string
|
||
size: number
|
||
type: string
|
||
uploadedAt: string
|
||
}
|
||
|
||
/**
|
||
* Upload file to external API
|
||
*/
|
||
export async function uploadFile(
|
||
buffer: Buffer,
|
||
filename: string,
|
||
contentType: string,
|
||
userId?: string
|
||
): Promise<UploadResult> {
|
||
console.log('📤 Starting file upload:', { filename, contentType, bufferSize: buffer.length })
|
||
|
||
try {
|
||
// Create FormData for multipart upload
|
||
const formData = new FormData()
|
||
const blob = new Blob([buffer], { type: contentType })
|
||
formData.append('file', blob, filename)
|
||
|
||
// Prepare headers
|
||
const headers: Record<string, string> = {
|
||
'x-user-data': userId || 'default-user',
|
||
Authorization: `Bearer ${env.FILE_UPLOAD_TOKEN || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRlc3QgVXNlciIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'}`,
|
||
}
|
||
|
||
console.log('📤 Uploading to external API:', {
|
||
url: process.env.UPLOAD_API_URL,
|
||
filename,
|
||
size: buffer.length,
|
||
userId: userId || 'default-user',
|
||
})
|
||
|
||
// Make the upload request
|
||
const response = await fetch(process.env.UPLOAD_API_URL, {
|
||
method: 'POST',
|
||
headers,
|
||
body: formData,
|
||
})
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text()
|
||
throw new Error(`Upload failed: ${response.status} ${response.statusText} - ${errorText}`)
|
||
}
|
||
|
||
const result: FileUploadResponse = await response.json()
|
||
|
||
if (!result.success) {
|
||
throw new Error('Upload API returned success: false')
|
||
}
|
||
|
||
console.log('✅ File uploaded successfully:', result)
|
||
|
||
return {
|
||
url: result.url,
|
||
filename: result.filename,
|
||
originalName: filename,
|
||
size: buffer.length,
|
||
type: contentType,
|
||
uploadedAt: new Date().toISOString(),
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error uploading file:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Generate unique filename with timestamp and random string
|
||
*/
|
||
export function generateUniqueFilename(originalName: string): string {
|
||
const timestamp = Date.now()
|
||
const randomString = Math.random().toString(36).substring(2)
|
||
const extension = originalName.split('.').pop()
|
||
return `${timestamp}-${randomString}.${extension}`
|
||
}
|
||
|
||
/**
|
||
* Validate file type
|
||
*/
|
||
export function validateFileType(mimetype: string, allowedTypes: string[]): boolean {
|
||
return allowedTypes.includes(mimetype)
|
||
}
|
||
|
||
/**
|
||
* Validate file size (in bytes)
|
||
*/
|
||
export function validateFileSize(size: number, maxSize: number): boolean {
|
||
return size <= maxSize
|
||
}
|
||
|
||
/**
|
||
* Get file URL (files are already publicly accessible)
|
||
*/
|
||
export async function getFileUrl(filePath: string): Promise<string> {
|
||
// Files from the upload API are already publicly accessible
|
||
// If filePath is already a full URL, return it as is
|
||
if (filePath.startsWith('http')) {
|
||
return filePath
|
||
}
|
||
|
||
// Otherwise, construct the URL using the delivery base URL
|
||
return `${process.env.DELIVERY_BASE_URL}/${filePath}`
|
||
}
|
||
|
||
/**
|
||
* Delete file (not supported by the current API)
|
||
*/
|
||
export async function deleteFile(filePath: string): Promise<void> {
|
||
console.warn('⚠️ File deletion not supported by current upload API:', filePath)
|
||
// The external API doesn't provide a delete endpoint
|
||
// This is a no-op for now
|
||
}
|
||
|
||
/**
|
||
* Move file to permanent storage (not needed with new API)
|
||
*/
|
||
export async function moveToPermStorage(tempPath: string, permanentPath: string): Promise<void> {
|
||
console.log('ℹ️ Move to permanent storage not needed with new API')
|
||
// The new API directly uploads to permanent storage
|
||
// This is a no-op
|
||
}
|
||
|
||
/**
|
||
* Initialize bucket (not needed with new API)
|
||
*/
|
||
export async function initializeBucket(): Promise<void> {
|
||
console.log('ℹ️ Bucket initialization not needed with new API')
|
||
// The new API handles storage internally
|
||
// This is a no-op
|
||
}
|