fix: serve uploads via route handler instead of public/ (standalone 404 fix)
All checks were successful
Deploy / build-and-deploy (push) Successful in 1m49s
All checks were successful
Deploy / build-and-deploy (push) Successful in 1m49s
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,6 +37,7 @@ yarn-error.log*
|
|||||||
|
|
||||||
# uploads (user content)
|
# uploads (user content)
|
||||||
/public/uploads/
|
/public/uploads/
|
||||||
|
/data/uploads/
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env*
|
||||||
|
|||||||
@@ -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/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
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
|
USER nextjs
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import path from "path";
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { eq } from "drizzle-orm";
|
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 ALLOWED_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
||||||
const MAX_SIZE = 5 * 1024 * 1024;
|
const MAX_SIZE = 5 * 1024 * 1024;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { convertToCompressedMp3 } from "@/lib/convert";
|
|||||||
|
|
||||||
export const maxDuration = 120;
|
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 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
|
const MAX_SIZE = 50 * 1024 * 1024; // 50 MB
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { convertToCompressedMp3 } from "@/lib/convert";
|
|||||||
|
|
||||||
export const maxDuration = 60;
|
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 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
|
const MAX_SIZE = 50 * 1024 * 1024; // 50 MB
|
||||||
|
|
||||||
|
|||||||
49
app/uploads/[...path]/route.ts
Normal file
49
app/uploads/[...path]/route.ts
Normal file
@@ -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<string, string> = {
|
||||||
|
".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",
|
||||||
|
};
|
||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
- "80:3000"
|
- "80:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
- ./uploads:/app/public/uploads
|
- ./uploads:/app/data/uploads
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- FREESOUND_API_KEY=${FREESOUND_API_KEY}
|
- FREESOUND_API_KEY=${FREESOUND_API_KEY}
|
||||||
|
|||||||
Reference in New Issue
Block a user