diff --git a/frontend/src/app/api/auth/avatar/route.ts b/frontend/src/app/api/auth/avatar/route.ts index 3c8b278..f20efc3 100644 --- a/frontend/src/app/api/auth/avatar/route.ts +++ b/frontend/src/app/api/auth/avatar/route.ts @@ -72,11 +72,8 @@ export async function POST(request: NextRequest) { } ); - // Generate public URL - const protocol = process.env.MINIO_USE_SSL === 'true' ? 'https' : 'http'; - const endpoint = process.env.MINIO_ENDPOINT || 'localhost'; - const port = process.env.MINIO_PORT || '9000'; - const avatarUrl = `${protocol}://${endpoint}:${port}/${BUCKET_NAME}/${fileName}`; + const avatarPath = fileName; + const avatarUrl = `/api/files/${avatarPath}`; // Delete old avatar if exists const user = await prisma.user.findUnique({ @@ -86,10 +83,17 @@ export async function POST(request: NextRequest) { if (user?.avatar) { try { - // Extract filename from URL - const oldFileName = user.avatar.split(`${BUCKET_NAME}/`)[1]; - if (oldFileName) { - await minioClient.removeObject(BUCKET_NAME, oldFileName); + const sanitized = user.avatar.replace(/^https?:\/\/[^/]+/i, ''); + let objectKey: string | undefined; + + if (sanitized.startsWith('/api/files/')) { + objectKey = sanitized.replace('/api/files/', ''); + } else if (sanitized.includes(`${BUCKET_NAME}/`)) { + objectKey = sanitized.split(`${BUCKET_NAME}/`)[1]; + } + + if (objectKey) { + await minioClient.removeObject(BUCKET_NAME, objectKey); } } catch (error) { console.error('Error deleting old avatar:', error); diff --git a/frontend/src/app/api/files/[...path]/route.ts b/frontend/src/app/api/files/[...path]/route.ts new file mode 100644 index 0000000..e3af25e --- /dev/null +++ b/frontend/src/app/api/files/[...path]/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { minioClient, bucketName } from '@/lib/minio'; +import { Readable } from 'stream'; + +export async function GET( + _request: NextRequest, + { params }: { params: Promise<{ path: string[] }> } +) { + const { path } = await params; + const key = path.join('/'); + + if (!key) { + return NextResponse.json({ error: 'Arquivo não especificado' }, { status: 400 }); + } + + try { + const object = await minioClient.getObject(bucketName, key); + + if (!object || !object.Body) { + return NextResponse.json({ error: 'Arquivo não encontrado' }, { status: 404 }); + } + + const contentType = object.ContentType || 'application/octet-stream'; + const body = object.Body instanceof Readable ? Readable.toWeb(object.Body) : object.Body; + + return new Response(body as ReadableStream, { + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=31536000, immutable', + }, + }); + } catch (error) { + console.error('[files] Erro ao buscar arquivo:', error); + return NextResponse.json({ error: 'Erro ao buscar arquivo' }, { status: 500 }); + } +} diff --git a/frontend/src/app/api/upload/route.ts b/frontend/src/app/api/upload/route.ts index 6f4ffe8..ab1f071 100644 --- a/frontend/src/app/api/upload/route.ts +++ b/frontend/src/app/api/upload/route.ts @@ -20,11 +20,9 @@ export async function POST(request: Request) { 'Content-Type': file.type, }); - // Construct public URL - // In a real production env, this should be an env var like NEXT_PUBLIC_STORAGE_URL - const url = `http://localhost:9000/${bucketName}/${filename}`; + const url = `/api/files/${filename}`; - return NextResponse.json({ url }); + return NextResponse.json({ url, path: filename }); } catch (error) { console.error('Upload error:', error); return NextResponse.json({ error: 'Error uploading file' }, { status: 500 }); diff --git a/frontend/src/app/api/users/avatar/route.ts b/frontend/src/app/api/users/avatar/route.ts index 88c3486..2a2733c 100644 --- a/frontend/src/app/api/users/avatar/route.ts +++ b/frontend/src/app/api/users/avatar/route.ts @@ -80,11 +80,8 @@ export async function POST(request: NextRequest) { } ); - // Generate public URL - const protocol = process.env.MINIO_USE_SSL === 'true' ? 'https' : 'http'; - const endpoint = process.env.MINIO_ENDPOINT || 'localhost'; - const port = process.env.MINIO_PORT || '9000'; - const avatarUrl = `${protocol}://${endpoint}:${port}/${BUCKET_NAME}/${fileName}`; + const avatarPath = fileName; + const avatarUrl = `/api/files/${avatarPath}`; // Delete old avatar if exists const user = await prisma.user.findUnique({ @@ -94,10 +91,17 @@ export async function POST(request: NextRequest) { if (user?.avatar) { try { - // Extract filename from URL - const oldFileName = user.avatar.split(`${BUCKET_NAME}/`)[1]; - if (oldFileName) { - await minioClient.removeObject(BUCKET_NAME, oldFileName); + const sanitized = user.avatar.replace(/^https?:\/\/[^/]+/i, ''); + let objectKey: string | undefined; + + if (sanitized.startsWith('/api/files/')) { + objectKey = sanitized.replace('/api/files/', ''); + } else if (sanitized.includes(`${BUCKET_NAME}/`)) { + objectKey = sanitized.split(`${BUCKET_NAME}/`)[1]; + } + + if (objectKey) { + await minioClient.removeObject(BUCKET_NAME, objectKey); } } catch (error) { console.error('Error deleting old avatar:', error); diff --git a/frontend/src/lib/minio.ts b/frontend/src/lib/minio.ts index 0235dd2..f7f2fa5 100644 --- a/frontend/src/lib/minio.ts +++ b/frontend/src/lib/minio.ts @@ -1,4 +1,4 @@ -import { S3Client, HeadBucketCommand, CreateBucketCommand, PutBucketPolicyCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'; +import { S3Client, HeadBucketCommand, CreateBucketCommand, PutBucketPolicyCommand, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'; const endpointHost = process.env.MINIO_ENDPOINT || 'localhost'; const port = Number.parseInt(process.env.MINIO_PORT || '9000', 10); @@ -77,6 +77,10 @@ export const minioClient = { })); }, + async getObject(bucket: string, key: string) { + return s3Client.send(new GetObjectCommand({ Bucket: bucket, Key: key })); + }, + async removeObject(bucket: string, key: string) { await s3Client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key })); },