DEV Community

Stanislav Karol
Stanislav Karol

Posted on

Загрузка изображений в cloudinary- модуль для NestJS

В работе над проектом потребовалось сделать загрузку картинок. Когда я писал "Кулинарную книгу", то использовал сервис Сloudinary. Но там был express, как дела обстоят в Nest? Готового модуля нет, но зато есть документация по модулям: Стартовая страница как написать свой модуль.
Постараюсь по шагам описать свои действия как написал модуль.

Подготовка настроек

Ну, конечно, сперва нужно в недоступном для случайных зрителей месте сохранить параметры доступа к сервису. Вот что в файле .env:

CLOUDINARY_CLOUD_NAME=cloudinaryCloudName
CLOUDINARY_API_KEY=1029384756
CLOUDINARY_API_SECRET=secrectapi
Enter fullscreen mode Exit fullscreen mode

Написание провайдера

Модуль будет хранится в каталоге src/cloudinary .

Для модуля необходим провайдер. В моём случае всё, что нужно от провайдера- это получение вышенаписанных параметров и передача их сервису. Чуть далее, мы узнаем как такой провайдер можно создать, поэтому пишем вот такой файл src/cloudinary/cloudinary.provider.ts:

import { Provider } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

export const cloudinaryProvider: Provider = {
  provide: 'CLOUDINARY_MODULE_OPTIONS',
  inject: [ConfigService],
  useFactory: async (configService: ConfigService) => {
    return {
      cloudinaryCloudName: configService.get<string>('CLOUDINARY_CLOUD_NAME'),
      cloudinaryApiKey: configService.get<string>('CLOUDINARY_API_KEY'),
      cloudinaryApiSecret: configService.get<string>('CLOUDINARY_API_SECRET'),
    };
  },
};
Enter fullscreen mode Exit fullscreen mode

Содержимое из этого файла должно попасть в сервис, который будет отвечать за загрузку файлов.
Начало этого сервиса будет таким:
src/cloudinary/cloudinary.service.ts

import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { UploadApiErrorResponse, UploadApiResponse, v2 } from 'cloudinary';

import { CloudinarySettings } from './types/types';


@Injectable()
export class CloudinaryService {
  constructor(@Inject('CLOUDINARY_MODULE_OPTIONS') options: CloudinarySettings) {
    this.cloudinary = v2;
    this.cloudinary.config({
      cloud_name: options.cloudinaryCloudName,
      api_key: options.cloudinaryApiKey,
      api_secret: options.cloudinaryApiSecret,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

CloudinarySettings из src/cloudinary/types/types.ts описывает известные нам опции, которые приходят от провайдера:

export type CloudinarySettings = {
  cloudinaryCloudName: string;
  cloudinaryApiKey: string;
  cloudinaryApiSecret: string;
};
Enter fullscreen mode Exit fullscreen mode

И как говорит документация cloudinary для загрузки используется объект поток (Stream), но через upload приходят файлы в виде буфера. Вопросом о переводе буфера в поток будет заниматься пакет buffer-to-stream, который нужно будет поставить. Естественно нужно поставить ещё пакет cloudinary и в dev-зависимость пакет @types/buffer-to-stream.
А дальше дело за малым: Написать метод, который будет загружать картинки в облако. У меня для тэгов к изображениям используется тип TypeUpload:

export type TypeUpload = 'avatar' | 'content';
Enter fullscreen mode Exit fullscreen mode

В нём я в тэгах обозначаю, что за картинки загружены: аватар или содержимое. Вот этот сервис, который загружает картинки: src/cloudinary/cloudinary.service.ts

import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { UploadApiErrorResponse, UploadApiResponse, v2 } from 'cloudinary';
import toStream = require('buffer-to-stream');

import { CloudinarySettings, TypeUpload } from './types/types';
import { CLOUDINARY_MODULE_OPTIONS } from './cloudinary.constants';
import { TAG_BASE_NAME } from './constants/tagFiles';

@Injectable()
export class CloudinaryService {
  constructor(@Inject(CLOUDINARY_MODULE_OPTIONS) options: CloudinarySettings) {
    v2.config({
      cloud_name: options.cloudinaryCloudName,
      api_key: options.cloudinaryApiKey,
      api_secret: options.cloudinaryApiSecret,
    });
  }

  /**
   * Метод загрузки изображений
   */
  async uploadImage({
    file,
    type = 'avatar',
  }: {
    file: Express.Multer.File;
    type: TypeUpload;
  }): Promise<UploadApiResponse> {
    // Загрузка картинки в облако
    const uploadResponse = await this.uploadFile(file);
    if (this.isUploadApiErrorResponse(uploadResponse)) {
      throw new HttpException(
        'Upload avatar error',
        HttpStatus.UNPROCESSABLE_ENTITY,
      );
    }
    // После загрузки картинки назначить ей тэг:
    const tag = `${TAG_BASE_NAME}_${type}`;
    const { public_id } = uploadResponse;
    await v2.uploader.add_tag(tag, [public_id]);
    return uploadResponse;
  }

  /**
   * Загрузка файла в сервис cloudinary
   */
  private async uploadFile(
    file: Express.Multer.File,
  ): Promise<UploadApiResponse | UploadApiErrorResponse> {
    if (!file) {
      throw new HttpException(
        'File not present!',
        HttpStatus.UNPROCESSABLE_ENTITY,
      );
    }
    return new Promise((resolve, reject) => {
      const upload = v2.uploader.upload_stream((error, result) => {
        if (error) return reject(error);
        resolve(result);
      });
      // Буфер в виде потока отправить в функцию upload
      toStream(file.buffer).pipe(upload);
    });
  }

  /**
   * Проверка результата загрузки на наличие ошибок
   * Т.е. - проверка типа: Это UploadApiErrorResponse ?
   */
  private isUploadApiErrorResponse(
    response: UploadApiErrorResponse | UploadApiResponse,
  ): response is UploadApiErrorResponse {
    return (<UploadApiErrorResponse>response).http_code !== undefined;
  }
}
Enter fullscreen mode Exit fullscreen mode

Ну и модуль, который который всё сводит в единую сущность:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

import { cloudinaryProvider } from './cloudinary.provider';
import { CloudinaryService } from './cloudinary.service';

@Module({
  imports: [ConfigModule],
  providers: [CloudinaryService, cloudinaryProvider],
  exports: [CloudinaryService],
})
export class CloudinaryModule {}
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)