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 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 MAX_TOTAL_UPLOAD_BYTES = 30 * 1024 * 1024;
|
||||||
|
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
@@ -209,8 +209,9 @@ const upload = multer({
|
|||||||
fileFilter: (req, file, cb) => {
|
fileFilter: (req, file, cb) => {
|
||||||
file.originalname = fixFilename(file.originalname);
|
file.originalname = fixFilename(file.originalname);
|
||||||
const ext = path.extname(file.originalname).toLowerCase();
|
const ext = path.extname(file.originalname).toLowerCase();
|
||||||
|
const isImageExt = ALLOWED_IMAGE_EXT.has(ext);
|
||||||
if (file.fieldname === 'photo') {
|
if (file.fieldname === 'photo') {
|
||||||
if (!file.mimetype || !file.mimetype.startsWith('image/') || !ALLOWED_IMAGE_EXT.has(ext)) {
|
if (!isImageExt) {
|
||||||
return cb(new Error('Only images'));
|
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({
|
const adminUpload = multer({
|
||||||
storage: multer.diskStorage({
|
storage: multer.diskStorage({
|
||||||
destination: (_, __, cb) => {
|
destination: (_, __, cb) => {
|
||||||
@@ -264,11 +265,22 @@ function removeUpload(file) {
|
|||||||
safeUnlink(file && file.path);
|
safeUnlink(file && file.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function convertHeicPhoto(file) {
|
async function convertPhoto(file) {
|
||||||
if (!file || !file.path) return;
|
if (!file || !file.path) return;
|
||||||
const ext = (path.extname(file.originalname || '') || '').toLowerCase();
|
const ext = (path.extname(file.originalname || '') || '').toLowerCase();
|
||||||
if (ext !== '.heic' && ext !== '.heif') return;
|
if (ext !== '.heic' && ext !== '.heif' && ext !== '.jfif') return;
|
||||||
try {
|
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 outName = `${path.basename(file.path, path.extname(file.path))}.jpg`;
|
||||||
const outPath = path.join(path.dirname(file.path), outName);
|
const outPath = path.join(path.dirname(file.path), outName);
|
||||||
const jpeg = await heicConvert({ buffer: fs.readFileSync(file.path), format: 'JPEG', quality: 0.85 });
|
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.filename = outName;
|
||||||
file.originalname = outName;
|
file.originalname = outName;
|
||||||
} catch (e) {
|
} 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) => {
|
groupPhotoUpload(req, res, async (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.code === 'LIMIT_FILE_SIZE') return res.status(400).json({ error: 'Файл слишком большой (макс. 10 МБ)' });
|
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 и т.п. запрещены)' });
|
if (err.message === 'Not allowed extension') return res.status(400).json({ error: 'Недопустимый тип файла (*.html, *.js, *.svg и т.п. запрещены)' });
|
||||||
return res.status(400).json({ error: 'Недопустимый файл' });
|
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;
|
const { caption, taken_at } = req.body;
|
||||||
if (!req.file) return res.status(400).json({ error: 'Файл обязателен' });
|
if (!req.file) return res.status(400).json({ error: 'Файл обязателен' });
|
||||||
try {
|
try {
|
||||||
await convertHeicPhoto(req.file);
|
await convertPhoto(req.file);
|
||||||
const { rows } = await pool.query(
|
const { rows } = await pool.query(
|
||||||
`INSERT INTO group_photos (group_id, photo_path, caption, taken_at)
|
`INSERT INTO group_photos (group_id, photo_path, caption, taken_at)
|
||||||
VALUES ($1, $2, $3, $4) RETURNING *`,
|
VALUES ($1, $2, $3, $4) RETURNING *`,
|
||||||
@@ -2124,7 +2136,7 @@ app.post('/api/entries', entryLimiter, (req, res, next) => {
|
|||||||
entryFields(req, res, (err) => {
|
entryFields(req, res, (err) => {
|
||||||
if (!err) return next();
|
if (!err) return next();
|
||||||
if (err.code === 'LIMIT_FILE_SIZE') return res.status(400).json({ error: 'Файл слишком большой (макс. 10 МБ)' });
|
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 и т.п. запрещены)' });
|
if (err.message === 'Not allowed extension') return res.status(400).json({ error: 'Недопустимый тип файла (*.html, *.js, *.svg и т.п. запрещены)' });
|
||||||
return res.status(400).json({ error: 'Недопустимый файл' });
|
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} минут` });
|
return res.status(429).json({ error: `Уже ответили: подождите ${intervalMin} минут` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await convertHeicPhoto(photo);
|
await convertPhoto(photo);
|
||||||
const photo_path = photo ? `/uploads/${photo.filename}` : null;
|
const photo_path = photo ? `/uploads/${photo.filename}` : null;
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user