diff --git a/package-lock.json b/package-lock.json index 9351075..3c1ab69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@nestjs/platform-fastify": "^10.3.1", "@nestjs/swagger": "^7.2.0", "@nestjs/throttler": "^5.1.1", - "@prisma/client": "^5.8.1", + "@prisma/client": "^5.9.1", "bcrypt": "^5.1.1", "file-type": "^19.0.0", "ioredis": "^5.3.2", @@ -33,7 +33,6 @@ "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", - "prisma": "^5.8.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "sharp": "^0.33.2" @@ -64,6 +63,7 @@ "jest": "^29.5.0", "lint-staged": "^15.2.1", "prettier": "^3.0.0", + "prisma": "^5.9.1", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", @@ -4224,9 +4224,9 @@ } }, "node_modules/@prisma/client": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.9.0.tgz", - "integrity": "sha512-dHvFZgCT0BpRS+gRhk3S+50DstXMmVowxbrPeUJaK7sjNq5OhzfpT/OGE1kq9z5Q8WmOwIXJXyxP8O2CmP+nSg==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.9.1.tgz", + "integrity": "sha512-caSOnG4kxcSkhqC/2ShV7rEoWwd3XrftokxJqOCMVvia4NYV/TPtJlS9C2os3Igxw/Qyxumj9GBQzcStzECvtQ==", "hasInstallScript": true, "engines": { "node": ">=16.13" @@ -4241,43 +4241,48 @@ } }, "node_modules/@prisma/debug": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.9.0.tgz", - "integrity": "sha512-3Uhj5YSPqaIfzJQ6JQzCNBXeBTy0x803fGIoo2tvP/KIEd+o4o49JxCQtKtP8aeef5iNh5Nn9Z25wDrdLjS80A==" + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.9.1.tgz", + "integrity": "sha512-yAHFSFCg8KVoL0oRUno3m60GAjsUKYUDkQ+9BA2X2JfVR3kRVSJFc/GpQ2fSORi4pSHZR9orfM4UC9OVXIFFTA==", + "devOptional": true }, "node_modules/@prisma/engines": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.9.0.tgz", - "integrity": "sha512-BH1fpXbMH09TwfZH5FVMJwRp6afEhKzqwebbCLdaEkJDuhxA//iwbILLqGFtGTgZbdBNUOThIK+UC3++5kWMTg==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.9.1.tgz", + "integrity": "sha512-gkdXmjxQ5jktxWNdDA5aZZ6R8rH74JkoKq6LD5mACSvxd2vbqWeWIOV0Py5wFC8vofOYShbt6XUeCIUmrOzOnQ==", + "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/debug": "5.9.0", + "@prisma/debug": "5.9.1", "@prisma/engines-version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", - "@prisma/fetch-engine": "5.9.0", - "@prisma/get-platform": "5.9.0" + "@prisma/fetch-engine": "5.9.1", + "@prisma/get-platform": "5.9.1" } }, "node_modules/@prisma/engines-version": { "version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64.tgz", - "integrity": "sha512-HFl7275yF0FWbdcNvcSRbbu9JCBSLMcurYwvWc8WGDnpu7APxQo2ONtZrUggU3WxLxUJ2uBX+0GOFIcJeVeOOQ==" + "integrity": "sha512-HFl7275yF0FWbdcNvcSRbbu9JCBSLMcurYwvWc8WGDnpu7APxQo2ONtZrUggU3WxLxUJ2uBX+0GOFIcJeVeOOQ==", + "devOptional": true }, "node_modules/@prisma/fetch-engine": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.9.0.tgz", - "integrity": "sha512-NL8Vm8Vl2d6NOSkkPGN5TTTz4s6cyCleXOzqtOFWzfKFJ4wtN2Shu7llOT+ykf6nDzh1lCN2JHUt1S6FGFZGig==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.9.1.tgz", + "integrity": "sha512-l0goQOMcNVOJs1kAcwqpKq3ylvkD9F04Ioe1oJoCqmz05mw22bNAKKGWuDd3zTUoUZr97va0c/UfLNru+PDmNA==", + "devOptional": true, "dependencies": { - "@prisma/debug": "5.9.0", + "@prisma/debug": "5.9.1", "@prisma/engines-version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", - "@prisma/get-platform": "5.9.0" + "@prisma/get-platform": "5.9.1" } }, "node_modules/@prisma/get-platform": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.9.0.tgz", - "integrity": "sha512-8CatX+E6eZxcOjJZe5hF8EXxdb5GsQTA/u7pdmUJSxGLacW9K3r5vDdgV8s22PubObQQ6979/rkCMItbCrG4Yg==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.9.1.tgz", + "integrity": "sha512-6OQsNxTyhvG+T2Ksr8FPFpuPeL4r9u0JF0OZHUBI/Uy9SS43sPyAIutt4ZEAyqWQt104ERh70EZedkHZKsnNbg==", + "devOptional": true, "dependencies": { - "@prisma/debug": "5.9.0" + "@prisma/debug": "5.9.1" } }, "node_modules/@sinclair/typebox": { @@ -12937,12 +12942,13 @@ } }, "node_modules/prisma": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.9.0.tgz", - "integrity": "sha512-0UcOofjNuAnd227JMaPqZvP01dsUXw9EXB9iC8fyoZtfv7zkQ0ozxyjY1g+vcjFPOnNLICMnLHx+lM5BJZYqOQ==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.9.1.tgz", + "integrity": "sha512-Hy/8KJZz0ELtkw4FnG9MS9rNWlXcJhf98Z2QMqi0QiVMoS8PzsBkpla0/Y5hTlob8F3HeECYphBjqmBxrluUrQ==", + "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "5.9.0" + "@prisma/engines": "5.9.1" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index 5d869d1..d7ba347 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@nestjs/platform-fastify": "^10.3.1", "@nestjs/swagger": "^7.2.0", "@nestjs/throttler": "^5.1.1", - "@prisma/client": "^5.8.1", + "@prisma/client": "^5.9.1", "bcrypt": "^5.1.1", "file-type": "^19.0.0", "ioredis": "^5.3.2", @@ -54,7 +54,6 @@ "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", - "prisma": "^5.8.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "sharp": "^0.33.2" @@ -85,6 +84,7 @@ "jest": "^29.5.0", "lint-staged": "^15.2.1", "prettier": "^3.0.0", + "prisma": "^5.9.1", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", diff --git a/src/kweeks/comments.service.ts b/src/kweeks/comments.service.ts new file mode 100644 index 0000000..029f39b --- /dev/null +++ b/src/kweeks/comments.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from "@nestjs/common"; +import { PrismaService } from "src/services/prisma/prisma.service"; +import { S3Service } from "src/services/s3/s3.service"; + +@Injectable() +export class CommentsService { + constructor( + private readonly prisma: PrismaService, + private readonly s3: S3Service, + ) {} +} diff --git a/src/kweeks/dto/update-kweek.dto.ts b/src/kweeks/dto/update-kweek.dto.ts index 31c5b25..c5b6d4a 100644 --- a/src/kweeks/dto/update-kweek.dto.ts +++ b/src/kweeks/dto/update-kweek.dto.ts @@ -1 +1,11 @@ -export class UpdateKweekDTO {} +import { createZodDto } from "nestjs-zod"; +import { z } from "nestjs-zod/z"; + +export const UpdateKweekSchema = z + .object({ + id: z.string().toLowerCase().describe("New username - optional"), + content: z.string({ required_error: "Content is required" }).max(300), + }) + .required(); + +export class UpdateKweekDTO extends createZodDto(UpdateKweekSchema) {} diff --git a/src/kweeks/kweeks.controller.ts b/src/kweeks/kweeks.controller.ts index 75fbe3a..70f222c 100644 --- a/src/kweeks/kweeks.controller.ts +++ b/src/kweeks/kweeks.controller.ts @@ -20,7 +20,6 @@ import { import { ApiCreateKweek } from "src/decorators/create-kweek.decorator"; import { Public } from "src/decorators/public.decorator"; import { MultiFileValidation } from "src/validators/multi-file.validator"; -import { UpdateKweekDTO } from "./dto/update-kweek.dto"; import { KweeksService } from "./kweeks.service"; @ApiTags("Kweeks") @@ -46,21 +45,21 @@ export class KweeksController { @Get(":id") @ApiOperation({ summary: "Retrieves information about a kweek" }) findOne(@Param("id") id: string) { - return this.kweeksService.findOne(+id); + return this.kweeksService.findOne(id); } - @Patch(":id") + @Patch() @ApiOperation({ summary: "Updates a kweek content" }) @ApiBearerAuth("JWT") - update(@Param("id") id: string, @Body() updateKweekDto: UpdateKweekDTO) { - return this.kweeksService.update(+id, updateKweekDto); + update(@Body() body: { id: string; content: string }) { + return this.kweeksService.update(body.id, body.content); } @Delete(":id") @ApiOperation({ summary: "Deletes a kweek" }) @ApiBearerAuth("JWT") remove(@Param("id") id: string) { - return this.kweeksService.remove(+id); + return this.kweeksService.remove(id); } @Post(":id/like") diff --git a/src/kweeks/kweeks.module.ts b/src/kweeks/kweeks.module.ts index 891235b..ff80a53 100644 --- a/src/kweeks/kweeks.module.ts +++ b/src/kweeks/kweeks.module.ts @@ -1,12 +1,13 @@ import { Module } from "@nestjs/common"; import { PrismaModule } from "src/services/prisma/prisma.module"; import { S3Service } from "src/services/s3/s3.service"; +import { CommentsService } from "./comments.service"; import { KweeksController } from "./kweeks.controller"; import { KweeksService } from "./kweeks.service"; @Module({ imports: [PrismaModule], controllers: [KweeksController], - providers: [KweeksService, S3Service], + providers: [KweeksService, S3Service, CommentsService], }) export class KweeksModule {} diff --git a/src/kweeks/kweeks.service.ts b/src/kweeks/kweeks.service.ts index 117d3e4..18a2ac3 100644 --- a/src/kweeks/kweeks.service.ts +++ b/src/kweeks/kweeks.service.ts @@ -1,8 +1,11 @@ import { File } from "@nest-lab/fastify-multer"; -import { Injectable } from "@nestjs/common"; +import { + BadRequestException, + Injectable, + NotFoundException, +} from "@nestjs/common"; import { PrismaService } from "src/services/prisma/prisma.service"; import { S3Service } from "src/services/s3/s3.service"; -import { UpdateKweekDTO } from "./dto/update-kweek.dto"; @Injectable() export class KweeksService { @@ -11,36 +14,71 @@ export class KweeksService { private readonly s3: S3Service, ) {} async create(content: string, authorId: string, files: Array) { - const post = await this.prisma.kweek.create({ - data: { - content, - authorId, - } - }); - - const attachments = await this.s3.multiImageUploadToMinio(post.id, files); + if (content.length > 300) { + throw new BadRequestException( + "Content too big. Must have 300 characters or lower", + ); + } - await this.prisma.kweek.updateMany({ - where: { - id: post.id - }, - data: { - attachments - }, - }); + const { id } = await this.prisma.kweek.create({ + data: { + content, + authorId, + }, + select: { + id: true, + }, + }); - return await this.prisma.kweek.findUnique({where: {id: post.id}}); + const attachments = await this.s3.multiImageUploadToMinio(id, files); + + await this.prisma.kweek.update({ + where: { + id, + }, + data: { + attachments, + }, + }); + + return await this.prisma.kweek.findFirst({ where: { id } }); } - findOne(id: number) { - return `This action returns a #${id} kweek`; + async findOne(id: string) { + const post = await this.prisma.kweek.findFirst({ + where: { + id, + }, + select: { + id: true, + content: true, + attachments: true, + createdAt: true, + updatedAt: true, + author: { + select: { + displayName: true, + username: true, + profileImage: true, + }, + }, + likes: true, + comments: true, + }, + }); + + if (post === null) { + throw new NotFoundException("Post not found"); + } + + return post; } - update(id: number, updateKweekDto: UpdateKweekDTO) { + update(id: string, content: string) { return `This action updates a #${id} kweek`; } - remove(id: number) { + remove(id: string) { return `This action removes a #${id} kweek`; } } diff --git a/src/services/s3/s3.service.ts b/src/services/s3/s3.service.ts index 000c41f..ddc2f71 100644 --- a/src/services/s3/s3.service.ts +++ b/src/services/s3/s3.service.ts @@ -1,7 +1,7 @@ import { PutObjectCommand, PutObjectCommandInput } from "@aws-sdk/client-s3"; -import { BadRequestException, Injectable, InternalServerErrorException } from "@nestjs/common"; -import { InjectS3, S3 } from "nestjs-s3"; import { File } from "@nest-lab/fastify-multer"; +import { Injectable, InternalServerErrorException } from "@nestjs/common"; +import { InjectS3, S3 } from "nestjs-s3"; import sharp from "sharp"; @Injectable() @@ -37,38 +37,40 @@ export class S3Service { ); } - async multiImageUploadToMinio(id: string, files: Array) { - const buffers: Buffer[] = []; + async multiImageUploadToMinio(id: string, files: Array) { + const buffers: Buffer[] = []; - if (files.length === 0) { - return []; - } + if (files.length === 0) { + return []; + } - for (let i = 0; i < files.length; i++) { - const { buffer } = files[i]; + for (let i = 0; i < files.length; i++) { + const { buffer } = files[i]; - const compressedBuffers = await sharp(buffer).webp({quality: 70}).toBuffer(); - buffers.push(compressedBuffers); - } - - const uploadPromises = buffers.map(async (buffer, index) => { - return await this.multiUploadToMinio(buffer, id, index + 1); - }); + const compressedBuffers = await sharp(buffer) + .webp({ quality: 70 }) + .toBuffer(); + buffers.push(compressedBuffers); + } - return Promise.all(uploadPromises); - } - - private async multiUploadToMinio(buffer: Buffer, id: string, index: number) { - const Key = `posts/${id}/${index}.webp`; + const uploadPromises = buffers.map(async (buffer, index) => { + return await this.multiUploadToMinio(buffer, id, index + 1); + }); - const params: PutObjectCommandInput = { - Bucket: process.env.MINIO_DEFAULT_BUCKETS, - Key, - Body: buffer, - ContentType: 'image/webp', - }; + return Promise.all(uploadPromises); + } - await this.s3.send(new PutObjectCommand(params)) - return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/${Key}` - } + private async multiUploadToMinio(buffer: Buffer, id: string, index: number) { + const Key = `posts/${id}/${index}.webp`; + + const params: PutObjectCommandInput = { + Bucket: process.env.MINIO_DEFAULT_BUCKETS, + Key, + Body: buffer, + ContentType: "image/webp", + }; + + await this.s3.send(new PutObjectCommand(params)); + return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/${Key}`; + } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 3548ce2..be38f2d 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -53,7 +53,7 @@ export class UserService { select: { id: true, content: true, - attachments: true, + attachments: true, createdAt: true, updatedAt: true, },