Spaces:
Runtime error
Runtime error
Download apps/server/src/middleware/upload.js from moustafa7/flwo: direct link, hf CLI and curl.
- Browser
- Download file 6.31 kB
-
https://huggingface.co/spaces/moustafa7/flwo/resolve/main/apps/server/src/middleware/upload.js
- Command line
-
hf download hf://spaces/moustafa7/flwo/apps/server/src/middleware/upload.js
-
curl -L -o upload.js https://huggingface.co/spaces/moustafa7/flwo/resolve/main/apps/server/src/middleware/upload.js
6.31 kB
| import multer from 'multer' | |
| import path from 'path' | |
| import fs from 'fs' | |
| import { fileURLToPath } from 'url' | |
| import { AppError } from '../lib/AppError.js' | |
| import { logger } from '../lib/logger.js' | |
| const ALLOWED_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.webp', '.mp4', '.webm'] | |
| const ALLOWED_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.webp'] | |
| const MAGIC_BYTES = { | |
| 'image/jpeg': { header: [0xFF, 0xD8, 0xFF], offset: 0 }, | |
| 'image/png': { header: [0x89, 0x50, 0x4E, 0x47], offset: 0 }, | |
| 'image/webp': { header: [0x52, 0x49, 0x46, 0x46], offset: 0, tail: [0x57, 0x45, 0x42, 0x50], tailOffset: 8 }, | |
| 'video/mp4': { header: [0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70], offset: 0 }, | |
| 'video/webm': { header: [0x1A, 0x45, 0xDF, 0xA3], offset: 0 }, | |
| } | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)) | |
| const PERSISTENT_DIR = '/data/uploads' | |
| const LOCAL_DIR = path.resolve(__dirname, '../../uploads') | |
| const uploadsDir = fs.existsSync('/data') ? PERSISTENT_DIR : LOCAL_DIR | |
| if (uploadsDir === PERSISTENT_DIR && fs.existsSync(LOCAL_DIR)) { | |
| try { | |
| const entries = fs.readdirSync(LOCAL_DIR) | |
| for (const entry of entries) { | |
| const src = path.join(LOCAL_DIR, entry) | |
| const dst = path.join(PERSISTENT_DIR, entry) | |
| if (!fs.existsSync(dst)) { | |
| fs.cpSync(src, dst, { recursive: true }) | |
| } | |
| } | |
| logger.info('Migrated existing uploads to /data/uploads') | |
| } catch (e) { | |
| logger.warn(e, 'Could not migrate uploads') | |
| } | |
| } | |
| if (!fs.existsSync(uploadsDir)) { | |
| fs.mkdirSync(uploadsDir, { recursive: true }) | |
| } | |
| logger.info('Uploads directory: %s', uploadsDir) | |
| const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'] | |
| const ALLOWED_MEDIA_MIMES = ['image/jpeg', 'image/png', 'image/webp', 'video/mp4', 'video/webm'] | |
| const MAX_FILE_SIZE = 5 * 1024 * 1024 | |
| const MAX_VIDEO_SIZE = 50 * 1024 * 1024 | |
| function checkExtension(filename, allowedExts) { | |
| const ext = path.extname(filename).toLowerCase() | |
| if (!allowedExts.includes(ext)) { | |
| throw new AppError(`ุงู ุชุฏุงุฏ ุงูู ูู ุบูุฑ ู ุณู ูุญ: ${ext}`, 400, 'INVALID_FILE_EXTENSION') | |
| } | |
| } | |
| function fileFilter(req, file, cb) { | |
| try { | |
| checkExtension(file.originalname, ALLOWED_IMAGE_EXTENSIONS) | |
| if (!ALLOWED_MIME_TYPES.includes(file.mimetype)) { | |
| throw new AppError('ููุน ุงูู ูู ุบูุฑ ู ุณู ูุญ (ููุท JPEG, PNG, WebP)', 400, 'INVALID_FILE_TYPE') | |
| } | |
| cb(null, true) | |
| } catch (err) { cb(err) } | |
| } | |
| function mediaFilter(req, file, cb) { | |
| try { | |
| checkExtension(file.originalname, ALLOWED_EXTENSIONS) | |
| if (!ALLOWED_MEDIA_MIMES.includes(file.mimetype)) { | |
| throw new AppError('ููุน ุงูู ูู ุบูุฑ ู ุณู ูุญ (ุตูุฑ ุฃู ููุฏูู ููุท)', 400, 'INVALID_FILE_TYPE') | |
| } | |
| cb(null, true) | |
| } catch (err) { cb(err) } | |
| } | |
| function makeStorage(folder) { | |
| const dest = path.join(uploadsDir, folder) | |
| if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true }) | |
| return multer.diskStorage({ | |
| destination: (req, file, cb) => cb(null, dest), | |
| filename: (req, file, cb) => { | |
| const unique = `${Date.now()}-${Math.round(Math.random() * 1e9)}` | |
| cb(null, `${unique}${path.extname(file.originalname)}`) | |
| } | |
| }) | |
| } | |
| export function validateFileContent(req, res, next) { | |
| if (!req.file) return next() | |
| const filePath = req.file.path | |
| try { | |
| const def = MAGIC_BYTES[req.file.mimetype] | |
| if (!def) return next(new AppError('ููุน ุงูู ูู ุบูุฑ ู ุนุฑูู', 400, 'INVALID_FILE_TYPE')) | |
| const headerLen = def.header.length | |
| const tailLen = def.tail?.length || 0 | |
| const totalLen = Math.max(headerLen, (def.tailOffset || 0) + tailLen) | |
| const buffer = Buffer.alloc(totalLen) | |
| const fd = fs.openSync(filePath, 'r') | |
| fs.readSync(fd, buffer, 0, totalLen, 0) | |
| fs.closeSync(fd) | |
| const match = def.header.every((byte, i) => byte === 0 || buffer[i] === byte) | |
| const tailMatch = !def.tail || def.tail.every((byte, i) => byte === 0 || buffer[(def.tailOffset || 0) + i] === byte) | |
| if (!match || !tailMatch) { | |
| fs.unlinkSync(filePath) | |
| return next(new AppError('ู ุญุชูู ุงูู ูู ูุง ูุชุทุงุจู ู ุน ููุนู', 400, 'INVALID_FILE_CONTENT')) | |
| } | |
| next() | |
| } catch { | |
| next(new AppError('ุฎุทุฃ ูู ุงูุชุญูู ู ู ุงูู ูู', 500, 'FILE_VALIDATION_ERROR')) | |
| } | |
| } | |
| export function toPublicUrl(absolutePath) { | |
| if (!absolutePath) return '' | |
| const normalized = absolutePath.replace(/\\/g, '/') | |
| const idx = normalized.indexOf('/uploads/') | |
| return idx !== -1 ? normalized.slice(idx) : normalized | |
| } | |
| export const uploadAvatar = multer({ | |
| storage: makeStorage('avatars'), | |
| limits: { fileSize: MAX_FILE_SIZE }, | |
| fileFilter, | |
| }).single('avatar') | |
| export const uploadTemplateImage = multer({ | |
| storage: makeStorage('templates'), | |
| limits: { fileSize: MAX_FILE_SIZE * 2 }, | |
| fileFilter, | |
| }).single('preview') | |
| export const uploadMedia = multer({ | |
| storage: makeStorage('media'), | |
| limits: { fileSize: MAX_VIDEO_SIZE }, | |
| fileFilter: mediaFilter, | |
| }).single('file') | |
| const ALLOWED_SOURCE_EXTS = ['.zip', '.tar.gz', '.gz'] | |
| const ALLOWED_MANIFEST_EXTS = ['.json'] | |
| function sourceFilter(req, file, cb) { | |
| const ext = path.extname(file.originalname).toLowerCase() | |
| if (ext === '.zip' || ext === '.gz') return cb(null, true) | |
| const name = file.originalname.toLowerCase() | |
| if (name.endsWith('.tar.gz')) return cb(null, true) | |
| cb(new AppError('ุงู ุชุฏุงุฏ ุงูู ูู ุบูุฑ ู ุณู ูุญ (ZIP ููุท)', 400, 'INVALID_FILE_EXTENSION')) | |
| } | |
| export const uploadSource = multer({ | |
| storage: makeStorage('source'), | |
| limits: { fileSize: 200 * 1024 * 1024 }, | |
| fileFilter: sourceFilter, | |
| }).single('source') | |
| export const uploadManifest = multer({ | |
| storage: makeStorage('manifests'), | |
| limits: { fileSize: 1024 * 1024 }, | |
| fileFilter: (req, file, cb) => { | |
| if (path.extname(file.originalname).toLowerCase() === '.json') return cb(null, true) | |
| cb(new AppError('manifest.json ููุท', 400, 'INVALID_FILE_EXTENSION')) | |
| }, | |
| }).single('manifest') | |
| export const uploadTemplateZip = multer({ | |
| storage: makeStorage('templates-preview'), | |
| limits: { fileSize: 50 * 1024 * 1024 }, | |
| fileFilter: (req, file, cb) => { | |
| if (path.extname(file.originalname).toLowerCase() === '.zip') return cb(null, true) | |
| cb(new AppError('ZIP ููุท', 400, 'INVALID_FILE_EXTENSION')) | |
| }, | |
| }).single('htmlZip') | |