diff --git a/backend/src/routes/upload.ts b/backend/src/routes/upload.ts index 16b65b0..0008d45 100644 --- a/backend/src/routes/upload.ts +++ b/backend/src/routes/upload.ts @@ -5,19 +5,28 @@ import { randomUUID } from 'crypto' import { pipeline } from 'stream/promises' const UPLOADS_DIR = process.env.UPLOADS_DIR ?? join(process.cwd(), 'uploads') +const CDN_HOST = process.env.CDN_HOST ?? 'cdn.hotelsync.ru' -// Ensure uploads directory exists -mkdirSync(UPLOADS_DIR, { recursive: true }) +const ALLOWED_FOLDERS = ['categories', 'rooms', 'hotels', 'guests'] as const +type UploadFolder = typeof ALLOWED_FOLDERS[number] const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'] const MAX_SIZE = 10 * 1024 * 1024 // 10MB +// Ensure base uploads directory exists +mkdirSync(UPLOADS_DIR, { recursive: true }) + const upload: FastifyPluginAsync = async (fastify) => { - // POST /api/upload — upload a single image, returns { url } + // POST /api/upload?folder=categories — upload a single image, returns { url } fastify.post( '/api/upload', { onRequest: [fastify.authenticate] }, async (request, reply) => { + const { folder } = request.query as { folder?: string } + const dir: UploadFolder = ALLOWED_FOLDERS.includes(folder as UploadFolder) + ? (folder as UploadFolder) + : 'rooms' + const data = await request.file({ limits: { fileSize: MAX_SIZE } }) if (!data) return reply.code(400).send({ error: 'No file provided' }) @@ -25,14 +34,16 @@ const upload: FastifyPluginAsync = async (fastify) => { return reply.code(400).send({ error: 'Only images allowed (jpg, png, webp, gif)' }) } - const ext = extname(data.filename) || '.jpg' + const subDir = join(UPLOADS_DIR, dir) + mkdirSync(subDir, { recursive: true }) + + const ext = extname(data.filename) || '.jpg' const filename = `${randomUUID()}${ext}` - const filepath = join(UPLOADS_DIR, filename) + const filepath = join(subDir, filename) await pipeline(data.file, createWriteStream(filepath)) - const cdnHost = process.env.CDN_HOST ?? 'cdn.hotelsync.ru' - return { url: `https://${cdnHost}/${filename}` } + return { url: `https://${CDN_HOST}/${dir}/${filename}` } }, ) } diff --git a/src/lib/api.ts b/src/lib/api.ts index 43f570e..4e8390b 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -346,11 +346,11 @@ export const api = { // ── File Upload ─────────────────────────────────────────────────────────── upload: { - photo: async (file: File): Promise => { + photo: async (file: File, folder: 'categories' | 'rooms' | 'hotels' | 'guests' = 'rooms'): Promise => { const token = localStorage.getItem('access_token') const fd = new FormData() fd.append('file', file) - const res = await fetch(`${BASE}/api/upload`, { + const res = await fetch(`${BASE}/api/upload?folder=${folder}`, { method: 'POST', headers: token ? { Authorization: `Bearer ${token}` } : {}, body: fd, diff --git a/src/pages/RoomCategoriesPage.tsx b/src/pages/RoomCategoriesPage.tsx index 2e79420..a2ca48f 100644 --- a/src/pages/RoomCategoriesPage.tsx +++ b/src/pages/RoomCategoriesPage.tsx @@ -59,7 +59,7 @@ function CategoryForm({ category, onClose, onSave }: CategoryFormProps) { if (!list.length) return setUploading(true) try { - const urls = await Promise.all(list.map(f => api.upload.photo(f))) + const urls = await Promise.all(list.map(f => api.upload.photo(f, 'categories'))) setPhotos(prev => { const next = [...prev, ...urls] setPhotoIdx(next.length - 1)