mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2025-12-17 12:33:22 +00:00
move to storage service
This commit is contained in:
@@ -24,9 +24,7 @@
|
|||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.454.0",
|
"@it-incubator/storage-sdk": "^0.0.8",
|
||||||
"@aws-sdk/client-ses": "^3.454.0",
|
|
||||||
"@aws-sdk/credential-provider-node": "^3.451.0",
|
|
||||||
"@nestjs-modules/mailer": "^1.9.1",
|
"@nestjs-modules/mailer": "^1.9.1",
|
||||||
"@nestjs/common": "10.2.9",
|
"@nestjs/common": "10.2.9",
|
||||||
"@nestjs/config": "^3.1.1",
|
"@nestjs/config": "^3.1.1",
|
||||||
@@ -41,7 +39,6 @@
|
|||||||
"@nestjs/swagger": "^7.1.16",
|
"@nestjs/swagger": "^7.1.16",
|
||||||
"@prisma/client": "4.16.0",
|
"@prisma/client": "4.16.0",
|
||||||
"@types/passport-local": "^1.0.38",
|
"@types/passport-local": "^1.0.38",
|
||||||
"aws-sdk": "^2.1499.0",
|
|
||||||
"bcrypt": "5.1.0",
|
"bcrypt": "5.1.0",
|
||||||
"class-transformer": "0.5.1",
|
"class-transformer": "0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
@@ -56,7 +53,6 @@
|
|||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"remeda": "^1.29.0",
|
"remeda": "^1.29.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"swagger-themes": "^1.2.30",
|
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
1262
pnpm-lock.yaml
generated
1262
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import * as process from 'process'
|
import * as process from 'process'
|
||||||
|
|
||||||
|
import { StorageModule } from '@it-incubator/storage-sdk'
|
||||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
|
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
|
||||||
import { CqrsModule } from '@nestjs/cqrs'
|
import { CqrsModule } from '@nestjs/cqrs'
|
||||||
import { ServeStaticModule } from '@nestjs/serve-static'
|
import { ServeStaticModule } from '@nestjs/serve-static'
|
||||||
@@ -28,6 +29,12 @@ import { ConfigModule } from './settings/config.module'
|
|||||||
ServeStaticModule.forRoot({
|
ServeStaticModule.forRoot({
|
||||||
rootPath: join(__dirname, '..', 'client'),
|
rootPath: join(__dirname, '..', 'client'),
|
||||||
}),
|
}),
|
||||||
|
StorageModule.register({
|
||||||
|
baseURL: process.env.STORAGE_SERVICE_URL,
|
||||||
|
headers: {
|
||||||
|
'service-token': process.env.STORAGE_SERVICE_TOKEN,
|
||||||
|
},
|
||||||
|
}),
|
||||||
MailerModule.forRoot({
|
MailerModule.forRoot({
|
||||||
transport: {
|
transport: {
|
||||||
host: process.env.AWS_SES_SMTP_HOST,
|
host: process.env.AWS_SES_SMTP_HOST,
|
||||||
|
|||||||
@@ -1,46 +1,35 @@
|
|||||||
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
|
import { CreateFileDto, FileType, StorageService } from '@it-incubator/storage-sdk'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { v4 as uuid } from 'uuid'
|
|
||||||
|
|
||||||
import { PrismaService } from '../../prisma.service'
|
import { PrismaService } from '../../prisma.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileUploadService {
|
export class FileUploadService {
|
||||||
constructor(private prismaService: PrismaService) {}
|
constructor(
|
||||||
|
private prismaService: PrismaService,
|
||||||
|
private storageService: StorageService
|
||||||
|
) {}
|
||||||
|
|
||||||
async uploadFile(dataBuffer: Buffer, fileName: string) {
|
private async uploadFileToStorageService(dto: CreateFileDto) {
|
||||||
const key = `${uuid()}-${fileName}`
|
return await this.storageService.create(dto).then(data => data.data)
|
||||||
const bucketName = process.env.AWS_BUCKET_NAME
|
}
|
||||||
const region = 'eu-central-1'
|
|
||||||
const s3 = new S3Client({
|
|
||||||
region,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: process.env.AWS_S3_ACCESS_KEY,
|
|
||||||
secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const encodeFileName = encodeURIComponent(key)
|
|
||||||
|
|
||||||
const fileUrl = `https://${bucketName}.s3.${region}.amazonaws.com/${encodeFileName}`
|
async uploadFile(file: CreateFileDto['file']) {
|
||||||
|
try {
|
||||||
|
const savedFile = await this.uploadFileToStorageService({
|
||||||
|
fileType: FileType.Image,
|
||||||
|
file,
|
||||||
|
})
|
||||||
|
|
||||||
const fileStorageInDB = {
|
return this.prismaService.fileEntity.create({
|
||||||
fileName,
|
data: {
|
||||||
fileUrl,
|
fileName: savedFile.name,
|
||||||
key,
|
fileUrl: savedFile.url,
|
||||||
|
key: savedFile.url,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const putCommand = new PutObjectCommand({
|
|
||||||
Bucket: bucketName,
|
|
||||||
Body: dataBuffer,
|
|
||||||
Key: key,
|
|
||||||
ContentDisposition: 'inline',
|
|
||||||
ContentType: `image/${fileName.split('.').at(-1)}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await s3.send(putCommand)
|
|
||||||
|
|
||||||
return this.prismaService.fileEntity.create({
|
|
||||||
data: fileStorageInDB,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ export class UpdateUserHandler implements ICommandHandler<UpdateUserCommand> {
|
|||||||
let avatar: string | null
|
let avatar: string | null
|
||||||
|
|
||||||
if (command.avatar) {
|
if (command.avatar) {
|
||||||
const addAvatarImagePromise = this.fileUploadService.uploadFile(
|
const addAvatarImagePromise = this.fileUploadService.uploadFile(command.avatar)
|
||||||
command.avatar?.buffer,
|
|
||||||
command.avatar?.originalname
|
|
||||||
)
|
|
||||||
|
|
||||||
const result = await addAvatarImagePromise
|
const result = await addAvatarImagePromise
|
||||||
|
|
||||||
|
|||||||
@@ -35,32 +35,20 @@ export class UpdateCardHandler implements ICommandHandler<UpdateCardCommand> {
|
|||||||
let questionImg, answerImg
|
let questionImg, answerImg
|
||||||
|
|
||||||
if (command.questionImg && command.answerImg) {
|
if (command.questionImg && command.answerImg) {
|
||||||
const addQuestionImagePromise = this.fileUploadService.uploadFile(
|
const addQuestionImagePromise = this.fileUploadService.uploadFile(command.questionImg)
|
||||||
command.questionImg?.buffer,
|
const addAnswerImagePromise = this.fileUploadService.uploadFile(command.answerImg)
|
||||||
command.questionImg?.originalname
|
|
||||||
)
|
|
||||||
const addAnswerImagePromise = this.fileUploadService.uploadFile(
|
|
||||||
command.answerImg?.buffer,
|
|
||||||
command.answerImg?.originalname
|
|
||||||
)
|
|
||||||
|
|
||||||
const result = await Promise.all([addQuestionImagePromise, addAnswerImagePromise])
|
const result = await Promise.all([addQuestionImagePromise, addAnswerImagePromise])
|
||||||
|
|
||||||
questionImg = result[0].fileUrl
|
questionImg = result[0].fileUrl
|
||||||
answerImg = result[1].fileUrl
|
answerImg = result[1].fileUrl
|
||||||
} else if (command.answerImg) {
|
} else if (command.answerImg) {
|
||||||
const addAnswerImagePromise = this.fileUploadService.uploadFile(
|
const addAnswerImagePromise = this.fileUploadService.uploadFile(command.answerImg)
|
||||||
command.answerImg?.buffer,
|
|
||||||
command.answerImg?.originalname
|
|
||||||
)
|
|
||||||
const result = await addAnswerImagePromise
|
const result = await addAnswerImagePromise
|
||||||
|
|
||||||
answerImg = result.fileUrl
|
answerImg = result.fileUrl
|
||||||
} else if (command.questionImg) {
|
} else if (command.questionImg) {
|
||||||
const addQuestionImagePromise = this.fileUploadService.uploadFile(
|
const addQuestionImagePromise = this.fileUploadService.uploadFile(command.questionImg)
|
||||||
command.questionImg?.buffer,
|
|
||||||
command.questionImg?.originalname
|
|
||||||
)
|
|
||||||
const result = await addQuestionImagePromise
|
const result = await addQuestionImagePromise
|
||||||
|
|
||||||
questionImg = result.fileUrl
|
questionImg = result.fileUrl
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
PaginatedDecksWithMaxCardsCount,
|
PaginatedDecksWithMaxCardsCount,
|
||||||
} from './entities/deck.entity'
|
} from './entities/deck.entity'
|
||||||
import { MinMaxCards } from './entities/min-max-cards.entity'
|
import { MinMaxCards } from './entities/min-max-cards.entity'
|
||||||
|
import { DecksRepository } from './infrastructure/decks.repository'
|
||||||
import {
|
import {
|
||||||
CreateCardCommand,
|
CreateCardCommand,
|
||||||
CreateDeckCommand,
|
CreateDeckCommand,
|
||||||
@@ -60,7 +61,10 @@ import {
|
|||||||
@ApiTags('Decks')
|
@ApiTags('Decks')
|
||||||
@Controller('decks')
|
@Controller('decks')
|
||||||
export class DecksController {
|
export class DecksController {
|
||||||
constructor(private commandBus: CommandBus) {}
|
constructor(
|
||||||
|
private commandBus: CommandBus,
|
||||||
|
private decksRepository: DecksRepository
|
||||||
|
) {}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.PARTIAL_CONTENT)
|
@HttpCode(HttpStatus.PARTIAL_CONTENT)
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
@@ -89,6 +93,25 @@ export class DecksController {
|
|||||||
return this.commandBus.execute(new GetAllDecksV2Command({ ...finalQuery, userId: req.user.id }))
|
return this.commandBus.execute(new GetAllDecksV2Command({ ...finalQuery, userId: req.user.id }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.PARTIAL_CONTENT)
|
||||||
|
@ApiOperation({ description: 'Retrieve paginated decks list.', summary: 'Paginated decks list' })
|
||||||
|
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Version('2')
|
||||||
|
@Get('empty')
|
||||||
|
async findAllEmpty(@Query() query: GetAllDecksDto, @Req() req) {
|
||||||
|
const result: PaginatedDecks = await this.commandBus.execute(
|
||||||
|
new GetAllDecksV2Command({
|
||||||
|
itemsPerPage: 5000,
|
||||||
|
minCardsCount: 0,
|
||||||
|
maxCardsCount: 0,
|
||||||
|
userId: req.user.id,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return this.decksRepository.deleteManyById(result.items.map(({ id }) => id))
|
||||||
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
description: 'Retrieve the minimum and maximum amount of cards in a deck.',
|
description: 'Retrieve the minimum and maximum amount of cards in a deck.',
|
||||||
|
|||||||
@@ -294,6 +294,21 @@ LIMIT $${conditions.length + havingConditions.length + 1} OFFSET $${
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deleteManyById(id: string[]) {
|
||||||
|
try {
|
||||||
|
return await this.prisma.deck.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(e?.message)
|
||||||
|
throw new InternalServerErrorException(e?.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async deleteDeckById(id: string) {
|
public async deleteDeckById(id: string) {
|
||||||
try {
|
try {
|
||||||
return await this.prisma.deck.delete({
|
return await this.prisma.deck.delete({
|
||||||
|
|||||||
@@ -38,32 +38,20 @@ export class CreateCardHandler implements ICommandHandler<CreateCardCommand> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (command.questionImg && command.answerImg) {
|
if (command.questionImg && command.answerImg) {
|
||||||
const addQuestionImagePromise = this.fileUploadService.uploadFile(
|
const addQuestionImagePromise = this.fileUploadService.uploadFile(command.questionImg)
|
||||||
command.questionImg?.buffer,
|
const addAnswerImagePromise = this.fileUploadService.uploadFile(command.answerImg)
|
||||||
command.questionImg?.originalname
|
|
||||||
)
|
|
||||||
const addAnswerImagePromise = this.fileUploadService.uploadFile(
|
|
||||||
command.answerImg?.buffer,
|
|
||||||
command.answerImg?.originalname
|
|
||||||
)
|
|
||||||
|
|
||||||
const result = await Promise.all([addQuestionImagePromise, addAnswerImagePromise])
|
const result = await Promise.all([addQuestionImagePromise, addAnswerImagePromise])
|
||||||
|
|
||||||
questionImg = result[0].fileUrl
|
questionImg = result[0].fileUrl
|
||||||
answerImg = result[1].fileUrl
|
answerImg = result[1].fileUrl
|
||||||
} else if (command.answerImg) {
|
} else if (command.answerImg) {
|
||||||
const addAnswerImagePromise = this.fileUploadService.uploadFile(
|
const addAnswerImagePromise = this.fileUploadService.uploadFile(command.answerImg)
|
||||||
command.answerImg?.buffer,
|
|
||||||
command.answerImg?.originalname
|
|
||||||
)
|
|
||||||
const result = await addAnswerImagePromise
|
const result = await addAnswerImagePromise
|
||||||
|
|
||||||
answerImg = result.fileUrl
|
answerImg = result.fileUrl
|
||||||
} else if (command.questionImg) {
|
} else if (command.questionImg) {
|
||||||
const addQuestionImagePromise = this.fileUploadService.uploadFile(
|
const addQuestionImagePromise = this.fileUploadService.uploadFile(command.questionImg)
|
||||||
command.questionImg?.buffer,
|
|
||||||
command.questionImg?.originalname
|
|
||||||
)
|
|
||||||
const result = await addQuestionImagePromise
|
const result = await addQuestionImagePromise
|
||||||
|
|
||||||
questionImg = result.fileUrl
|
questionImg = result.fileUrl
|
||||||
|
|||||||
@@ -23,10 +23,7 @@ export class CreateDeckHandler implements ICommandHandler<CreateDeckCommand> {
|
|||||||
let cover
|
let cover
|
||||||
|
|
||||||
if (command.cover) {
|
if (command.cover) {
|
||||||
const result = await this.fileUploadService.uploadFile(
|
const result = await this.fileUploadService.uploadFile(command.cover)
|
||||||
command.cover.buffer,
|
|
||||||
command.cover.originalname
|
|
||||||
)
|
|
||||||
|
|
||||||
cover = result.fileUrl
|
cover = result.fileUrl
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ export class UpdateDeckHandler implements ICommandHandler<UpdateDeckCommand> {
|
|||||||
let cover
|
let cover
|
||||||
|
|
||||||
if (command.cover) {
|
if (command.cover) {
|
||||||
const result = await this.fileUploadService.uploadFile(
|
const result = await this.fileUploadService.uploadFile(command.cover)
|
||||||
command.cover.buffer,
|
|
||||||
command.cover.originalname
|
|
||||||
)
|
|
||||||
|
|
||||||
cover = result.fileUrl
|
cover = result.fileUrl
|
||||||
} else if (command.deck.cover === '') {
|
} else if (command.deck.cover === '') {
|
||||||
|
|||||||
Reference in New Issue
Block a user