feat: update file upload paths and add dynamic file retrieval endpoint

This commit is contained in:
2025-10-18 20:05:11 +02:00
parent fc13c94d5f
commit 1994ed5810
7 changed files with 54 additions and 4 deletions

1
.gitignore vendored
View File

@@ -54,6 +54,7 @@ prisma/dev.db
uploads/
uploads/*
!app/api/uploads
data/
data/*

View File

@@ -9,4 +9,4 @@ ENV NEXTAUTH_SECRET=your_super_secret_key_here_change_in_production
RUN npx prisma generate
RUN pnpm run build
EXPOSE 3000
CMD ["sh", "-c", "mkdir -p /app/prisma/data && npx prisma db push && pnpm start"]
CMD ["sh", "-c", "mkdir -p /app/prisma/data && mkdir -p /app/uploads && npx prisma db push && pnpm start"]

View File

@@ -27,10 +27,10 @@ export async function POST(request: NextRequest) {
const buffer = Buffer.from(bytes);
const filename = `${Date.now()}-${file.name}`;
const filepath = path.join(process.cwd(), 'public', 'uploads', filename);
const filepath = path.join(process.cwd(), 'uploads', filename);
await mkdir(path.dirname(filepath), { recursive: true });
await writeFile(filepath, buffer);
return NextResponse.json({ path: `/uploads/${filename}` });
return NextResponse.json({ path: `/api/uploads/${filename}` });
}

View File

@@ -0,0 +1,31 @@
import { NextRequest, NextResponse } from 'next/server';
import { readFile } from 'fs/promises';
import path from 'path';
import mime from 'mime-types';
export const dynamic = 'force-dynamic';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ filename: string }> },
) {
const { filename } = await params;
const filepath = path.join(process.cwd(), 'uploads', filename);
try {
const file = await readFile(filepath);
const mimeType = mime.lookup(filename) || 'application/octet-stream';
const uint8Array = new Uint8Array(file);
return new NextResponse(uint8Array as any, {
headers: {
'Content-Type': mimeType,
'Cache-Control': 'public, max-age=31536000',
},
});
} catch (error) {
return NextResponse.json({ error: 'File not found' }, { status: 404 });
}
}

View File

@@ -5,7 +5,7 @@ services:
- '3000:3000'
volumes:
- ./data:/app/prisma/data
- ./uploads:/app/public/uploads
- ./uploads:/app/uploads
environment:
- NEXTAUTH_SECRET=your_secret_key
- NEXTAUTH_URL=http://localhost:3000

View File

@@ -28,6 +28,7 @@
"date-fns": "^4.1.0",
"exceljs": "^4.4.0",
"lucide-react": "^0.546.0",
"mime-types": "^3.0.1",
"next": "15.5.6",
"next-auth": "^4.24.11",
"next-themes": "^0.4.6",

17
pnpm-lock.yaml generated
View File

@@ -53,6 +53,9 @@ importers:
lucide-react:
specifier: ^0.546.0
version: 0.546.0(react@19.1.0)
mime-types:
specifier: ^3.0.1
version: 3.0.1
next:
specifier: 15.5.6
version: 15.5.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1518,10 +1521,18 @@ packages:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mime-types@3.0.1:
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
engines: {node: '>= 0.6'}
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -3465,10 +3476,16 @@ snapshots:
mime-db@1.52.0: {}
mime-db@1.54.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
mime-types@3.0.1:
dependencies:
mime-db: 1.54.0
mimic-fn@2.1.0: {}
minimatch@3.1.2: