project-standalo-sonic-cloud/app/api/upload/complete/[uploadId]/route.ts

112 lines
3.7 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { requireAuth } from '@/lib/auth'
import { getUploadSession, completeUploadSession } from '@/lib/upload'
import { prisma } from '@/lib/prisma'
import { readFile, writeFile, mkdir, rm } from 'fs/promises'
import { join } from 'path'
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ uploadId: string }> }
) {
try {
const user = await requireAuth()
const { uploadId } = await params
// Get upload session
const uploadSession = await getUploadSession(uploadId, user.id)
if (!uploadSession) {
return NextResponse.json(
{ error: 'Upload session not found or expired' },
{ status: 404 }
)
}
if (uploadSession.status !== 'completed') {
return NextResponse.json(
{ error: 'Upload not complete. All chunks must be uploaded first.' },
{ status: 400 }
)
}
// Combine chunks into a single file
const tempDir = join(process.cwd(), 'temp', 'uploads', uploadId)
const finalPath = join(process.cwd(), 'uploads', `${uploadId}-${uploadSession.fileName}`)
// Ensure uploads directory exists
await mkdir(join(process.cwd(), 'uploads'), { recursive: true })
// Combine all chunks
const fileBuffer = Buffer.alloc(uploadSession.fileSize)
let offset = 0
for (let i = 0; i < uploadSession.totalChunks; i++) {
const chunkPath = join(tempDir, `chunk-${i}`)
const chunkData = await readFile(chunkPath)
chunkData.copy(fileBuffer, offset)
offset += chunkData.length
}
// Write the complete file
await writeFile(finalPath, fileBuffer)
// Create a database record for the uploaded file
const fileId = `file_${Date.now()}_${uploadId}`
// In a real implementation, you would:
// 1. Upload to S3/CloudStorage
// 2. Store the URL/Key in the database
// 3. Process the file (transcode audio, generate thumbnails, etc.)
// For now, we'll create a simple record
let createdRecord = null
if (uploadSession.mimeType.startsWith('audio/')) {
// Create a song record
createdRecord = await prisma.song.create({
data: {
artistId: user.id, // Assuming user is an artist
title: uploadSession.fileName.replace(/\.[^/.]+$/, ''), // Remove extension
slug: uploadSession.fileName.replace(/\.[^/.]+$/, '').toLowerCase().replace(/\s+/g, '-'),
audioUrl: `/uploads/${uploadId}-${uploadSession.fileName}`,
duration: 0, // Would be determined during processing
isPublic: false, // Default to private until processed
},
})
} else if (uploadSession.mimeType.startsWith('image/')) {
// Could create an image record or associate with artist/album
// For now, just store the file reference
}
// Mark upload session as complete with file ID
const completedSession = await completeUploadSession(uploadId, fileId)
// Clean up temporary chunks
await rm(tempDir, { recursive: true, force: true })
return NextResponse.json(
{
success: true,
fileId,
fileName: uploadSession.fileName,
fileSize: uploadSession.fileSize,
mimeType: uploadSession.mimeType,
record: createdRecord,
uploadSession: completedSession,
},
{ status: 200 }
)
} catch (error) {
console.error('Upload complete error:', error)
if (error instanceof Error && error.message === 'Unauthorized') {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
return NextResponse.json(
{ error: 'Failed to complete upload' },
{ status: 500 }
)
}
}