diff --git a/.gitignore b/.gitignore index 983249c..e66a9fd 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ yarn-error.log* # uploads (user content) /public/uploads/ +/data/uploads/ # env files (can opt-in for committing if needed) .env* diff --git a/Dockerfile b/Dockerfile index 840b398..8591de8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static -RUN mkdir -p /app/data /app/public/uploads && chmod 777 /app/data /app/public/uploads +RUN mkdir -p /app/data /app/data/uploads && chmod 777 /app/data /app/data/uploads USER nextjs diff --git a/app/api/images/route.ts b/app/api/images/route.ts index fb91364..c17d31f 100644 --- a/app/api/images/route.ts +++ b/app/api/images/route.ts @@ -7,7 +7,7 @@ import path from "path"; import { v4 as uuidv4 } from "uuid"; import { eq } from "drizzle-orm"; -const UPLOAD_DIR = path.join(process.cwd(), "public", "uploads", "images"); +const UPLOAD_DIR = path.join(process.cwd(), "data", "uploads", "images"); const ALLOWED_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]; const MAX_SIZE = 5 * 1024 * 1024; diff --git a/app/api/sounds/route.ts b/app/api/sounds/route.ts index 9910f75..ce7c3d4 100644 --- a/app/api/sounds/route.ts +++ b/app/api/sounds/route.ts @@ -10,7 +10,7 @@ import { convertToCompressedMp3 } from "@/lib/convert"; export const maxDuration = 120; -const UPLOAD_DIR = path.join(process.cwd(), "public", "uploads", "sounds"); +const UPLOAD_DIR = path.join(process.cwd(), "data", "uploads", "sounds"); const ALLOWED_EXTENSIONS = /\.(mp3|ogg|wav|flac|aac|m4a|wma|opus|webm|mp4|avi|mov|mkv|webm|wmv|flv)$/i; const MAX_SIZE = 50 * 1024 * 1024; // 50 MB diff --git a/app/api/upload/sound/route.ts b/app/api/upload/sound/route.ts index a992925..bd28412 100644 --- a/app/api/upload/sound/route.ts +++ b/app/api/upload/sound/route.ts @@ -9,7 +9,7 @@ import { convertToCompressedMp3 } from "@/lib/convert"; export const maxDuration = 60; -const UPLOAD_DIR = path.join(process.cwd(), "public", "uploads", "sounds"); +const UPLOAD_DIR = path.join(process.cwd(), "data", "uploads", "sounds"); const ALLOWED_EXTENSIONS = /\.(mp3|ogg|wav|flac|aac|m4a|wma|opus|webm|mp4|avi|mov|mkv|webm|wmv|flv)$/i; const MAX_SIZE = 50 * 1024 * 1024; // 50 MB diff --git a/app/uploads/[...path]/route.ts b/app/uploads/[...path]/route.ts new file mode 100644 index 0000000..24246fe --- /dev/null +++ b/app/uploads/[...path]/route.ts @@ -0,0 +1,49 @@ +import { NextRequest, NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; + +const UPLOADS_DIR = path.join(process.cwd(), "data", "uploads"); + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ path: string[] }> } +) { + const { path: segments } = await params; + const filePath = path.join(UPLOADS_DIR, ...segments); + + if (!filePath.startsWith(UPLOADS_DIR)) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + + try { + const content = fs.readFileSync(filePath); + const ext = path.extname(filePath).toLowerCase(); + const mime = MIME_MAP[ext] ?? "application/octet-stream"; + return new NextResponse(content, { + headers: { + "Content-Type": mime, + "Cache-Control": "public, max-age=31536000, immutable", + }, + }); + } catch { + return NextResponse.json({ error: "Not found" }, { status: 404 }); + } +} + +const MIME_MAP: Record = { + ".mp3": "audio/mpeg", + ".ogg": "audio/ogg", + ".wav": "audio/wav", + ".flac": "audio/flac", + ".m4a": "audio/mp4", + ".webm": "audio/webm", + ".mp4": "video/mp4", + ".avi": "video/x-msvideo", + ".mov": "video/quicktime", + ".mkv": "video/x-matroska", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", +}; diff --git a/docker-compose.yml b/docker-compose.yml index 4a26f73..af98a88 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: - "80:3000" volumes: - ./data:/app/data - - ./uploads:/app/public/uploads + - ./uploads:/app/data/uploads environment: - NODE_ENV=production - FREESOUND_API_KEY=${FREESOUND_API_KEY}