Fix image upload 500 in production: use UPLOAD_DIR env for absolute path
Deploy to LXC / deploy (push) Successful in 20s
Deploy to LXC / deploy (push) Successful in 20s
In production with adapter-node, the relative path 'static/uploads' doesn't resolve correctly. Now uses UPLOAD_DIR env variable (absolute path) for both saving and serving uploaded files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { writeFile, unlink, mkdir } from 'fs/promises';
|
import { writeFile, unlink, mkdir } from 'fs/promises';
|
||||||
import { join, extname } from 'path';
|
import { join, extname, resolve } from 'path';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
const UPLOAD_BASE = 'static/uploads';
|
const UPLOAD_BASE = resolve(env.UPLOAD_DIR || 'static/uploads');
|
||||||
const THUMBNAIL_WIDTH = 300;
|
const THUMBNAIL_WIDTH = 300;
|
||||||
|
|
||||||
const ALLOWED_IMAGE_TYPES = [
|
const ALLOWED_IMAGE_TYPES = [
|
||||||
@@ -67,8 +68,9 @@ export async function saveDocument(file: File): Promise<{ filePath: string; orig
|
|||||||
|
|
||||||
export async function deleteFile(filePath: string): Promise<void> {
|
export async function deleteFile(filePath: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// filePath is like /uploads/devices/xxx.jpg, need to prepend static/
|
// filePath is like /uploads/devices/xxx.jpg — strip /uploads/ prefix and join with UPLOAD_BASE
|
||||||
const fullPath = join('static', filePath);
|
const relativePath = filePath.replace(/^\/uploads\//, '');
|
||||||
|
const fullPath = join(UPLOAD_BASE, relativePath);
|
||||||
await unlink(fullPath);
|
await unlink(fullPath);
|
||||||
} catch {
|
} catch {
|
||||||
// File may already be deleted
|
// File may already be deleted
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import { join, extname } from 'path';
|
import { join, extname, resolve } from 'path';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
const MIME_TYPES: Record<string, string> = {
|
const MIME_TYPES: Record<string, string> = {
|
||||||
'.jpg': 'image/jpeg',
|
'.jpg': 'image/jpeg',
|
||||||
@@ -21,7 +22,8 @@ export const GET: RequestHandler = async ({ params }) => {
|
|||||||
// Prevent directory traversal
|
// Prevent directory traversal
|
||||||
if (filePath.includes('..')) error(400, 'Invalid path');
|
if (filePath.includes('..')) error(400, 'Invalid path');
|
||||||
|
|
||||||
const fullPath = join('static', 'uploads', filePath);
|
const uploadBase = resolve(env.UPLOAD_DIR || 'static/uploads');
|
||||||
|
const fullPath = join(uploadBase, filePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await readFile(fullPath);
|
const data = await readFile(fullPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user