Подписаться на блог

Диплом, часть 3. MongoDB, API, Cloudinary

Формат, когда я просто рассказываю, что я сделал меня не совсем устраивает, ибо в будущем я хочу проанализировать ход разработки. Поэтому воспользуюсь форматом, похожим на то, как герой фильма «Социальная сеть» описывал разработку Facemash: короткие записи о каждом небольшом шаге.

Прикрутить базу оказалось несложно, работать с ней тоже проще простого. Начнём с написания авторизации пользователей.
В этот раз я решил юзать `passport.js` и ещё некоторые обвесы. Писать буду на основе своих существующих знаний и серии видео по написанию системы логина с использованием выше названной библиотеки.



В общем и целом объясняют не совсем так, как мне хотелось бы и как объяснял бы я именно взаимодействие NodeJS, работу Express. Но непосредственно по `passport.js` меня всё устраивает.

На самом деле многие моменты этих видео-уроков оставляют желать лучшего. Единтсвенное, что оказалось полезным... да ничего, наверно, просто нужно было понять в каком месте в общей системе надо подцеплять библиотеку и что как и где использовать.

—-

Долго сидел и искал непонятную ошибку, которая всё мне роняла, причиной оказалась моя невнимательность и особенность Express, в 1 и 2 строке сниппета res не одно и то же, т. к. в зависимости от числа параметров в функцию пробрасываются отличающиеся параметры:

export default (err, req, res, next) => ...
export default (err, req, res) => ...

Помимо всего `passport.js` как-то не особо укладывается в архитектуру приложения, которую я сначала хотел реализовать. Пока сделаю чисто так, чтобы просто работало, а затем уже буду рефакторить.

У этой библиотеки ну очень странная документация и очень странное поведение. Довольно долго разбирался со всем этим. Пока готова простейшая регистрация и логин. Нужно прикрутить токены, и, в принципе, серверная часть с авторизацией будет готова.

После деплоя все вызовы API работают, это гуд. Прикручиваю токены.

Всё оказалось гораздо проще, и Passport мне вообще не нужен будет, ибо есть JWT. День в пустую.

Окей, с API логина и регистрации разобрались. Теперь накидаем простые варианты API и чуть расширим схемы моделей.
Конечно, я шифрую пароли bcrypt’ом. Сейчас объект юзера в БД хранится в примерно таком виде:

{
  "_id": {
    "$oid": "5a5cbe2425ac100014571bd4"
  },
  "login": "root",
  "password": "$2a$10$YWEDx35UrhtLOg1x2FPP5ezxPP7XSbFfuYsvpdWblGGbo9nqLLzZe",
  "email": "[email protected]",
  "__v": 0
}

Сделал API для юзеров. Вроде бы всё ок, нужно только продумать, как быть с аватарами и всякими такими файлами.

Серверная часть готова абсолютно, если исключить всякие особенности и, конечно, загрузку файлов.

Несколько часов промучался с загрузкой файлов, а все проблемы были в правах и неверных заголовках. Теперь вроде бы окей работает загрузка отдельных файлов в виде `form-data`. За это спасибо `Multer.js`. Теперь ещё впилю поддержку облака `cloudinary`.

Собирался накостылять своё решение, но вовремя нашёл `multer-storage-cloudinary.js`. Закинул в деплой, теперь осталось чуть-чуть протестировать.

Окончательная версия выглядит примерно так:

Routes.js

import express from 'express';
import checkToken from '../middlewares/checkToken';
import * as FileController from '../controllers/file';

const router = express.Router();
router.post('/avatar', checkToken, FileController.uploadFile('avatar'), FileController.uploadController);
router.post('/event', checkToken, FileController.uploadFile('event'), FileController.uploadController);

export default router;

Controllers.js

import path from 'path';
import multer from 'multer';
import cloudinary from 'cloudinary';
import cloudinaryStorage from 'multer-storage-cloudinary';
import config from '../config';

cloudinary.config(config.cloudinary);

const storage = fieldName => (config.isProduction ?
  cloudinaryStorage({
    cloudinary,
    folder: `${config.uploadDirectory}/${fieldName}`,
    allowedFormats: ['jpg', 'jpeg', 'png', 'gif'],
    filename: (req, file, callback) => {
      callback(null, req.user._id);
    },
  }) :
  multer.diskStorage({
    destination: config.uploadDirectory,
    filename: (req, file, callback) => {
      callback(null, req.user._id + path.extname(file.originalname));
    },
  })
);

export const uploadFile = fieldName => multer({
  storage: storage(fieldName),
  limits: { fileSize: config.maxFileSize },
}).single(fieldName);

export const uploadController = async (req, res, next) => {
  if (req.file) {
    if (config.isProduction) {
      return res.json({
        fileUrl: req.file.url,
      });
    }
    const filePath = req.file.destination.slice(req.file.destination.indexOf('/') + 1);
    return res.json({
      fileUrl: `/${filePath}${req.file.filename}`,
    });
  }

  return next({
    status: 500,
    message: 'Upload error!',
  });
};

Проверить работу можно следующим образом (отправлять запросы можно, например, Postman’ом):

  1. Регистрируемся, послав post-запрос на http://diploma.ifedyukin.ru/api/user/register, формат `x-www-form-urlencoded`, поля: login, password, email (что в них отправлять, думаю, не стоит комментировать); последующий вход осуществляется отправкой такого же запроса, только на http://diploma.ifedyukin.ru/api/user/login и без поля «email» — в ответ придёт токен авторизации;
  2. В ответе получаем объект, в котором нас интересует поле `token`, содержимое этого поля вносим в заголовок `Authorization`;
  3. Посылаем post-запрос на http://diploma.ifedyukin.ru/api/file/avatar, формата `form-data`, с полем `avatar`, в котором будет лежать картинка.

Прикрутил аналитику от «Keen.io» и логгирование от «Logentries». Теперь могу смотреть статистику всяких событий, отслеживать «атаки» (сегодня в 4 утра кто-то пытался подбирать пароли к учетным записям, которых нет) и много-много всего интересного, а так же анализировать ошибки, внезапно возникающие при работе.

В планах прикрутить авторизацию на основе блокчейна (воспользуюсь готовым сервисом, но главное — блокчейн антихайп), server-side рендеринг страничек полностью реализовать и сделать нормальную систему service-worker’ов, чтобы клиентская часть работала быстро в нескольких потоках (вероятно) и вообще: всё было здорово.
Но к этому я перейду после того, как сделаю базовую часть клиентской части, ибо, по сравнению с сервером, времени на это уйдёт ну очень много.

Впилил защиту от бесконечных регистраций и брутфорса паролей, ну и, конечно, закинул сбор статистики кого и с каким IP заблокировала «защита».