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

Published • 5 min read

#Diploma, #Education

Формат, когда я просто рассказываю, что я сделал меня не совсем устраивает, ибо в будущем я хочу проанализировать ход разработки. Поэтому воспользуюсь форматом, похожим на то, как герой фильма “Социальная сеть” описывал разработку 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 работают, это гуд. Прикручиваю токены.


Всё оказалось гораздо проще, и Passort мне вообще не нужен будет, ибо есть 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’ом):

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

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


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


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

© Igor Fedyukin 2009 - 2020