From 8a55a1938c9e8b89ef4bdd0632d4a3d92fb9e0a2 Mon Sep 17 00:00:00 2001 From: hknsh Date: Sat, 12 Oct 2024 17:36:41 +0100 Subject: [PATCH] feat: finished routes, renamed a few files and functions --- README.md | 20 +- bun.lockb | Bin 0 -> 424944 bytes docker-compose.db.yml | 2 - docker-compose.yml | 2 - package-lock.json | 15114 ---------------- package.json | 73 +- .../migration.sql | 2 + .../migration.sql | 6 + prisma/schema.prisma | 23 +- src/app.module.ts | 27 +- src/auth/auth.service.ts | 4 +- src/decorators/create-kweek.decorator.ts | 28 - src/kweeks/comments.controller.ts | 85 + src/kweeks/comments.service.ts | 176 +- src/kweeks/dto/comments/update_comment.dto.ts | 10 + .../update_kweek.dto.ts} | 2 +- src/kweeks/entities/kweek.entity.ts | 1 - src/kweeks/kweeks.controller.ts | 47 +- src/kweeks/kweeks.module.ts | 3 +- src/kweeks/kweeks.service.ts | 125 +- src/kweeks/schemas/attachments.schema.ts | 18 + src/kweeks/schemas/prisma_queries.schema.ts | 50 + src/main.ts | 25 +- src/services/s3/s3.service.ts | 64 +- ...{create-user.dto.ts => create_user.dto.ts} | 0 ...{delete-user.dto.ts => delete_user.dto.ts} | 0 src/users/dto/follow_user.dto.ts | 16 + ...pdate-email.dto.ts => update_email.dto.ts} | 0 ...{update-name.dto.ts => update_name.dto.ts} | 0 ...password.dto.ts => update_password.dto.ts} | 0 ...image.schema.ts => upload_image.schema.ts} | 0 src/users/users.controller.ts | 22 +- src/users/users.service.ts | 48 +- src/validators/buffer.validator.ts | 2 +- ...e.validator.ts => multi_file.validator.ts} | 0 ...validator.ts => upload_image.validator.ts} | 0 test/app.e2e-spec.ts | 24 - test/jest-e2e.json | 9 - 38 files changed, 678 insertions(+), 15350 deletions(-) create mode 100755 bun.lockb delete mode 100644 package-lock.json create mode 100644 prisma/migrations/20241012135246_added_attachments_field_comments/migration.sql create mode 100644 prisma/migrations/20241012141115_updated_model_comments/migration.sql delete mode 100644 src/decorators/create-kweek.decorator.ts create mode 100644 src/kweeks/comments.controller.ts create mode 100644 src/kweeks/dto/comments/update_comment.dto.ts rename src/kweeks/dto/{update-kweek.dto.ts => kweeks/update_kweek.dto.ts} (80%) delete mode 100644 src/kweeks/entities/kweek.entity.ts create mode 100644 src/kweeks/schemas/attachments.schema.ts create mode 100644 src/kweeks/schemas/prisma_queries.schema.ts rename src/users/dto/{create-user.dto.ts => create_user.dto.ts} (100%) rename src/users/dto/{delete-user.dto.ts => delete_user.dto.ts} (100%) create mode 100644 src/users/dto/follow_user.dto.ts rename src/users/dto/{update-email.dto.ts => update_email.dto.ts} (100%) rename src/users/dto/{update-name.dto.ts => update_name.dto.ts} (100%) rename src/users/dto/{update-password.dto.ts => update_password.dto.ts} (100%) rename src/users/schemas/{upload-image.schema.ts => upload_image.schema.ts} (100%) rename src/validators/{multi-file.validator.ts => multi_file.validator.ts} (100%) rename src/validators/{upload-image.validator.ts => upload_image.validator.ts} (100%) delete mode 100644 test/app.e2e-spec.ts delete mode 100644 test/jest-e2e.json diff --git a/README.md b/README.md index 9e1ea31..26bbe2c 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,14 @@ A simple RESTful API made with **NestJS** and **Fastify**. ### 🚀 Preparing the environment -Make sure that you have Node, NPM, Docker and Docker Compose installed on your computer. +Make sure that you have Bun, Docker and Docker Compose installed on your computer. + +This project also works with Node and Npm. First, install the necessary packages with the following commands: ```bash -$ npm i +bun i ``` After that, you can update the `.env` and the `docker.env` files. The `.env` file is for development environment and the `docker.env` is for production. @@ -25,7 +27,7 @@ You can find the templates for those files on `.env.example` and `docker.env.exa To run the necessary services you can execute the following command: ```bash -$ npm run docker:db +bun docker:db ``` This will start the following services: @@ -37,13 +39,13 @@ This will start the following services: Apply the migrations to the database with the following command: ```bash -$ npm run migrate:dev +bun migrate:dev ``` And now, you can start the server with the command: ```bash -$ npm run dev:start +bun dev:start ``` You can check the documentation accessing the endpoint `/` in your browser @@ -51,7 +53,7 @@ You can check the documentation accessing the endpoint `/` in your browser To run in production you can use the following command: ```bash -$ npm run docker +bun docker ``` This will start all the previous services and the back-end image. @@ -60,11 +62,11 @@ This will start all the previous services and the back-end image. This back-end uses the following stack: -- **Fastify** -- **MinIO** - **NestJS** -- **PostgreSQL** +- **Fastify** - **Prisma** +- **MinIO** +- **PostgreSQL** - **Redis** - **Swagger** - **Typescript** diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..1040ec0a3f923c83757c5ca7668b08c400211916 GIT binary patch literal 424944 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 Request. - TODO: Remove some useless information on `README`. - TODO: Check if the Dockerfile is still working. - TODO: Replace Prisma to Kysely or something more low-level. - TODO: Replace Zod to Typebox. - - --- Future --- - - TODO: Kubernetes. - TODO: Send e-mails to the user when something happens to his account. TODO: Add a authorization system. - TODO: Create a administrator dashboard showing statistics of the platform. <- Needs front-end first I guess... - -> Only users with moderation/administration permission will be able to access it. - -> Users with moderation role can't access the statistics. - -> They will need the permission of an administrator to delete kweeks and users. - -> These users will be able to delete kweeks and terminate accounts with a obligatory reason. - -> This reason will be send to the person by e-mail and he can contest this decision. - TODO: Create a TOS. + TODO: Send e-mails to the user when something happens to his account. TODO: Create the chat system. -> Initialize the websocket system first. - TODO: Check compatibility with Bun. + TODO: Create a TOS. */ async function bootstrap() { diff --git a/src/services/s3/s3.service.ts b/src/services/s3/s3.service.ts index 4fc8cec..d70f561 100644 --- a/src/services/s3/s3.service.ts +++ b/src/services/s3/s3.service.ts @@ -1,4 +1,8 @@ -import { PutObjectCommand, PutObjectCommandInput } from "@aws-sdk/client-s3"; +import { + DeleteObjectsCommand, + PutObjectCommand, + PutObjectCommandInput, +} from "@aws-sdk/client-s3"; import { File } from "@nest-lab/fastify-multer"; import { Injectable, InternalServerErrorException } from "@nestjs/common"; import { InjectS3, S3 } from "nestjs-s3"; @@ -7,6 +11,9 @@ import { Configuration } from "src/configuration"; @Injectable() export class S3Service { + private bucket: string = Configuration.MINIO_DEFAULT_BUCKETS(); + private endpoint: string = Configuration.MINIO_ENDPOINT(); + constructor(@InjectS3() private readonly s3: S3) {} /* @@ -16,16 +23,16 @@ export class S3Service { */ /** - * Returns the image url if the upload to minio was successful. + * Returns the image url if the upload was successful. */ - async uploadImageToMinio(userID: string, buffer: Buffer): Promise { + async uploadImage(userID: string, buffer: Buffer): Promise { const compressedBuffer = await sharp(buffer) .resize(200, 200) .webp({ quality: 70 }) .toBuffer(); const params: PutObjectCommandInput = { - Bucket: Configuration.MINIO_DEFAULT_BUCKETS(), + Bucket: this.bucket, Key: `profile_images/${userID}.webp`, Body: compressedBuffer, ContentType: "image/webp", @@ -36,7 +43,7 @@ export class S3Service { const { ETag } = await this.s3.send(new PutObjectCommand(params)); if (ETag !== null) { - return `${Configuration.MINIO_ENDPOINT}/${Configuration.MINIO_DEFAULT_BUCKETS}/profile_images/${userID}.webp`; + return `${this.endpoint}/${this.bucket}/profile_images/${userID}.webp`; } throw new InternalServerErrorException( @@ -44,7 +51,7 @@ export class S3Service { ); } - async multiImageUploadToMinio(id: string, files: Array) { + async multiImageUpload(id: string, files: Array) { const buffers: Buffer[] = []; if (files.length === 0) { @@ -61,23 +68,60 @@ export class S3Service { } const uploadPromises = buffers.map(async (buffer, index) => { - return await this.multiUploadToMinio(buffer, id, index + 1); + return await this.multiUpload(buffer, id, index + 1); }); return Promise.all(uploadPromises); } - private async multiUploadToMinio(buffer: Buffer, id: string, index: number) { + private async multiUpload(buffer: Buffer, id: string, index: number) { const Key = `posts/${id}/${index}.webp`; const params: PutObjectCommandInput = { - Bucket: Configuration.MINIO_DEFAULT_BUCKETS(), + Bucket: this.bucket, Key, Body: buffer, ContentType: "image/webp", }; await this.s3.send(new PutObjectCommand(params)); - return `${Configuration.MINIO_ENDPOINT}/${Configuration.MINIO_DEFAULT_BUCKETS}/${Key}`; + return `${this.endpoint}/${this.bucket}/${Key}`; + } + + async deleteFiles(attachments: string[]) { + if (attachments.length === 0) { + return; + } + + const keys = attachments.map((url) => { + try { + const parsed_url = new URL(url); + let key = parsed_url.pathname.substring(1); + + if (key.startsWith(`${this.bucket}/`)) { + key = key.replace(`${this.bucket}`, ""); + } + + return key; + } catch (e) { + throw new InternalServerErrorException("Failed to parse URL"); + } + }); + + const command = new DeleteObjectsCommand({ + Bucket: this.bucket, + Delete: { + Objects: keys.map((key) => ({ Key: key })), + }, + }); + + try { + await this.s3.send(command); + } catch (error) { + throw new InternalServerErrorException( + "Failed to delete files from S3:", + error, + ); + } } } diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create_user.dto.ts similarity index 100% rename from src/users/dto/create-user.dto.ts rename to src/users/dto/create_user.dto.ts diff --git a/src/users/dto/delete-user.dto.ts b/src/users/dto/delete_user.dto.ts similarity index 100% rename from src/users/dto/delete-user.dto.ts rename to src/users/dto/delete_user.dto.ts diff --git a/src/users/dto/follow_user.dto.ts b/src/users/dto/follow_user.dto.ts new file mode 100644 index 0000000..2266f08 --- /dev/null +++ b/src/users/dto/follow_user.dto.ts @@ -0,0 +1,16 @@ +import { createZodDto } from "nestjs-zod"; +import { z } from "nestjs-zod/z"; + +export const FollowUserSchema = z + .object({ + username: z + .string() + .regex( + /^[a-zA-Z0-9_.]{5,15}$/, + "The username must have alphanumerics characters, underscore, dots and it must be between 5 and 15 characters", + ) + .toLowerCase(), + }) + .required(); + +export class FollowUserDTO extends createZodDto(FollowUserSchema) {} diff --git a/src/users/dto/update-email.dto.ts b/src/users/dto/update_email.dto.ts similarity index 100% rename from src/users/dto/update-email.dto.ts rename to src/users/dto/update_email.dto.ts diff --git a/src/users/dto/update-name.dto.ts b/src/users/dto/update_name.dto.ts similarity index 100% rename from src/users/dto/update-name.dto.ts rename to src/users/dto/update_name.dto.ts diff --git a/src/users/dto/update-password.dto.ts b/src/users/dto/update_password.dto.ts similarity index 100% rename from src/users/dto/update-password.dto.ts rename to src/users/dto/update_password.dto.ts diff --git a/src/users/schemas/upload-image.schema.ts b/src/users/schemas/upload_image.schema.ts similarity index 100% rename from src/users/schemas/upload-image.schema.ts rename to src/users/schemas/upload_image.schema.ts diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index fafd60a..a9cba88 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -25,12 +25,13 @@ import { } from "@nestjs/swagger"; import { Public } from "src/decorators/public.decorator"; import { BufferValidator } from "src/validators/buffer.validator"; -import UploadImageValidator from "src/validators/upload-image.validator"; -import { CreateUserDTO } from "./dto/create-user.dto"; -import { UpdateEmailDTO } from "./dto/update-email.dto"; -import { UpdateNameDTO } from "./dto/update-name.dto"; -import { UpdatePasswordDTO } from "./dto/update-password.dto"; -import UploadImageSchema from "./schemas/upload-image.schema"; +import UploadImageValidator from "src/validators/upload_image.validator"; +import { CreateUserDTO } from "./dto/create_user.dto"; +import { FollowUserDTO } from "./dto/follow_user.dto"; +import { UpdateEmailDTO } from "./dto/update_email.dto"; +import { UpdateNameDTO } from "./dto/update_name.dto"; +import { UpdatePasswordDTO } from "./dto/update_password.dto"; +import UploadImageSchema from "./schemas/upload_image.schema"; import { UserService } from "./users.service"; @ApiTags("Users") @@ -50,6 +51,15 @@ export class UserController { return this.userService.create(createUserDTO); } + @Post("/follow") + @ApiOperation({ summary: "Follow/unfollow a user" }) + @ApiCreatedResponse({ description: "Followed/unfollowed successfully" }) + @ApiNotFoundResponse({ description: "User to follow not found" }) + @ApiBearerAuth("JWT") + follow(@Body() { username }: FollowUserDTO, @Request() req) { + return this.userService.follow(req.user.id, username); + } + // GET @Get("/profile") @ApiOperation({ summary: "Returns information about the logged user" }) diff --git a/src/users/users.service.ts b/src/users/users.service.ts index b864e64..76d7768 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -4,10 +4,10 @@ import { Injectable, NotFoundException, } from "@nestjs/common"; -import * as bcrypt from "bcrypt"; +import * as argon2 from "argon2"; import { PrismaService } from "src/services/prisma/prisma.service"; import { S3Service } from "src/services/s3/s3.service"; -import { CreateUserDTO } from "./dto/create-user.dto"; +import { CreateUserDTO } from "./dto/create_user.dto"; import { UserModel } from "./models/user.model"; import { User } from "./types/user.type"; @@ -88,8 +88,7 @@ export class UserService { } // Password encryption - const salt = await bcrypt.genSalt(15); - const hash = await bcrypt.hash(password, salt); + const hash = await argon2.hash(password); const user = await this.prisma.user.create({ data: { @@ -107,6 +106,40 @@ export class UserService { return user; } + async follow(authenticated_id: string, username: string) { + const user_to_follow = await this.prisma.user.findFirst({ + where: { username }, + }); + + if (user_to_follow === null) { + throw new NotFoundException("User to follow not found"); + } + + const is_already_following = await this.prisma.follows.findFirst({ + where: { + followerId: user_to_follow.id, + followingId: authenticated_id, + }, + }); + + if (is_already_following !== null) { + await this.prisma.follows.deleteMany({ + where: { + followerId: user_to_follow.id, + followingId: authenticated_id, + }, + }); + return {}; + } + + return await this.prisma.follows.create({ + data: { + followerId: user_to_follow.id, + followingId: authenticated_id, + }, + }); + } + async updateEmail(id: string, email: string): Promise<{ message: string }> { const user = await this.prisma.user.findFirst({ where: { id }, @@ -175,14 +208,13 @@ export class UserService { where: { id }, }); - const validatePassword = await bcrypt.compare(old_password, user.password); + const validatePassword = await argon2.verify(user.password, old_password); if (!validatePassword) { throw new BadRequestException("Wrong password"); } - const salt = await bcrypt.genSalt(15); - const hash = await bcrypt.hash(new_password, salt); + const hash = await argon2.hash(new_password); await this.prisma.user.update({ where: { @@ -197,7 +229,7 @@ export class UserService { } async uploadImage(id: string, image: File) { - const url = await this.s3.uploadImageToMinio(id, image.buffer); + const url = await this.s3.uploadImage(id, image.buffer); return await this.prisma.user.update({ where: { diff --git a/src/validators/buffer.validator.ts b/src/validators/buffer.validator.ts index 3fede41..f3460bc 100644 --- a/src/validators/buffer.validator.ts +++ b/src/validators/buffer.validator.ts @@ -9,7 +9,7 @@ export class BufferValidator implements PipeTransform { async transform(value: File) { const { fileTypeFromBuffer } = await (eval( 'import("file-type")', - ) as Promise); // TODO: Find a way to remove this eval. This is very dangerous. TOP PRIORITY. <-- Downgrade to 16.5.4 should work. + ) as Promise); const ALLOWED_MIMES = ["image/jpeg", "image/png", "image/webp"]; const buffer_type = await fileTypeFromBuffer(value.buffer); diff --git a/src/validators/multi-file.validator.ts b/src/validators/multi_file.validator.ts similarity index 100% rename from src/validators/multi-file.validator.ts rename to src/validators/multi_file.validator.ts diff --git a/src/validators/upload-image.validator.ts b/src/validators/upload_image.validator.ts similarity index 100% rename from src/validators/upload-image.validator.ts rename to src/validators/upload_image.validator.ts diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts deleted file mode 100644 index 23c007e..0000000 --- a/test/app.e2e-spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { INestApplication } from "@nestjs/common"; -import { Test, TestingModule } from "@nestjs/testing"; -import * as request from "supertest"; -import { AppModule } from "./../src/app.module"; - -describe("AppController (e2e)", () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it("/ (GET)", () => { - return request(app.getHttpServer()) - .get("/") - .expect(200) - .expect("Hello World!"); - }); -}); diff --git a/test/jest-e2e.json b/test/jest-e2e.json deleted file mode 100644 index f43e8a6..0000000 --- a/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -}