Add JFIF to JPG conversion for student photo uploads
This commit is contained in:
@@ -190,7 +190,7 @@ function fixFilename(str) {
|
||||
}
|
||||
|
||||
const BLOCKED_EXT = /\.(?:html?|js|mjs|cjs|svg|xml|json|map|wasm|php\d?|phtml|asp|aspx|jsp|sh|bat|cmd|cgi|exe|dll|com|msi|scr|hta|vbs|py|r|rb|htaccess)$/i;
|
||||
const ALLOWED_IMAGE_EXT = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.avif', '.ico', '.heic', '.heif']);
|
||||
const ALLOWED_IMAGE_EXT = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.avif', '.ico', '.heic', '.heif', '.jfif']);
|
||||
const MAX_TOTAL_UPLOAD_BYTES = 30 * 1024 * 1024;
|
||||
|
||||
const upload = multer({
|
||||
@@ -209,8 +209,9 @@ const upload = multer({
|
||||
fileFilter: (req, file, cb) => {
|
||||
file.originalname = fixFilename(file.originalname);
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
const isImageExt = ALLOWED_IMAGE_EXT.has(ext);
|
||||
if (file.fieldname === 'photo') {
|
||||
if (!file.mimetype || !file.mimetype.startsWith('image/') || !ALLOWED_IMAGE_EXT.has(ext)) {
|
||||
if (!isImageExt) {
|
||||
return cb(new Error('Only images'));
|
||||
}
|
||||
}
|
||||
@@ -219,7 +220,7 @@ const upload = multer({
|
||||
},
|
||||
});
|
||||
|
||||
const ADMIN_ALLOWED_EXT = new Set(['.pdf', '.doc', '.docx', '.txt', '.md', '.html', '.htm', '.zip', '.rar', '.7z', '.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.avif', '.heic', '.heif']);
|
||||
const ADMIN_ALLOWED_EXT = new Set(['.pdf', '.doc', '.docx', '.txt', '.md', '.html', '.htm', '.zip', '.rar', '.7z', '.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.avif', '.heic', '.heif', '.jfif']);
|
||||
const adminUpload = multer({
|
||||
storage: multer.diskStorage({
|
||||
destination: (_, __, cb) => {
|
||||
@@ -264,11 +265,22 @@ function removeUpload(file) {
|
||||
safeUnlink(file && file.path);
|
||||
}
|
||||
|
||||
async function convertHeicPhoto(file) {
|
||||
async function convertPhoto(file) {
|
||||
if (!file || !file.path) return;
|
||||
const ext = (path.extname(file.originalname || '') || '').toLowerCase();
|
||||
if (ext !== '.heic' && ext !== '.heif') return;
|
||||
if (ext !== '.heic' && ext !== '.heif' && ext !== '.jfif') return;
|
||||
try {
|
||||
if (ext === '.jfif') {
|
||||
// JFIF is already JPEG, just rename to .jpg for consistency
|
||||
const outName = `${path.basename(file.path, path.extname(file.path))}.jpg`;
|
||||
const outPath = path.join(path.dirname(file.path), outName);
|
||||
fs.renameSync(file.path, outPath);
|
||||
file.path = outPath;
|
||||
file.filename = outName;
|
||||
file.originalname = outName;
|
||||
return;
|
||||
}
|
||||
// HEIC/HEIF conversion
|
||||
const outName = `${path.basename(file.path, path.extname(file.path))}.jpg`;
|
||||
const outPath = path.join(path.dirname(file.path), outName);
|
||||
const jpeg = await heicConvert({ buffer: fs.readFileSync(file.path), format: 'JPEG', quality: 0.85 });
|
||||
@@ -278,7 +290,7 @@ async function convertHeicPhoto(file) {
|
||||
file.filename = outName;
|
||||
file.originalname = outName;
|
||||
} catch (e) {
|
||||
console.error('HEIC convert failed:', e);
|
||||
console.error('Photo convert failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1431,7 +1443,7 @@ app.post('/api/groups/:id/photos', requireAuth, (req, res, next) => {
|
||||
groupPhotoUpload(req, res, async (err) => {
|
||||
if (err) {
|
||||
if (err.code === 'LIMIT_FILE_SIZE') return res.status(400).json({ error: 'Файл слишком большой (макс. 10 МБ)' });
|
||||
if (err.message === 'Only images') return res.status(400).json({ error: 'Фото: допустимы только изображения (jpg, png, gif, webp, bmp, avif, ico, heic, heif)' });
|
||||
if (err.message === 'Only images') return res.status(400).json({ error: 'Фото: допустимы только изображения (jpg, png, gif, webp, bmp, avif, ico, heic, heif, jfif)' });
|
||||
if (err.message === 'Not allowed extension') return res.status(400).json({ error: 'Недопустимый тип файла (*.html, *.js, *.svg и т.п. запрещены)' });
|
||||
return res.status(400).json({ error: 'Недопустимый файл' });
|
||||
}
|
||||
@@ -1450,7 +1462,7 @@ app.post('/api/groups/:id/photos', requireAuth, (req, res, next) => {
|
||||
const { caption, taken_at } = req.body;
|
||||
if (!req.file) return res.status(400).json({ error: 'Файл обязателен' });
|
||||
try {
|
||||
await convertHeicPhoto(req.file);
|
||||
await convertPhoto(req.file);
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO group_photos (group_id, photo_path, caption, taken_at)
|
||||
VALUES ($1, $2, $3, $4) RETURNING *`,
|
||||
@@ -2124,7 +2136,7 @@ app.post('/api/entries', entryLimiter, (req, res, next) => {
|
||||
entryFields(req, res, (err) => {
|
||||
if (!err) return next();
|
||||
if (err.code === 'LIMIT_FILE_SIZE') return res.status(400).json({ error: 'Файл слишком большой (макс. 10 МБ)' });
|
||||
if (err.message === 'Only images') return res.status(400).json({ error: 'Фото: допустимы только изображения (jpg, png, gif, webp, bmp, avif, ico, heic, heif)' });
|
||||
if (err.message === 'Only images') return res.status(400).json({ error: 'Фото: допустимы только изображения (jpg, png, gif, webp, bmp, avif, ico, heic, heif, jfif)' });
|
||||
if (err.message === 'Not allowed extension') return res.status(400).json({ error: 'Недопустимый тип файла (*.html, *.js, *.svg и т.п. запрещены)' });
|
||||
return res.status(400).json({ error: 'Недопустимый файл' });
|
||||
});
|
||||
@@ -2172,7 +2184,7 @@ app.post('/api/entries', entryLimiter, (req, res, next) => {
|
||||
return res.status(429).json({ error: `Уже ответили: подождите ${intervalMin} минут` });
|
||||
}
|
||||
}
|
||||
await convertHeicPhoto(photo);
|
||||
await convertPhoto(photo);
|
||||
const photo_path = photo ? `/uploads/${photo.filename}` : null;
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user