В работе над проектом потребовалось сделать загрузку картинок. Когда я писал "Кулинарную книгу", то использовал сервис Сloudinary. Но там был express, как дела обстоят в Nest? Готового модуля нет, но зато есть документация по модулям: Стартовая страница как написать свой модуль.
Постараюсь по шагам описать свои действия как написал модуль.
Подготовка настроек
Ну, конечно, сперва нужно в недоступном для случайных зрителей месте сохранить параметры доступа к сервису. Вот что в файле .env:
CLOUDINARY_CLOUD_NAME=cloudinaryCloudName
CLOUDINARY_API_KEY=1029384756
CLOUDINARY_API_SECRET=secrectapi
Написание провайдера
Модуль будет хранится в каталоге 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'),
};
},
};
Содержимое из этого файла должно попасть в сервис, который будет отвечать за загрузку файлов.
Начало этого сервиса будет таким:
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,
});
}
}
CloudinarySettings из src/cloudinary/types/types.ts описывает известные нам опции, которые приходят от провайдера:
export type CloudinarySettings = {
cloudinaryCloudName: string;
cloudinaryApiKey: string;
cloudinaryApiSecret: string;
};
И как говорит документация cloudinary для загрузки используется объект поток (Stream), но через upload приходят файлы в виде буфера. Вопросом о переводе буфера в поток будет заниматься пакет buffer-to-stream, который нужно будет поставить. Естественно нужно поставить ещё пакет cloudinary и в dev-зависимость пакет @types/buffer-to-stream.
А дальше дело за малым: Написать метод, который будет загружать картинки в облако. У меня для тэгов к изображениям используется тип TypeUpload:
export type TypeUpload = 'avatar' | 'content';
В нём я в тэгах обозначаю, что за картинки загружены: аватар или содержимое. Вот этот сервис, который загружает картинки: 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;
}
}
Ну и модуль, который который всё сводит в единую сущность:
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 {}
Исходный код можно увидеть на гитхаб - в этом репозитории есть этот самый модуль, он подключен и работает.
Top comments (0)