чернетка
завантаження файлів і тд (аналог lamp сервер типу воржпес чи phpmyadmin)
Створимо папку в корені проекта і назвемо її public або statics і створимо в ній index.html і можна створити структуру файлів
їх можна роздавати назовні для користувача, але потрібно вказати в проекті, що це статичні файди для цього в index там де мідлвари пишемо
app.use(express.static('statics'));
Після цього запустивши сервер і пройшовши за посиланням ми відкриємо нашу статичного сторінку
далі розберемо як вантажити файли
для зберігання даних наприклад найпопулярніше рішення може бути https://aws.amazon.com/ru/s3/
можна накрайняк помучавщись прикрутити і гуглдрайв
Але в цьому прикдалі будемо зберігати локально файли. Але на реальних проектах коли є заливка зі сторони юзера це погана ідея, бо на сервері щвидко закінчиться місце
завантажувати файди дозволяє мідлвалка Multer
https://www.npmjs.com/package/multer
менш популярний, але також вживаний пакет (простіший синтаксис, але менш популярний і ведикий розмір має)
https://www.npmjs.com/package/express-fileupload
встановимо малтер
npm i multer
бібліотека шарп, яка утискає картинки перед завантаженням
https://www.npmjs.com/package/sharp
npm i sharp
аналог jimp
https://www.npmjs.com/package/jimp
ПОЧАТОК
Будемо вантажити аватарку юзера. введемо такий фугкіонал при апдейті юзера. дя цьог потрібно поміняти роути
відповідно роут, мідлварка і контролер
router.patch("/update-me", uploadUserAvatar, updateMe);
Пропишемо спочатку middleware і сконфігуруємо storage (там де буде зберігатися файл). (прим є 2 варіанти - диск-сторедж і меморісторедж):
Розберемо код: Спочатку конфігурується сторедж - multerStorage. У destination налаштовуємо куди буде зберігатися файл. Передається реквест, сам файл і також колбек функція (як її назвати неважливо, головне, щоб порядок зберігся) як параметри першим іде ерорка. в даному разі ми її прирівнюємо null statics/img - показує куди буде заливатися файл.
У filename схожа функція, тілльки тут extension - отримуємо розширення переданого файлу. Тут mimetype приходить у форматі image/jpg, де перший параметр - це тип файлу, а другий - його розширення. розбивши оядок на масив по роздільнику / і взявши елемент з індексом 1 ми отримаємо розширення jpg.
Колбек-функція формує назву файлу першим ерорку робимо null. а назву файлу робимо у вигляді шаблонного рядка айдіюзера-рендомчисло.розширення.
Далі налаштовуємо фільтр малтера. Якщо у нас формат файду імідж, то пропускаємо далі, якщо ні то повертаємо помилку.
після цього наращтовуємо весь мідлвар. Вказуємо сторедж, фільтр, обмедення (модна розмір числом передати а модна перемноженням). avatar - поле, яке ми передаємо при запиті з фронта, коли передаємо файл
const multer = require("multer");
const uuid = require("uuid").v4;
// MULTER example
// config multer storage
const multerStorage = multer.diskStorage({
destination: (req, file, cbk) => {
cbk(null, "statics/img");
},
filename: (req, file, cbk) => {
const extension = file.mimetype.split("/")[1]; // jpeg/jpg/png/gif
// userID-random.fileExtension => ncdjasnhcjsadns-nuy48329qxbfy732nyfx73.jpg
cbk(null, `${req.user.id}-${uuid()}.${extension}`);
},
});
// config multer filter
const multerFilter = (req, file, cbk) => {
// 'image/*'
if (file.mimetype.startsWith("image/")) {
cbk(null, true);
} else {
cbk(new AppError(400, "Please, upload images only!"), false);
}
};
exports.uploadUserAvatar = multer({
storage: multerStorage,
fileFilter: multerFilter,
limits: {
fileSize: 1 * 1024 * 1024,
},
}).single("avatar");
Пропишемо контролер: У ньому оновимо поля які прийшли і подивимося в консолі що це за файл який приходить з реквесту
exports.updateMe = catchAsync(async (req, res) => {
const { user, file } = req;
if (file) {
console.log(file);
}
Object.keys(req.body).forEach((key) => {
user[key] = req.body[key];
});
const updatedUser = await user.save();
res.status(200).json({
user: updatedUser,
});
});
відправимо patch (а в налаштуваннях виставляємо body - form data)
http://localhost:3000/users/update-me
перевіримо змінимо спочатку ім'я і перевіримо, що змінилося у базі даних.
А коли хочемо передати файл то треба вибрати "файл" І вказати шлях до файлу як ніби це робимо на фронт енд
якщо скаже, що немає папки img, то треба її створити в проекті у папці статікс і відправити файл переконатися, що вона фізично заливається у папку.
А також глянути в консолі що приходить з реквеста у файл
Поки що просто завантажується файл, але не записується ніде в базу даних.
створимо автоматичне генерування аватарки для юзера
https://codingforseo.com/blog/generate-gravatar/
Для цього потрібно підправити модель юзера
Підключимо модуль крипто, який уже є в ноді
const crypto = require('crypto');
створимо в схемі окреме поле під аватар
avatar: String,
І пропишемо вбудовану мідлварку моделі перед тим як робити основні мідлвари чи контроллери
// Pre save mongoose hook. Fires on Create and Save.
userSchema.pre('save', async function(next) {
if (this.isNew) {
const emailHash = crypto.createHash('md5').update(this.email).digest('hex');
this.avatar = `https://www.gravatar.com/avatar/${emailHash}.jpg?d=retro`;
}
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
із нового ми прописали там оце
if (this.isNew) {
const emailHash = crypto.createHash('md5').update(this.email).digest('hex');
this.avatar = `https://www.gravatar.com/avatar/${emailHash}.jpg?d=retro`;
}
розберемо цей код:
якшо користувач створюється новий, то створимо хеш по імейлу і пропишемо у значення avatar посилання на зшенерований аватар. також у лінку можна вказати стиль створеного аватаара.
після цього через програму postman спробуємо зареєструвати юзера і перевірити чи згенерується йому аватарка
це буде пост запит створення юзера із переданим обїєктом необхідних полів. Наприклад таке
{
"name": "User with ava",
"email": "ava@mail.com",
"password": "Password1234$",
"year": 2000
}
і побачимо що згенеруєтсья аватар і пропишеться у поле нашого створеного користувача
РЕФАКТОРИНГ
для початку встановимо модуль fs-extrа. Це додатковий модуль який розширює функціонал стандартного вбудованого fs
https://www.npmjs.com/package/fs-extra
npm i fs-extra
В сервісес стовримо файл imageService.js і в нього винесемо сервіси повʼязані із записом картинок
Це буде у вигляді класу
Пояснення:
const multer = require("multer");
const sharp = require("sharp");
const path = require("path");
const uuid = require("uuid").v4;
const fse = require("fs-extra");
const { AppError } = require("../utils/appError");
/**
* Image service class.
*/
class ImageService {
/**
* Initialize express middleware.
* @param {string} name
* @returns {Object}
*/
static initUploadMiddleware(name) {
const multerStorage = multer.memoryStorage();
const multerFilter = (req, file, cbk) => {
if (file.mimetype.startsWith("image/")) {
cbk(null, true);
} else {
cbk(new AppError(400, "Please, upload images only!"), false);
}
};
return multer({
storage: multerStorage,
fileFilter: multerFilter,
}).single(name);
}
/**
* Save file to local machine and return link.
* @param {Object} file - multer file object
* @param {Object} options - settings object
* @param {...string} pathSegments - path segments
* @returns {string}
*/
static async save(file, options, ...pathSegments) {
if (file.size > (options?.maxSize || 1 * 1024 * 1024))
throw new AppError(400, "File is too large..");
const fileName = `${uuid()}.jpeg`;
const fullFilePath = path.join(process.cwd(), "statics", ...pathSegments);
await fse.ensureDir(fullFilePath);
await sharp(file.buffer)
.resize(options || { height: 300, width: 300 })
.toFormat("jpeg")
.jpeg({ quality: 90 })
.toFile(path.join(fullFilePath, fileName));
return path.join(...pathSegments, fileName);
}
}
module.exports = ImageService;
Той самий функціонал оптимізації картинок можна зробити замість sharp за допомогою jimp
/* OPTIONAL Jimp example
const avatar = await jimp.read(file.buffer);
await avatar
.cover(options.width || 500, options.height || 500)
.quality(90)
.writeAsync(path.join(fullFilePath, fileName));
*/
ТЕПЕР ПРОПИШЕМО ЯК ЗБЕРІШАТИ ЛІНК НА АВАТАР ЗАВАНТАЖЕНОГО ФОТО
відрефакторимо наш код
поправимо наш мідрвар із врахуванням уже написаного сервісу
Перепишемо вище написаний код.
підключимо нащ сервіс
const ImageService = require('../services/imageService');
exports.uploadUserAvatar = ImageService.initUploadMiddleware("avatar");
другий метод класу ми вже використаємо в контролері
замість оцього коду
if (file) {
console.log(file);
}
напишемо:
спочатку імпортуємо
const ImageService = require("../services/imageService");
а потім
ТУТ:
if (file) {
user.avatar = await ImageService.save(
file,
{ height: 600, width: 600, maxSize: 2 * 1024 * 1024 },
"images",
"users",
user.id
);
}
Тепер зробимо запит на апдейт юзера
patch
http://localhost:3000/users/update-me
передамо name - sasha
avatar - file і оберемо наш файл аватара.
перевіримо чи зʼявився наш файл в папці і чи оновилася база даних
(при цьому старий файл в папці не видаляється. можна зробити перевірку по базі даних і видаляти старий файл)
-------------------------------
Додатково про крипто
Напишемо роут оновлення пароля уже залогіненого користувача
Краще для оновлення пароля робити окрему гілку запиту
router.patch("/update-my-password", checkMyPassword, updateMyPassword);
вмідлварці пишемо
/**
* Check current password and set new one.
*/
exports.checkMyPassword = catchAsync(async (req, res, next) => {
const { currentPassword, newPassword } = req.body;
// :TODO validate new password with regex
await userService.checkUserPassword(
req.user.id,
currentPassword,
newPassword
);
next();
});
в сервісах обробляємо пароль
/**
* Check current password and save new password.
* @param {string} userId
* @param {string} currentPassword
* @param {string} newPassword
*/
exports.checkUserPassword = async (userId, currentPassword, newPassword) => {
const user = await User.findById(userId).select("password");
const checkPassword = await bcrypt.compare(currentPassword, user.password);
if (!checkPassword) {
throw new AppError(401, "Current password wrong..");
}
user.password = newPassword;
await user.save();
};
і сам контролер
// /**
// * Update logged in user password.
// */
exports.updateMyPassword = (req, res) => {
res.status(200).json({
user: req.user,
});
};
Last updated