160 lines
4.3 KiB
TypeScript
160 lines
4.3 KiB
TypeScript
import { prisma } from './prisma'
|
|
import { requireAuth } from './auth'
|
|
// Using type from Prisma for now
|
|
type UploadSession = any
|
|
|
|
const DEFAULT_CHUNK_SIZE = 1024 * 1024 * 5 // 5MB chunks
|
|
const UPLOAD_SESSION_EXPIRY = 24 * 60 * 60 * 1000 // 24 hours in ms
|
|
|
|
export async function createUploadSession(
|
|
userId: string,
|
|
fileName: string,
|
|
fileSize: number,
|
|
mimeType: string,
|
|
chunkSize?: number,
|
|
metadata?: Record<string, unknown>
|
|
): Promise<any> {
|
|
const actualChunkSize = chunkSize || DEFAULT_CHUNK_SIZE
|
|
const totalChunks = Math.ceil(fileSize / actualChunkSize)
|
|
const expiresAt = new Date(Date.now() + UPLOAD_SESSION_EXPIRY)
|
|
|
|
const session = await prisma.uploadSession.create({
|
|
data: {
|
|
userId,
|
|
fileName,
|
|
fileSize,
|
|
mimeType,
|
|
chunkSize: actualChunkSize,
|
|
totalChunks,
|
|
metadata: metadata ? JSON.stringify(metadata) : undefined,
|
|
expiresAt,
|
|
},
|
|
})
|
|
|
|
return {
|
|
...session,
|
|
uploadedChunks: session.uploadedChunks ? JSON.parse(session.uploadedChunks) : [],
|
|
metadata: session.metadata ? JSON.parse(session.metadata) : undefined,
|
|
}
|
|
}
|
|
|
|
export async function getUploadSession(uploadId: string, userId: string): Promise<UploadSession | null> {
|
|
const session = await prisma.uploadSession.findFirst({
|
|
where: {
|
|
id: uploadId,
|
|
userId,
|
|
expiresAt: {
|
|
gt: new Date(),
|
|
},
|
|
},
|
|
})
|
|
|
|
if (!session) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
...session,
|
|
uploadedChunks: session.uploadedChunks ? JSON.parse(session.uploadedChunks) : [],
|
|
metadata: session.metadata ? JSON.parse(session.metadata) : undefined,
|
|
}
|
|
}
|
|
|
|
export async function markChunkUploaded(uploadId: string, chunkIndex: number): Promise<any> {
|
|
const session = await prisma.uploadSession.findUnique({
|
|
where: { id: uploadId },
|
|
})
|
|
|
|
if (!session) {
|
|
throw new Error('Upload session not found')
|
|
}
|
|
|
|
const uploadedChunks = session.uploadedChunks ? JSON.parse(session.uploadedChunks) : []
|
|
|
|
if (!uploadedChunks.includes(chunkIndex)) {
|
|
uploadedChunks.push(chunkIndex)
|
|
}
|
|
|
|
const updatedSession = await prisma.uploadSession.update({
|
|
where: { id: uploadId },
|
|
data: {
|
|
uploadedChunks: JSON.stringify(uploadedChunks),
|
|
status: uploadedChunks.length >= session.totalChunks ? 'completed' : 'uploading',
|
|
},
|
|
})
|
|
|
|
return {
|
|
...updatedSession,
|
|
uploadedChunks: JSON.parse(updatedSession.uploadedChunks || '[]'),
|
|
metadata: updatedSession.metadata ? JSON.parse(updatedSession.metadata) : undefined,
|
|
}
|
|
}
|
|
|
|
export async function completeUploadSession(uploadId: string, fileId: string): Promise<any> {
|
|
const session = await prisma.uploadSession.update({
|
|
where: { id: uploadId },
|
|
data: {
|
|
status: 'completed',
|
|
fileId,
|
|
},
|
|
})
|
|
|
|
return {
|
|
...session,
|
|
uploadedChunks: session.uploadedChunks ? JSON.parse(session.uploadedChunks) : [],
|
|
metadata: session.metadata ? JSON.parse(session.metadata) : undefined,
|
|
}
|
|
}
|
|
|
|
export async function failUploadSession(uploadId: string): Promise<void> {
|
|
await prisma.uploadSession.update({
|
|
where: { id: uploadId },
|
|
data: {
|
|
status: 'failed',
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function generatePresignedUrl(
|
|
fileName: string,
|
|
mimeType: string,
|
|
expiresIn: number = 3600
|
|
): Promise<{ url: string; key: string }> {
|
|
// This is a placeholder for actual S3/CloudStorage presigned URL generation
|
|
// In a real implementation, you would use AWS SDK, Google Cloud Storage SDK, etc.
|
|
|
|
const key = `uploads/${Date.now()}-${fileName}`
|
|
|
|
// For now, return a mock URL - in production, generate a real presigned URL
|
|
const url = `${process.env.NEXT_PUBLIC_APP_URL}/api/upload/presigned-upload?key=${encodeURIComponent(key)}`
|
|
|
|
return { url, key }
|
|
}
|
|
|
|
export async function cleanupExpiredSessions(): Promise<void> {
|
|
await prisma.uploadSession.deleteMany({
|
|
where: {
|
|
expiresAt: {
|
|
lt: new Date(),
|
|
},
|
|
status: {
|
|
in: ['pending', 'uploading'],
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// Helper function to validate file type
|
|
export function validateFileType(mimeType: string, allowedTypes: string[]): boolean {
|
|
return allowedTypes.some(type => {
|
|
if (type.endsWith('/*')) {
|
|
return mimeType.startsWith(type.slice(0, -1))
|
|
}
|
|
return mimeType === type
|
|
})
|
|
}
|
|
|
|
// Helper function to validate file size
|
|
export function validateFileSize(fileSize: number, maxSize: number): boolean {
|
|
return fileSize <= maxSize
|
|
} |