From 70381c9a98f2f973df2ffa74545e4d9a064082c9 Mon Sep 17 00:00:00 2001 From: omersafakbebek Date: Tue, 21 Nov 2023 12:42:14 +0300 Subject: [PATCH 1/4] added post endpoints --- ludos/backend/src/app.module.ts | 52 ++- .../src/controllers/post.controller.ts | 227 +++++++++++ .../src/controllers/review.controller.ts | 172 +++++---- .../backend/src/controllers/s3.controller.ts | 7 +- .../src/controllers/user.controller.ts | 10 +- .../src/dtos/game/response/get.response.ts | 2 +- .../src/dtos/post/request/create.dto.ts | 34 ++ .../src/dtos/post/request/update.dto.ts | 35 ++ .../dtos/post/response/create.response.dto.ts | 28 ++ .../dtos/post/response/get.response.dto.ts | 51 +++ .../dtos/post/response/list.response.dto.ts | 42 +++ .../dtos/post/response/page.response.dto.ts | 15 + .../src/dtos/review/request/edit.dto.ts | 6 +- .../src/dtos/review/response/create.dto.ts | 2 +- .../src/dtos/review/response/edit.dto.ts | 2 +- .../dtos/user/request/get-user-info.dto.ts | 10 +- .../response/get-user-info-response.dto.ts | 12 +- .../response/user-in-other-responses.dto.ts | 17 + ludos/backend/src/entities/post.entity.ts | 78 ++++ ludos/backend/src/entities/review.entity.ts | 94 ++--- ludos/backend/src/entities/user.entity.ts | 10 + .../src/repositories/post.repository.ts | 143 +++++++ .../src/repositories/review.repository.ts | 1 - .../src/repositories/user.repository.ts | 6 +- .../services/config/typeorm-config.service.ts | 3 +- ludos/backend/src/services/game.service.ts | 2 +- ludos/backend/src/services/post.service.ts | 140 +++++++ ludos/backend/src/services/review.service.ts | 355 +++++++++--------- ludos/backend/src/services/s3.service.ts | 30 +- ludos/backend/src/services/user.service.ts | 4 +- 30 files changed, 1211 insertions(+), 379 deletions(-) create mode 100644 ludos/backend/src/controllers/post.controller.ts create mode 100644 ludos/backend/src/dtos/post/request/create.dto.ts create mode 100644 ludos/backend/src/dtos/post/request/update.dto.ts create mode 100644 ludos/backend/src/dtos/post/response/create.response.dto.ts create mode 100644 ludos/backend/src/dtos/post/response/get.response.dto.ts create mode 100644 ludos/backend/src/dtos/post/response/list.response.dto.ts create mode 100644 ludos/backend/src/dtos/post/response/page.response.dto.ts create mode 100644 ludos/backend/src/dtos/user/response/user-in-other-responses.dto.ts create mode 100644 ludos/backend/src/entities/post.entity.ts create mode 100644 ludos/backend/src/repositories/post.repository.ts create mode 100644 ludos/backend/src/services/post.service.ts diff --git a/ludos/backend/src/app.module.ts b/ludos/backend/src/app.module.ts index 528a15be..974012a2 100644 --- a/ludos/backend/src/app.module.ts +++ b/ludos/backend/src/app.module.ts @@ -1,28 +1,31 @@ -import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; -import { AppController } from './controllers/app.controller'; -import { AppService } from './services/app.service'; +import { MiddlewareConsumer, Module, NestModule, Post } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { TypeOrmConfigService } from './services/config/typeorm-config.service'; -import { User } from './entities/user.entity'; -import { ResetPassword } from './entities/reset-password.entity'; -import { UserRepository } from './repositories/user.repository'; -import { UserController } from './controllers/user.controller'; -import { UserService } from './services/user.service'; import { JwtModule } from '@nestjs/jwt'; -import { JwtConfigService } from './services/config/jwt-config.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AppController } from './controllers/app.controller'; import { GameController } from './controllers/game.controller'; -import { GameService } from './services/game.service'; -import { GameRepository } from './repositories/game.repository'; +import { PostController } from './controllers/post.controller'; +import { ReviewController } from './controllers/review.controller'; +import { S3Controller } from './controllers/s3.controller'; +import { UserController } from './controllers/user.controller'; import { Game } from './entities/game.entity'; +import { ResetPassword } from './entities/reset-password.entity'; +import { Review } from './entities/review.entity'; +import { User } from './entities/user.entity'; import { TokenDecoderMiddleware } from './middlewares/tokenDecoder.middleware'; +import { GameRepository } from './repositories/game.repository'; +import { PostRepository } from './repositories/post.repository'; import { ResetPasswordRepository } from './repositories/reset-password.repository'; -import { S3Service } from './services/s3.service'; -import { S3Controller } from './controllers/s3.controller'; import { ReviewRepository } from './repositories/review.repository'; +import { UserRepository } from './repositories/user.repository'; +import { AppService } from './services/app.service'; +import { JwtConfigService } from './services/config/jwt-config.service'; +import { TypeOrmConfigService } from './services/config/typeorm-config.service'; +import { GameService } from './services/game.service'; +import { PostService } from './services/post.service'; import { ReviewService } from './services/review.service'; -import { ReviewController } from './controllers/review.controller'; -import { Review } from './entities/review.entity'; +import { S3Service } from './services/s3.service'; +import { UserService } from './services/user.service'; @Module({ imports: [ @@ -37,9 +40,16 @@ import { Review } from './entities/review.entity'; useClass: TypeOrmConfigService, inject: [TypeOrmConfigService], }), - TypeOrmModule.forFeature([User, Game, Review, ResetPassword]), + TypeOrmModule.forFeature([User, Game, Review, ResetPassword, Post]), + ], + controllers: [ + AppController, + UserController, + GameController, + S3Controller, + ReviewController, + PostController, ], - controllers: [AppController, UserController, GameController, S3Controller, ReviewController], providers: [ AppService, UserRepository, @@ -49,7 +59,9 @@ import { Review } from './entities/review.entity'; ResetPasswordRepository, S3Service, ReviewRepository, - ReviewService + ReviewService, + PostRepository, + PostService, ], }) export class AppModule implements NestModule { diff --git a/ludos/backend/src/controllers/post.controller.ts b/ludos/backend/src/controllers/post.controller.ts new file mode 100644 index 00000000..7cdd7c51 --- /dev/null +++ b/ludos/backend/src/controllers/post.controller.ts @@ -0,0 +1,227 @@ +import { + Body, + Controller, + DefaultValuePipe, + Delete, + Get, + HttpCode, + HttpStatus, + Param, + ParseBoolPipe, + ParseIntPipe, + Post, + Put, + Query, + Req, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiCreatedResponse, + ApiForbiddenResponse, + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiQuery, + ApiTags, +} from '@nestjs/swagger'; +import { PostCreateDto } from '../dtos/post/request/create.dto'; +import { PostUpdateDto } from '../dtos/post/request/update.dto'; +import { PostCreateResponseDto } from '../dtos/post/response/create.response.dto'; +import { PostGetResponseDto } from '../dtos/post/response/get.response.dto'; +import { PostPageResponseDto } from '../dtos/post/response/page.response.dto'; +import { Post as PostEntity } from '../entities/post.entity'; +import { SerializerInterceptor } from '../interceptors/customSerializer.interceptor'; +import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; +import { AuthGuard } from '../services/guards/auth.guard'; +import { PostService } from '../services/post.service'; + +@ApiTags('post') +@Controller('post') +export class PostController { + constructor(private readonly postService: PostService) {} + + @ApiCreatedResponse({ + description: 'Game created successfully', + type: PostCreateResponseDto, + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(201) + @ApiCreatedResponse({ + type: PostCreateResponseDto, + }) + @ApiForbiddenResponse({ description: 'User should login' }) + @ApiOperation({ summary: 'Create Post Endpoint' }) + @ApiBearerAuth() + @UseInterceptors(new SerializerInterceptor(PostCreateResponseDto)) + @UseGuards(AuthGuard) + @Post() + public async createPost( + @Req() req: AuthorizedRequest, + @Body() input: PostCreateDto, + ) { + const createdPost = await this.postService.createPost(req.user.id, input); + return createdPost; + } + + @ApiOkResponse({ + type: PostGetResponseDto, + }) + @ApiNotFoundResponse({ + description: 'Post not found', + }) + @ApiOperation({ summary: 'Get Post by ID Endpoint' }) + @ApiBearerAuth() + @UseInterceptors(new SerializerInterceptor(PostGetResponseDto)) + @Get(':id') + public async getPost(@Req() req: AuthorizedRequest, @Param('id') id: string) { + return await this.postService.getPost(id, req.user && req.user.id); + } + + @ApiOkResponse() + @ApiNotFoundResponse({ + description: 'Post not found Or does not belong to the user', + }) + @ApiOperation({ summary: 'Update Post by ID Endpoint' }) + @ApiForbiddenResponse({ description: 'User should login' }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Put(':id') + public async updatePost( + @Req() req: AuthorizedRequest, + @Param('id') id: string, + @Body() input: PostUpdateDto, + ) { + await this.postService.updatePost(id, input, req.user.id); + } + @ApiOkResponse() + @ApiNotFoundResponse({ + description: 'Post not found Or does not belong to the user', + }) + @ApiOperation({ summary: 'Delete Post by ID Endpoint' }) + @ApiBearerAuth() + @ApiForbiddenResponse({ description: 'User should login' }) + @UseGuards(AuthGuard) + @Delete(':id') + public async deletePost( + @Req() req: AuthorizedRequest, + @Param('id') id: string, + ) { + await this.postService.deletePost(id, req.user.id); + } + + @ApiBearerAuth() + @ApiOperation({ summary: 'Like a post, or remove like if liked' }) + @ApiNotFoundResponse({ description: 'Post is not found!' }) + @ApiForbiddenResponse({ description: 'User should login' }) + @UseGuards(AuthGuard) + @Put('/like/:postId') + public async likePost( + @Req() req: AuthorizedRequest, + @Param('postId') postId: string, + ) { + await this.postService.likePost(req.user.id, postId); + return HttpStatus.OK; + } + @ApiBearerAuth() + @ApiOperation({ summary: 'Dislike a post, or remove dislike if disliked' }) + @ApiNotFoundResponse({ description: 'Post is not found!' }) + @ApiForbiddenResponse({ description: 'User should login' }) + @UseGuards(AuthGuard) + @Put('/dislike/:postId') + public async dislikePost( + @Req() req: AuthorizedRequest, + @Param('postId') postId: string, + ) { + await this.postService.dislikePost(req.user.id, postId); + return HttpStatus.OK; + } + + @ApiOperation({ summary: 'List posts' }) + @ApiQuery({ name: 'page', required: false, description: 'Default is 1' }) + @ApiQuery({ + name: 'limit', + required: false, + description: 'Limit the number of the items in the page. Default is 10', + }) + @ApiQuery({ + name: 'searchKey', + required: false, + description: + 'Search by title or body. This is a full text search. i.e. Completed words should be provided', + }) + @ApiQuery({ + name: 'tags', + required: false, + description: 'Comma separated list of tags. This filter works like AND', + example: 'tag1,tag2,tag3', + }) + @ApiQuery({ name: 'ownerUserId', required: false }) + @ApiQuery({ + name: 'isLiked', + required: false, + description: 'Filter by liked posts. If false no filter is applied', + example: 'true', + }) + @ApiQuery({ + name: 'isDisliked', + required: false, + description: + 'Filter by disliked posts. If false no filter is applied. This filter valid if isLiked not true', + example: 'true', + }) + @ApiQuery({ + name: 'orderByKey', + required: false, + type: 'string', + description: + 'A post field that will be used for ordering the items. Default is createdAt', + example: 'numberOfLikes', + }) + @ApiQuery({ + name: 'order', + required: false, + description: 'ASC or DESC. Default is DESC', + example: 'ASC', + }) + @ApiOkResponse({ + type: PostPageResponseDto, + }) + @ApiBearerAuth() + @UseInterceptors(new SerializerInterceptor(PostPageResponseDto)) + @Get() + public async listPosts( + @Req() req: AuthorizedRequest, + @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1, + @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number = 10, + @Query('searchKey') searchKey?: string, + @Query('tags') tags?: string, + @Query('ownerUserId') ownerUserId?: string, + @Query('isLiked', new DefaultValuePipe(false), ParseBoolPipe) + isLiked?: boolean, + @Query('isDisliked', new DefaultValuePipe(false), ParseBoolPipe) + isDisliked?: boolean, + @Query('orderByKey') orderByKey?: keyof PostEntity, + @Query('order') order?: 'ASC' | 'DESC', + ) { + return await this.postService.listPosts( + page, + limit, + searchKey, + tags, + ownerUserId, + req.user && req.user.id, + isLiked, + isDisliked, + orderByKey, + order, + ); + } +} diff --git a/ludos/backend/src/controllers/review.controller.ts b/ludos/backend/src/controllers/review.controller.ts index f8b6c9ce..9a99eedc 100644 --- a/ludos/backend/src/controllers/review.controller.ts +++ b/ludos/backend/src/controllers/review.controller.ts @@ -1,91 +1,91 @@ import { - Body, - Controller, - HttpCode, - Delete, - Param, - Post, - Put, - Req, - UseGuards, - } from '@nestjs/common'; - import { - ApiBadRequestResponse, - ApiBearerAuth, - ApiConflictResponse, - ApiCreatedResponse, - ApiNotFoundResponse, - ApiTags, - } from '@nestjs/swagger'; - import { ReviewCreateDto } from '../dtos/review/request/create.dto'; - import { Review } from '../entities/review.entity'; - import { AuthGuard } from '../services/guards/auth.guard'; - import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; - import { ReviewService } from '../services/review.service'; - import { ReviewEditDto } from '../dtos/review/request/edit.dto'; - - @ApiBearerAuth() - @UseGuards(AuthGuard) - @ApiTags('review') - @Controller('review') - export class ReviewController { - constructor(private readonly reviewService: ReviewService) {} - - @ApiCreatedResponse({ - description: 'Review created successfully', - type: Review, - }) - @ApiConflictResponse({ - description: 'Conflict in creating the review', - }) - @ApiBadRequestResponse({ - description: 'Bad Request', - }) - @HttpCode(201) - @Post(':gameId') - public async createReview( - @Req() req: AuthorizedRequest, - @Param('gameId') gameId: string, - @Body() reviewCreateDto: ReviewCreateDto, - ) { - const createdReview = await this.reviewService.createReview( - req.user.id, - gameId, - reviewCreateDto, - ); - return createdReview; - } + Body, + Controller, + HttpCode, + Delete, + Param, + Post, + Put, + Req, + UseGuards, +} from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiConflictResponse, + ApiCreatedResponse, + ApiNotFoundResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReviewCreateDto } from '../dtos/review/request/create.dto'; +import { Review } from '../entities/review.entity'; +import { AuthGuard } from '../services/guards/auth.guard'; +import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; +import { ReviewService } from '../services/review.service'; +import { ReviewEditDto } from '../dtos/review/request/edit.dto'; + +@ApiBearerAuth() +@UseGuards(AuthGuard) +@ApiTags('review') +@Controller('review') +export class ReviewController { + constructor(private readonly reviewService: ReviewService) {} + + @ApiCreatedResponse({ + description: 'Review created successfully', + type: Review, + }) + @ApiConflictResponse({ + description: 'Conflict in creating the review', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(201) + @Post(':gameId') + public async createReview( + @Req() req: AuthorizedRequest, + @Param('gameId') gameId: string, + @Body() reviewCreateDto: ReviewCreateDto, + ) { + const createdReview = await this.reviewService.createReview( + req.user.id, + gameId, + reviewCreateDto, + ); + return createdReview; + } - @HttpCode(200) - @Post(':reviewId/like') - public async likeReview( - @Req() req: AuthorizedRequest, - @Param('reviewId') reviewId: string, - ) { - await this.reviewService.likeReview(req.user.id, reviewId); - return { message: 'Review liked successfully.' }; - } + @HttpCode(200) + @Post(':reviewId/like') + public async likeReview( + @Req() req: AuthorizedRequest, + @Param('reviewId') reviewId: string, + ) { + await this.reviewService.likeReview(req.user.id, reviewId); + return { message: 'Review liked successfully.' }; + } - @HttpCode(200) - @Post(':reviewId/dislike') - public async dislikeReview( - @Req() req: AuthorizedRequest, - @Param('reviewId') reviewId: string, - ) { - await this.reviewService.dislikeReview(req.user.id, reviewId); - return { message: 'Review disliked successfully.' }; - } + @HttpCode(200) + @Post(':reviewId/dislike') + public async dislikeReview( + @Req() req: AuthorizedRequest, + @Param('reviewId') reviewId: string, + ) { + await this.reviewService.dislikeReview(req.user.id, reviewId); + return { message: 'Review disliked successfully.' }; + } - @ApiNotFoundResponse({ description: 'Review is not found!' }) - @HttpCode(204) - @Delete(':reviewId') - public async deleteReview( - @Req() req: AuthorizedRequest, - @Param('reviewId') reviewId: string, - ) { - await this.reviewService.deleteReview(req.user.id, reviewId); - return { message: 'Review deleted successfully.'}; - } + @ApiNotFoundResponse({ description: 'Review is not found!' }) + @HttpCode(204) + @Delete(':reviewId') + public async deleteReview( + @Req() req: AuthorizedRequest, + @Param('reviewId') reviewId: string, + ) { + await this.reviewService.deleteReview(req.user.id, reviewId); + return { message: 'Review deleted successfully.' }; + } @HttpCode(200) @Put(':reviewId') @@ -101,6 +101,4 @@ import { ); return editedReview; } - - } - \ No newline at end of file +} diff --git a/ludos/backend/src/controllers/s3.controller.ts b/ludos/backend/src/controllers/s3.controller.ts index 391c37c5..314928d0 100644 --- a/ludos/backend/src/controllers/s3.controller.ts +++ b/ludos/backend/src/controllers/s3.controller.ts @@ -52,11 +52,12 @@ export class S3Controller { }, }, }) - @UseInterceptors(FileInterceptor('file', { + @UseInterceptors( + FileInterceptor('file', { storage: diskStorage({ destination: './uploads', }), - }) + }), ) public async uploadFile( @Req() _req: AuthorizedRequest, @@ -64,4 +65,4 @@ export class S3Controller { ) { return await this.s3Service.uploadFile(file); } -} \ No newline at end of file +} diff --git a/ludos/backend/src/controllers/user.controller.ts b/ludos/backend/src/controllers/user.controller.ts index dd75196f..b5d08411 100644 --- a/ludos/backend/src/controllers/user.controller.ts +++ b/ludos/backend/src/controllers/user.controller.ts @@ -30,11 +30,12 @@ import { EditUserInfoDto } from '../dtos/user/request/edit-info.dto'; import { UserService } from '../services/user.service'; import { AuthGuard } from '../services/guards/auth.guard'; import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; +import { GetUserInfoResponseDto } from '../dtos/user/response/get-user-info-response.dto'; @ApiTags('user') @Controller('user') export class UserController { - constructor(private readonly userService: UserService) { } + constructor(private readonly userService: UserService) {} @ApiOkResponse({ description: 'Successful Register', type: RegisterResponseDto, @@ -78,7 +79,6 @@ export class UserController { @ApiBadRequestResponse({ description: 'Bad Request', }) - @HttpCode(200) @ApiOperation({ summary: 'Password Reset Request Endpoint' }) @Post('/reset-password') @@ -139,7 +139,7 @@ export class UserController { ) { await this.userService.editInfo(req.user.id, input); } - + @HttpCode(200) @ApiUnauthorizedResponse({ description: 'Invalid User', @@ -147,9 +147,11 @@ export class UserController { @ApiBadRequestResponse({ description: 'Bad Request', }) - @ApiBearerAuth() @ApiOperation({ summary: 'Get User Info Request Endpoint' }) + @ApiOkResponse({ + type: GetUserInfoResponseDto, + }) @Get('/info') public async getUserInfoById(@Req() req: AuthorizedRequest) { return await this.userService.getUserInfo(req.user.id); diff --git a/ludos/backend/src/dtos/game/response/get.response.ts b/ludos/backend/src/dtos/game/response/get.response.ts index 728bc302..8fdacc20 100644 --- a/ludos/backend/src/dtos/game/response/get.response.ts +++ b/ludos/backend/src/dtos/game/response/get.response.ts @@ -18,4 +18,4 @@ export class GameGetResponseDto { @ApiProperty() developer: string; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/post/request/create.dto.ts b/ludos/backend/src/dtos/post/request/create.dto.ts new file mode 100644 index 00000000..1fe89a2b --- /dev/null +++ b/ludos/backend/src/dtos/post/request/create.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsOptional, IsString } from 'class-validator'; + +export class PostCreateDto { + @ApiProperty({ + example: 'Post Title (Optional for now)', + required: false, + }) + @IsString() + @IsOptional() + title: string; + + @ApiProperty({ + description: 'Content of the post', + }) + @IsString() + body: string; + + @ApiProperty({ + description: 'Optional list of links for media', + required: false, + }) + @IsArray() + @IsOptional() + media: string[]; + + @ApiProperty({ + description: 'Optional list of tags', + required: false, + }) + @IsArray() + @IsOptional() + tags: string[]; +} diff --git a/ludos/backend/src/dtos/post/request/update.dto.ts b/ludos/backend/src/dtos/post/request/update.dto.ts new file mode 100644 index 00000000..6cf543cd --- /dev/null +++ b/ludos/backend/src/dtos/post/request/update.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsOptional, IsString } from 'class-validator'; + +export class PostUpdateDto { + @ApiProperty({ + example: 'Post Title', + required: false, + }) + @IsString() + @IsOptional() + title?: string; + + @ApiProperty({ + description: 'Content of the post', + }) + @IsString() + @IsOptional() + body?: string; + + @ApiProperty({ + description: 'Optional list of links for media', + required: false, + }) + @IsArray() + @IsOptional() + media: string[]; + + @ApiProperty({ + description: 'Optional list of tags', + required: false, + }) + @IsArray() + @IsOptional() + tags: string[]; +} diff --git a/ludos/backend/src/dtos/post/response/create.response.dto.ts b/ludos/backend/src/dtos/post/response/create.response.dto.ts new file mode 100644 index 00000000..ef003ede --- /dev/null +++ b/ludos/backend/src/dtos/post/response/create.response.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; + +export class PostCreateResponseDto { + @ApiProperty() + @Expose() + id: string; + + @ApiProperty() + @Expose() + title: string; + + @ApiProperty() + @Expose() + body: string; + + @ApiProperty() + @Expose() + media: string[]; + + @ApiProperty() + @Expose() + tags: string[]; + + @ApiProperty() + @Expose() + createdAt: Date; +} diff --git a/ludos/backend/src/dtos/post/response/get.response.dto.ts b/ludos/backend/src/dtos/post/response/get.response.dto.ts new file mode 100644 index 00000000..13537ce6 --- /dev/null +++ b/ludos/backend/src/dtos/post/response/get.response.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose, Type } from 'class-transformer'; +import { UserInOtherResponsesDto } from '../../user/response/user-in-other-responses.dto'; + +export class PostGetResponseDto { + @ApiProperty() + @Expose() + id: string; + + @ApiProperty() + @Expose() + title: string; + + @ApiProperty({ + type: () => UserInOtherResponsesDto, + }) + @Expose() + @Type(() => UserInOtherResponsesDto) + user: UserInOtherResponsesDto; + @ApiProperty() + @Expose() + body: string; + + @ApiProperty() + @Expose() + media: string[]; + + @ApiProperty() + @Expose() + tags: string[]; + + @Expose() + @ApiProperty() + numberOfLikes: number; + + @Expose() + @ApiProperty() + numberOfDislikes: number; + + @Expose() + @ApiProperty() + isLiked: boolean; + + @Expose() + @ApiProperty() + isDisliked: boolean; + + @ApiProperty() + @Expose() + createdAt: Date; +} diff --git a/ludos/backend/src/dtos/post/response/list.response.dto.ts b/ludos/backend/src/dtos/post/response/list.response.dto.ts new file mode 100644 index 00000000..dd72c6c3 --- /dev/null +++ b/ludos/backend/src/dtos/post/response/list.response.dto.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose, Type } from 'class-transformer'; +import { UserInOtherResponsesDto } from '../../user/response/user-in-other-responses.dto'; + +export class PostListResponseDto { + @Expose() + @ApiProperty() + id: string; + @Expose() + @ApiProperty() + title: string; + @ApiProperty({ + type: UserInOtherResponsesDto, + }) + @Expose() + @Type(() => UserInOtherResponsesDto) + user: UserInOtherResponsesDto; + @Expose() + @ApiProperty() + body: string; + @Expose() + @ApiProperty() + media: string[]; + @Expose() + @ApiProperty() + numberOfLikes: number; + @Expose() + @ApiProperty() + numberOfDislikes: number; + @Expose() + @ApiProperty() + tags: string[]; + @Expose() + @ApiProperty() + createdAt: Date; + @Expose() + @ApiProperty() + isLiked: boolean; + @Expose() + @ApiProperty() + isDisliked: boolean; +} diff --git a/ludos/backend/src/dtos/post/response/page.response.dto.ts b/ludos/backend/src/dtos/post/response/page.response.dto.ts new file mode 100644 index 00000000..57e589e3 --- /dev/null +++ b/ludos/backend/src/dtos/post/response/page.response.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose, Type } from 'class-transformer'; +import { PaginationMetaDto } from '../../common/response/pagination-meta.dto'; +import { PostListResponseDto } from './list.response.dto'; + +export class PostPageResponseDto { + @Expose() + @Type(() => PostListResponseDto) + @ApiProperty({ type: () => [PostListResponseDto] }) + items: PostListResponseDto[]; + @Expose() + @Type(() => PaginationMetaDto) + @ApiProperty({ type: () => PaginationMetaDto }) + meta: PaginationMetaDto; +} diff --git a/ludos/backend/src/dtos/review/request/edit.dto.ts b/ludos/backend/src/dtos/review/request/edit.dto.ts index 24af2b17..c6fed6ed 100644 --- a/ludos/backend/src/dtos/review/request/edit.dto.ts +++ b/ludos/backend/src/dtos/review/request/edit.dto.ts @@ -1,11 +1,10 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString, IsNumber, Min, Max} from 'class-validator'; +import { IsOptional, IsString, IsNumber, Min, Max } from 'class-validator'; export class ReviewEditDto { @ApiProperty({ example: 'This game is amazing!', }) - @IsOptional() @IsString() content: string; @@ -13,10 +12,9 @@ export class ReviewEditDto { @ApiProperty({ example: 4.5, }) - @IsOptional() @IsNumber() @Min(0.0) @Max(5.0) rating: number; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/review/response/create.dto.ts b/ludos/backend/src/dtos/review/response/create.dto.ts index f887b83c..0e1ae2f4 100644 --- a/ludos/backend/src/dtos/review/response/create.dto.ts +++ b/ludos/backend/src/dtos/review/response/create.dto.ts @@ -18,4 +18,4 @@ export class ReviewCreateResponseDto { @ApiProperty() gameId: string; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/review/response/edit.dto.ts b/ludos/backend/src/dtos/review/response/edit.dto.ts index 76727391..b4cee99f 100644 --- a/ludos/backend/src/dtos/review/response/edit.dto.ts +++ b/ludos/backend/src/dtos/review/response/edit.dto.ts @@ -12,4 +12,4 @@ export class ReviewEditResponseDto { @ApiProperty() updatedAt: Date; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/user/request/get-user-info.dto.ts b/ludos/backend/src/dtos/user/request/get-user-info.dto.ts index 8fac37d5..7b42a590 100644 --- a/ludos/backend/src/dtos/user/request/get-user-info.dto.ts +++ b/ludos/backend/src/dtos/user/request/get-user-info.dto.ts @@ -2,9 +2,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; export class GetUserInfoDto { - @ApiProperty({ - example: '1', - }) - @IsString() - id: string; + @ApiProperty({ + example: '1', + }) + @IsString() + id: string; } diff --git a/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts b/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts index 482a9109..a2418111 100644 --- a/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts +++ b/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts @@ -2,10 +2,10 @@ import { ApiProperty } from '@nestjs/swagger'; import { Game } from '../../../entities/game.entity'; export class GetUserInfoResponseDto { - @ApiProperty() - username: string; - @ApiProperty() - email: string; - @ApiProperty() - followedGames: Game[]; + @ApiProperty() + username: string; + @ApiProperty() + email: string; + @ApiProperty() + followedGames: Game[]; } diff --git a/ludos/backend/src/dtos/user/response/user-in-other-responses.dto.ts b/ludos/backend/src/dtos/user/response/user-in-other-responses.dto.ts new file mode 100644 index 00000000..aff873ee --- /dev/null +++ b/ludos/backend/src/dtos/user/response/user-in-other-responses.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; + +export class UserInOtherResponsesDto { + @ApiProperty() + @Expose() + username: string; + @ApiProperty() + @Expose() + email: string; + @ApiProperty() + @Expose() + avatar: string; + @ApiProperty() + @Expose() + fullName: string; +} diff --git a/ludos/backend/src/entities/post.entity.ts b/ludos/backend/src/entities/post.entity.ts new file mode 100644 index 00000000..6884f9d6 --- /dev/null +++ b/ludos/backend/src/entities/post.entity.ts @@ -0,0 +1,78 @@ +import { + Column, + Entity, + PrimaryGeneratedColumn, + ManyToOne, + CreateDateColumn, + UpdateDateColumn, + ManyToMany, + JoinTable, + VirtualColumn, +} from 'typeorm'; +import { User } from './user.entity'; + +@Entity('posts') +export class Post { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => User, { lazy: false }) + user: User; + + @Column({ nullable: true }) + title: string; + + @Column() + body: string; + + @Column('text', { array: true, default: [] }) + media: string[]; + + @ManyToMany('User', 'likedPosts') + @JoinTable({ name: 'post_user_likes' }) + likedUsers: User[]; + + @VirtualColumn({ + query: (post) => + `SELECT COUNT(*) FROM post_user_likes WHERE "postsId" = ${post}.id`, + }) + numberOfLikes: number; + + @ManyToMany('User', 'dislikedPosts') + @JoinTable({ name: 'post_user_dislikes' }) + dislikedUsers: User[]; + + @VirtualColumn({ + query: (post) => + `SELECT COUNT(*) FROM post_user_dislikes WHERE "postsId" = ${post}.id`, + }) + numberOfDislikes: number; + + // @OneToMany(() => Comment) + // comments: Comment[]; + + // @VirtualColumn({ + // query: (post) => `SELECT COUNT(*) FROM comments WHERE parentId = ${post}.id` + // }) + // numberOfComments: number; + + @Column('text', { array: true, default: [] }) + tags: string[]; + + @CreateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + onUpdate: 'CURRENT_TIMESTAMP(6)', + }) + updatedAt: Date; + + isLiked: boolean; + + isDisliked: boolean; +} diff --git a/ludos/backend/src/entities/review.entity.ts b/ludos/backend/src/entities/review.entity.ts index b8c5fd66..15fbe095 100644 --- a/ludos/backend/src/entities/review.entity.ts +++ b/ludos/backend/src/entities/review.entity.ts @@ -1,45 +1,51 @@ import { - Column, - Entity, - ManyToOne, - PrimaryGeneratedColumn, - CreateDateColumn, - ManyToMany, - JoinTable, - UpdateDateColumn, - } from 'typeorm'; - import { Game } from './game.entity'; - import { User } from './user.entity'; - - @Entity('reviews') - export class Review { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column('text') - content: string; - - @Column('float') - rating: number; - - @ManyToOne(() => Game, game => game.reviews, { cascade: true }) - game: Game; - - @ManyToOne(() => User, user => user.reviews, { cascade: true }) - user: User; - - @ManyToMany('User', 'likedReviews') - @JoinTable({ name: 'review_user_likes' }) - likedUsers: User[] - - @ManyToMany('User', 'dislikedReviews') - @JoinTable({ name: 'review_user_dislikes' }) - dislikedUsers: User[]; - - @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' }) - createdAt: Date; - - @UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)' }) - updatedAt: Date; - } - \ No newline at end of file + Column, + Entity, + ManyToOne, + PrimaryGeneratedColumn, + CreateDateColumn, + ManyToMany, + JoinTable, + UpdateDateColumn, +} from 'typeorm'; +import { Game } from './game.entity'; +import { User } from './user.entity'; + +@Entity('reviews') +export class Review { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column('text') + content: string; + + @Column('float') + rating: number; + + @ManyToOne(() => Game, (game) => game.reviews, { cascade: true }) + game: Game; + + @ManyToOne(() => User, (user) => user.reviews, { cascade: true }) + user: User; + + @ManyToMany('User', 'likedReviews') + @JoinTable({ name: 'review_user_likes' }) + likedUsers: User[]; + + @ManyToMany('User', 'dislikedReviews') + @JoinTable({ name: 'review_user_dislikes' }) + dislikedUsers: User[]; + + @CreateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + onUpdate: 'CURRENT_TIMESTAMP(6)', + }) + updatedAt: Date; +} diff --git a/ludos/backend/src/entities/user.entity.ts b/ludos/backend/src/entities/user.entity.ts index b40066de..2f3a41ca 100644 --- a/ludos/backend/src/entities/user.entity.ts +++ b/ludos/backend/src/entities/user.entity.ts @@ -12,6 +12,7 @@ import * as bcrypt from 'bcrypt'; import { Game } from './game.entity'; import { UserType } from '../enums/user-type.enum'; import { Review } from './review.entity'; +import { Post } from './post.entity'; @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') @@ -51,12 +52,21 @@ export class User { @OneToMany('Review', 'user') reviews: Review[]; + @OneToMany('Post', 'user') + posts: Post[]; + @ManyToMany('Review', 'likedUsers') likedReviews: Review[]; @ManyToMany('Review', 'dislikedUsers') dislikedReviews: Review[]; + @ManyToMany('Post', 'likedUsers') + likedPosts: Post[]; + + @ManyToMany('Post', 'dislikedUsers') + dislikedPosts: Post[]; + @BeforeInsert() @BeforeUpdate() async hashPasswordBeforeInsertAndUpdate() { diff --git a/ludos/backend/src/repositories/post.repository.ts b/ludos/backend/src/repositories/post.repository.ts new file mode 100644 index 00000000..ba39c4b2 --- /dev/null +++ b/ludos/backend/src/repositories/post.repository.ts @@ -0,0 +1,143 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource, Repository, UpdateResult, DeleteResult } from 'typeorm'; +import { Post } from '../entities/post.entity'; +import { IPaginationMeta, Pagination, paginate } from 'nestjs-typeorm-paginate'; + +@Injectable() +export class PostRepository extends Repository { + constructor(dataSource: DataSource) { + super(Post, dataSource.createEntityManager()); + } + + public async createPost(input: Partial): Promise { + const post = this.create(input); + await this.insert(post); + return post; + } + + public async updatePost(input: Partial): Promise { + const post = this.create(input); + await this.save(post); + } + public async deletePostByIdAndUserId( + id: string, + userId: string, + ): Promise { + return await this.delete({ id, user: { id: userId } }); + } + public async updatePostByIdAndUserId( + id: string, + userId: string, + input: Partial, + ): Promise { + const post = this.create(input); + return await this.update( + { + id: id, + user: { + id: userId, + }, + }, + post, + ); + } + + public findPostById(id: string): Promise { + return this.findOne({ + where: { id }, + relations: this.getAllRelationsAsList(), + }); + } + + public async findPosts( + page: number, + limit: number, + searchKey?: string, + tags?: string[], + ownerUserId?: string, + userId?: string, // denotes current user, used for like and dislike + isLiked?: boolean, + isDisliked?: boolean, + orderByKey: keyof Post = 'createdAt', + order: 'ASC' | 'DESC' = 'DESC', + ): Promise> { + const queryBuilder = this.createQueryBuilder('posts') + .leftJoinAndSelect('posts.user', 'user') // Assuming the relationship is named 'user' + .where('1=1'); + if (searchKey) { + searchKey = searchKey.trim().replace(/ /g, ' & '); + queryBuilder.andWhere( + `to_tsvector(\'english\', posts.title || \' \' || posts.body) @@ to_tsquery('${searchKey}')`, + ); + } + if (tags) { + queryBuilder.andWhere('posts.tags @> :tags', { tags }); + } + if (ownerUserId) { + queryBuilder.andWhere('posts.userId = :ownerUserId', { ownerUserId }); + } + if (userId && isLiked) { + const subQuery = this.createQueryBuilder() + .select('1') + .from('post_user_likes', 'pul') + .where(`pul.usersId = '${userId}'`) + .andWhere('pul.postsId = posts.id') + .getQuery(); + queryBuilder.andWhere(`EXISTS (${subQuery})`); + } + if (userId && isDisliked && !isLiked) { + const subQuery = this.createQueryBuilder() + .select('1') + .from('post_user_dislikes', 'pud') + .where(`pud.usersId = '${userId}'`) + .andWhere('pud.postsId = posts.id') + .getQuery(); + queryBuilder.andWhere(`EXISTS (${subQuery})`); + } + if (orderByKey) { + queryBuilder.orderBy(`"posts_${orderByKey}"`, order); + } + const posts = await paginate(queryBuilder, { page, limit }); + if (userId) { + await Promise.all( + posts.items.map(async (post) => { + post.isLiked = await this.checkIfPostIsLiked(post.id, userId); + }), + ); + await Promise.all( + posts.items.map(async (post) => { + post.isDisliked = await this.checkIfPostIsDisliked(post.id, userId); + }), + ); + } + return posts; + } + private async checkIfPostIsLiked( + postId: string, + userId: string, + ): Promise { + const result = await this.createQueryBuilder() + .select('1') + .from('post_user_likes', 'pul') + .where(`pul.usersId = '${userId}'`) + .andWhere('pul.postsId = :postId', { postId }) + .getExists(); + return result ? true : false; + } + private async checkIfPostIsDisliked( + postId: string, + userId: string, + ): Promise { + const result = await this.createQueryBuilder() + .select('1') + .from('post_user_dislikes', 'pud') + .where(`pud.usersId = '${userId}'`) + .andWhere('pud.postsId = :postId', { postId }) + .getExists(); + return result ? true : false; + } + + public getAllRelationsAsList() { + return this.metadata.relations.map((relation) => relation.propertyName); + } +} diff --git a/ludos/backend/src/repositories/review.repository.ts b/ludos/backend/src/repositories/review.repository.ts index 75e79a44..00296926 100644 --- a/ludos/backend/src/repositories/review.repository.ts +++ b/ludos/backend/src/repositories/review.repository.ts @@ -34,5 +34,4 @@ export class ReviewRepository extends Repository { public async deleteReview(id: string): Promise { await this.delete(id); } - } diff --git a/ludos/backend/src/repositories/user.repository.ts b/ludos/backend/src/repositories/user.repository.ts index 03a40c47..d0f03add 100644 --- a/ludos/backend/src/repositories/user.repository.ts +++ b/ludos/backend/src/repositories/user.repository.ts @@ -21,17 +21,17 @@ export class UserRepository extends Repository { public findUserByEmail(email: string): Promise { return this.findOneBy({ email: email }); } - + public findUserById(id: string): Promise { return this.findOneBy({ id }); } - public async updateUserPassword(input: Partial, newPassword: string) { + public async updateUserPassword(input: Partial, newPassword: string) { const user = await this.findUserByUsername(input.username); user.password = newPassword; await this.save(user); } - + public async findUserByIdWithRelations(id: string): Promise { const user = await this.findOne({ relations: this.getAllRelationsAsList(), diff --git a/ludos/backend/src/services/config/typeorm-config.service.ts b/ludos/backend/src/services/config/typeorm-config.service.ts index ccc8da49..f906c890 100644 --- a/ludos/backend/src/services/config/typeorm-config.service.ts +++ b/ludos/backend/src/services/config/typeorm-config.service.ts @@ -5,6 +5,7 @@ import { User } from '../../entities/user.entity'; import { ResetPassword } from '../../entities/reset-password.entity'; import { Game } from '../../entities/game.entity'; import { Review } from '../../entities/review.entity'; +import { Post } from '../../entities/post.entity'; @Injectable() export class TypeOrmConfigService implements TypeOrmOptionsFactory { @@ -18,7 +19,7 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory { password: this.configService.get('DB_PASSWORD'), port: this.configService.get('DB_PORT'), database: this.configService.get('DB_NAME'), - entities: [User, ResetPassword, Game, Review], + entities: [User, ResetPassword, Game, Review, Post], synchronize: true, }; } diff --git a/ludos/backend/src/services/game.service.ts b/ludos/backend/src/services/game.service.ts index 4c5e74a9..cdd9e4a4 100644 --- a/ludos/backend/src/services/game.service.ts +++ b/ludos/backend/src/services/game.service.ts @@ -32,7 +32,7 @@ export class GameService { developer: game.developer, }; } catch (e) { - console.log(e) + console.log(e); if (e.code == '23505') { throw new ConflictException(e.detail); } diff --git a/ludos/backend/src/services/post.service.ts b/ludos/backend/src/services/post.service.ts new file mode 100644 index 00000000..ecd25fb9 --- /dev/null +++ b/ludos/backend/src/services/post.service.ts @@ -0,0 +1,140 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; +import { PostCreateDto } from '../dtos/post/request/create.dto'; +import { PostUpdateDto } from '../dtos/post/request/update.dto'; +import { PostCreateResponseDto } from '../dtos/post/response/create.response.dto'; +import { Post } from '../entities/post.entity'; +import { PostRepository } from '../repositories/post.repository'; +import { UserRepository } from '../repositories/user.repository'; + +@Injectable() +export class PostService { + constructor( + private readonly postRepository: PostRepository, + private readonly userRepository: UserRepository, + ) {} + + public async createPost( + userId: string, + input: PostCreateDto, + ): Promise { + const user = await this.userRepository.findUserById(userId); + const post = await this.postRepository.createPost({ + user, + ...input, + }); + return post; + } + public async updatePost( + id: string, + input: PostUpdateDto, + userId: string, + ): Promise { + const updateResult = await this.postRepository.updatePostByIdAndUserId( + id, + userId, + input, + ); + if (updateResult.affected == 0) { + throw new NotFoundException('Post not found or Forbidden'); + } + return; + } + public async deletePost(id: string, userId: string): Promise { + const deleteResult = await this.postRepository.deletePostByIdAndUserId( + id, + userId, + ); + if (deleteResult.affected == 0) { + throw new NotFoundException('Post not found or Forbidden'); + } + return; + } + + public async getPost(id: string, userId?: string): Promise { + const post = await this.postRepository.findPostById(id); + if (!post) { + throw new NotFoundException('Post not found'); + } + post.isLiked = userId + ? post.likedUsers.some((user) => user.id === userId) + : false; + post.isDisliked = userId + ? post.dislikedUsers.some((user) => user.id === userId) + : false; + return post; + } + async likePost(userId: string, postId: string): Promise { + const post = await this.postRepository.findPostById(postId); + if (!post) { + throw new NotFoundException('Post Not Found!'); + } + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } + if (post.likedUsers.some((user) => user.id === userId)) { + post.likedUsers = post.likedUsers.filter((user) => user.id !== userId); + } else if (post.dislikedUsers.some((user) => user.id === userId)) { + post.dislikedUsers = post.dislikedUsers.filter( + (user) => user.id !== userId, + ); + post.likedUsers.push(user); + } else { + post.likedUsers.push(user); + } + + await this.postRepository.updatePost(post); + } + + async dislikePost(userId: string, postId: string): Promise { + const post = await this.postRepository.findPostById(postId); + if (!post) { + throw new NotFoundException('Post Not Found!'); + } + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } + if (post.dislikedUsers.some((user) => user.id === userId)) { + post.dislikedUsers = post.dislikedUsers.filter( + (user) => user.id !== userId, + ); + } else if (post.likedUsers.some((user) => user.id === userId)) { + post.likedUsers = post.likedUsers.filter((user) => user.id !== userId); + post.dislikedUsers.push(user); + } else { + post.dislikedUsers.push(user); + } + + await this.postRepository.updatePost(post); + } + + async listPosts( + page: number, + limit: number, + searchKey?: string, + tags?: string, + ownerUserId?: string, + userId?: string, + isLiked?: boolean, + isDisliked?: boolean, + orderByKey?: keyof Post, + order?: 'ASC' | 'DESC', + ): Promise> { + const tagList = tags ? tags.split(',') : undefined; + const posts = await this.postRepository.findPosts( + page, + limit, + searchKey, + tagList, + ownerUserId, + userId, + isLiked, + isDisliked, + orderByKey, + order, + ); + return posts; + } +} diff --git a/ludos/backend/src/services/review.service.ts b/ludos/backend/src/services/review.service.ts index 6fe096b8..7ba54f49 100644 --- a/ludos/backend/src/services/review.service.ts +++ b/ludos/backend/src/services/review.service.ts @@ -1,200 +1,195 @@ import { - ConflictException, - Injectable, - InternalServerErrorException, - NotFoundException, - } from '@nestjs/common'; - import { GameRepository } from '../repositories/game.repository'; - import { UserRepository } from '../repositories/user.repository'; - import { ReviewCreateDto } from '../dtos/review/request/create.dto'; - import { ReviewCreateResponseDto } from '../dtos/review/response/create.dto'; - import { ReviewRepository } from '../repositories/review.repository'; - import { log } from 'console'; - import { ReviewEditDto } from '../dtos/review/request/edit.dto'; + ConflictException, + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { GameRepository } from '../repositories/game.repository'; +import { UserRepository } from '../repositories/user.repository'; +import { ReviewCreateDto } from '../dtos/review/request/create.dto'; +import { ReviewCreateResponseDto } from '../dtos/review/response/create.dto'; +import { ReviewRepository } from '../repositories/review.repository'; +import { log } from 'console'; +import { ReviewEditDto } from '../dtos/review/request/edit.dto'; import { ReviewEditResponseDto } from '../dtos/review/response/edit.dto'; - - @Injectable() - export class ReviewService { - constructor( - private readonly gameRepository: GameRepository, - private readonly userRepository: UserRepository, - private readonly reviewRepository: ReviewRepository, - ) {} - - public async createReview( - userId: string, - gameId: string, - reviewCreateDto: ReviewCreateDto, - ): Promise { - try { - const user = await this.userRepository.findUserById(userId); - const game = await this.gameRepository.findGameById(gameId); - const review = await this.reviewRepository.createReview({ - content: reviewCreateDto.content, - rating: reviewCreateDto.rating, - user: user, - game: game - }); - - console.log(game.reviews); - return { - id: review.id, - rating: review.rating, - content: review.content, - userId: review.user.id, - gameId: review.game.id, - createdAt: review.createdAt, - }; - } catch (e) { - console.log(e) - if (e.code == '23505') { - throw new ConflictException(e.detail); - } - throw new InternalServerErrorException(); + +@Injectable() +export class ReviewService { + constructor( + private readonly gameRepository: GameRepository, + private readonly userRepository: UserRepository, + private readonly reviewRepository: ReviewRepository, + ) {} + + public async createReview( + userId: string, + gameId: string, + reviewCreateDto: ReviewCreateDto, + ): Promise { + try { + const user = await this.userRepository.findUserById(userId); + const game = await this.gameRepository.findGameById(gameId); + const review = await this.reviewRepository.createReview({ + content: reviewCreateDto.content, + rating: reviewCreateDto.rating, + user: user, + game: game, + }); + + console.log(game.reviews); + return { + id: review.id, + rating: review.rating, + content: review.content, + userId: review.user.id, + gameId: review.game.id, + createdAt: review.createdAt, + }; + } catch (e) { + console.log(e); + if (e.code == '23505') { + throw new ConflictException(e.detail); } + throw new InternalServerErrorException(); } + } + public async likeReview(userId: string, reviewId: string): Promise { + try { + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } - public async likeReview(userId: string, reviewId: string): Promise { - try { - const user = await this.userRepository.findUserById(userId); - if (!user) { - throw new NotFoundException('User Not Found!'); - } - - const review = await this.reviewRepository.findReviewByIdWithLikedUsers(reviewId); - if (!review) { - throw new NotFoundException('Review Not Found!'); - } - - if (review.likedUsers.find(likedUser => likedUser.id === userId)) { - log("User already liked the review."); - review.likedUsers = review.likedUsers.filter(likedUser => likedUser.id !== userId); - } - else { - review.likedUsers.push(user); - } - - await this.reviewRepository.updateReview(review); - - } catch (e) { - if (e instanceof NotFoundException) { - throw e; - } else { - console.log(e); - throw new InternalServerErrorException(); - } + const review = + await this.reviewRepository.findReviewByIdWithLikedUsers(reviewId); + if (!review) { + throw new NotFoundException('Review Not Found!'); } - } + if (review.likedUsers.find((likedUser) => likedUser.id === userId)) { + log('User already liked the review.'); + review.likedUsers = review.likedUsers.filter( + (likedUser) => likedUser.id !== userId, + ); + } else { + review.likedUsers.push(user); + } - public async dislikeReview(userId: string, reviewId: string): Promise { - try { - const user = await this.userRepository.findUserById(userId); - if (!user) { - throw new NotFoundException('User Not Found!'); - } - - const review = await this.reviewRepository.findReviewByIdWithDislikedUsers(reviewId); - if (!review) { - throw new NotFoundException('Review Not Found!'); - } - - if (review.dislikedUsers.find(dislikedUser => dislikedUser.id === userId)) { - log('User has already disliked the review.'); - review.dislikedUsers = review.dislikedUsers.filter(dislikedUser => dislikedUser.id !== userId); - } - - else { - review.dislikedUsers.push(user); - } - - await this.reviewRepository.updateReview(review); - - } catch (e) { - if (e instanceof NotFoundException) { - throw e; - } else { - console.log(e); - throw new InternalServerErrorException(); - } + await this.reviewRepository.updateReview(review); + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); } } + } - public async deleteReview(userId: string, reviewId: string): Promise { - try { - - const review = await this.reviewRepository.findReviewById(reviewId); - if (!review) { - throw new NotFoundException('Review Not Found!'); - } - - const user = await this.userRepository.findUserById(userId); - if (!user) { - throw new NotFoundException('User Not Found!'); - } - - await this.reviewRepository.deleteReview(reviewId); - - } catch (e) { - if (e instanceof NotFoundException) { - throw e; - } else { - console.log(e); - throw new InternalServerErrorException(); - } + public async dislikeReview(userId: string, reviewId: string): Promise { + try { + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } + + const review = + await this.reviewRepository.findReviewByIdWithDislikedUsers(reviewId); + if (!review) { + throw new NotFoundException('Review Not Found!'); + } + + if ( + review.dislikedUsers.find((dislikedUser) => dislikedUser.id === userId) + ) { + log('User has already disliked the review.'); + review.dislikedUsers = review.dislikedUsers.filter( + (dislikedUser) => dislikedUser.id !== userId, + ); + } else { + review.dislikedUsers.push(user); + } + + await this.reviewRepository.updateReview(review); + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); } } + } + + public async deleteReview(userId: string, reviewId: string): Promise { + try { + const review = await this.reviewRepository.findReviewById(reviewId); + if (!review) { + throw new NotFoundException('Review Not Found!'); + } + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } - public async editReview( - userId: string, - reviewId: string, - reviewEditDto: ReviewEditDto, - ): Promise { - try { - const review = await this.reviewRepository.findReviewById(reviewId); - if (!review) { - throw new NotFoundException('Review Not Found!'); - } - - const user = await this.userRepository.findUserById(userId); - if (!user) { - throw new NotFoundException('User Not Found!'); - } - - if (!reviewEditDto.content && !reviewEditDto.rating) { - throw new NotFoundException('Please provide at least one field to update!'); - } - - if (reviewEditDto.content) { - review.content = reviewEditDto.content; - } - - if (reviewEditDto.rating) { - review.rating = reviewEditDto.rating; - } - - await this.reviewRepository.updateReview(review); - - return { - id: review.id, - content: reviewEditDto.content, - rating: reviewEditDto.rating, - updatedAt: review.updatedAt, - }; - - - } catch (e) { - if (e instanceof NotFoundException) { - throw e; - } else { - console.log(e); - throw new InternalServerErrorException(); - } + await this.reviewRepository.deleteReview(reviewId); + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); } } - + } + public async editReview( + userId: string, + reviewId: string, + reviewEditDto: ReviewEditDto, + ): Promise { + try { + const review = await this.reviewRepository.findReviewById(reviewId); + if (!review) { + throw new NotFoundException('Review Not Found!'); + } + + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } + + if (!reviewEditDto.content && !reviewEditDto.rating) { + throw new NotFoundException( + 'Please provide at least one field to update!', + ); + } + + if (reviewEditDto.content) { + review.content = reviewEditDto.content; + } + + if (reviewEditDto.rating) { + review.rating = reviewEditDto.rating; + } + + await this.reviewRepository.updateReview(review); + + return { + id: review.id, + content: reviewEditDto.content, + rating: reviewEditDto.rating, + updatedAt: review.updatedAt, + }; + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); + } + } } - \ No newline at end of file +} diff --git a/ludos/backend/src/services/s3.service.ts b/ludos/backend/src/services/s3.service.ts index 6c486ecf..2fc45489 100644 --- a/ludos/backend/src/services/s3.service.ts +++ b/ludos/backend/src/services/s3.service.ts @@ -1,18 +1,16 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { S3 } from "@aws-sdk/client-s3"; -import { Upload } from "@aws-sdk/lib-storage"; +import { S3 } from '@aws-sdk/client-s3'; +import { Upload } from '@aws-sdk/lib-storage'; import { createReadStream } from 'fs'; import { UploadResponseDto } from '../dtos/s3/response/upload-response.dto'; @Injectable() export class S3Service { - constructor( - private readonly configService: ConfigService, - ) {} + constructor(private readonly configService: ConfigService) {} public initializeS3(): S3 { - let s3 = new S3({ + const s3 = new S3({ credentials: { accessKeyId: this.configService.get('AWS_ACCESS_KEY'), secretAccessKey: this.configService.get('AWS_SECRET_KEY'), @@ -22,22 +20,24 @@ export class S3Service { return s3; } - public async uploadFile(file: Express.Multer.File): Promise { - let s3 = this.initializeS3(); - let stream = createReadStream(file.path); - let uploadParams = { + public async uploadFile( + file: Express.Multer.File, + ): Promise { + const s3 = this.initializeS3(); + const stream = createReadStream(file.path); + const uploadParams = { Bucket: this.configService.get('AWS_BUCKET_NAME'), Body: stream, Key: file.filename, - } - let d = await new Upload({ + }; + const d = await new Upload({ client: s3, - params: uploadParams + params: uploadParams, }).done(); - if ("Location" in d) { + if ('Location' in d) { return { url: d.Location, - } + }; } return; } diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index f1a917c6..48457b80 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -32,7 +32,7 @@ export class UserService { private readonly resetPasswordRepository: ResetPasswordRepository, private readonly jwtService: JwtService, private readonly configService: ConfigService, - ) {} + ) {} public async register(input: RegisterDto): Promise { try { @@ -189,7 +189,7 @@ export class UserService { delete updated.password; await this.userRepository.save(updated); } - + public async getUserInfo(userId: string): Promise { const user = await this.userRepository.findUserByIdWithRelations(userId); if (!user) { From 8426530275ddccb9f7f7e68b7676902bb071ed93 Mon Sep 17 00:00:00 2001 From: omersafakbebek Date: Tue, 21 Nov 2023 12:43:29 +0300 Subject: [PATCH 2/4] refactor --- ludos/backend/src/controllers/post.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ludos/backend/src/controllers/post.controller.ts b/ludos/backend/src/controllers/post.controller.ts index 7cdd7c51..3c86a2bf 100644 --- a/ludos/backend/src/controllers/post.controller.ts +++ b/ludos/backend/src/controllers/post.controller.ts @@ -44,7 +44,7 @@ export class PostController { constructor(private readonly postService: PostService) {} @ApiCreatedResponse({ - description: 'Game created successfully', + description: 'Post created successfully', type: PostCreateResponseDto, }) @ApiBadRequestResponse({ From d9803b6cacb3dc8a3d84bcafa2c0058fae2dfad8 Mon Sep 17 00:00:00 2001 From: omersafakbebek Date: Tue, 21 Nov 2023 19:42:35 +0300 Subject: [PATCH 3/4] added game to post entity --- .../src/controllers/post.controller.ts | 5 +++++ .../src/dtos/game/response/get.response.ts | 7 ++++++ .../src/dtos/post/request/create.dto.ts | 10 ++++++++- .../src/dtos/post/request/update.dto.ts | 9 +++++++- .../dtos/post/response/get.response.dto.ts | 9 +++++--- .../dtos/post/response/list.response.dto.ts | 9 +++++--- .../response/user-in-other-responses.dto.ts | 3 +++ ludos/backend/src/entities/post.entity.ts | 4 ++++ .../src/repositories/post.repository.ts | 7 +++++- ludos/backend/src/services/post.service.ts | 22 +++++++++++++++++-- 10 files changed, 74 insertions(+), 11 deletions(-) diff --git a/ludos/backend/src/controllers/post.controller.ts b/ludos/backend/src/controllers/post.controller.ts index 3c86a2bf..9a957581 100644 --- a/ludos/backend/src/controllers/post.controller.ts +++ b/ludos/backend/src/controllers/post.controller.ts @@ -55,6 +55,7 @@ export class PostController { type: PostCreateResponseDto, }) @ApiForbiddenResponse({ description: 'User should login' }) + @ApiNotFoundResponse({ description: 'Game not found' }) @ApiOperation({ summary: 'Create Post Endpoint' }) @ApiBearerAuth() @UseInterceptors(new SerializerInterceptor(PostCreateResponseDto)) @@ -91,6 +92,7 @@ export class PostController { @ApiBadRequestResponse({ description: 'Bad Request', }) + @ApiNotFoundResponse({ description: 'Game not found' }) @ApiBearerAuth() @UseGuards(AuthGuard) @Put(':id') @@ -163,6 +165,7 @@ export class PostController { description: 'Comma separated list of tags. This filter works like AND', example: 'tag1,tag2,tag3', }) + @ApiQuery({ name: 'gameId', required: false }) @ApiQuery({ name: 'ownerUserId', required: false }) @ApiQuery({ name: 'isLiked', @@ -203,6 +206,7 @@ export class PostController { @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number = 10, @Query('searchKey') searchKey?: string, @Query('tags') tags?: string, + @Query('gameId') gameId?: string, @Query('ownerUserId') ownerUserId?: string, @Query('isLiked', new DefaultValuePipe(false), ParseBoolPipe) isLiked?: boolean, @@ -216,6 +220,7 @@ export class PostController { limit, searchKey, tags, + gameId, ownerUserId, req.user && req.user.id, isLiked, diff --git a/ludos/backend/src/dtos/game/response/get.response.ts b/ludos/backend/src/dtos/game/response/get.response.ts index 8fdacc20..c8781ed7 100644 --- a/ludos/backend/src/dtos/game/response/get.response.ts +++ b/ludos/backend/src/dtos/game/response/get.response.ts @@ -1,21 +1,28 @@ import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; export class GameGetResponseDto { + @Expose() @ApiProperty() id: string; + @Expose() @ApiProperty() title: string; + @Expose() @ApiProperty() coverLink: string; + @Expose() @ApiProperty() gameBio: string; + @Expose() @ApiProperty() releaseDate: Date; + @Expose() @ApiProperty() developer: string; } diff --git a/ludos/backend/src/dtos/post/request/create.dto.ts b/ludos/backend/src/dtos/post/request/create.dto.ts index 1fe89a2b..ec9042f4 100644 --- a/ludos/backend/src/dtos/post/request/create.dto.ts +++ b/ludos/backend/src/dtos/post/request/create.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsOptional, IsString, IsUUID } from 'class-validator'; export class PostCreateDto { @ApiProperty({ @@ -16,6 +16,14 @@ export class PostCreateDto { @IsString() body: string; + @ApiProperty({ + description: 'Id of the game', + required: false, + }) + @IsUUID() + @IsOptional() + gameId?: string; + @ApiProperty({ description: 'Optional list of links for media', required: false, diff --git a/ludos/backend/src/dtos/post/request/update.dto.ts b/ludos/backend/src/dtos/post/request/update.dto.ts index 6cf543cd..6f39b6b0 100644 --- a/ludos/backend/src/dtos/post/request/update.dto.ts +++ b/ludos/backend/src/dtos/post/request/update.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsOptional, IsString, IsUUID } from 'class-validator'; export class PostUpdateDto { @ApiProperty({ @@ -17,6 +17,13 @@ export class PostUpdateDto { @IsOptional() body?: string; + @ApiProperty({ + description: 'Content of the post', + }) + @IsUUID() + @IsOptional() + gameId?: string; + @ApiProperty({ description: 'Optional list of links for media', required: false, diff --git a/ludos/backend/src/dtos/post/response/get.response.dto.ts b/ludos/backend/src/dtos/post/response/get.response.dto.ts index 13537ce6..29ba8856 100644 --- a/ludos/backend/src/dtos/post/response/get.response.dto.ts +++ b/ludos/backend/src/dtos/post/response/get.response.dto.ts @@ -1,6 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Expose, Type } from 'class-transformer'; import { UserInOtherResponsesDto } from '../../user/response/user-in-other-responses.dto'; +import { GameGetResponseDto } from '../../game/response/get.response'; export class PostGetResponseDto { @ApiProperty() @@ -11,11 +12,13 @@ export class PostGetResponseDto { @Expose() title: string; - @ApiProperty({ - type: () => UserInOtherResponsesDto, - }) + @Expose() + @Type(() => GameGetResponseDto) + @ApiProperty({ type: GameGetResponseDto }) + game: GameGetResponseDto; @Expose() @Type(() => UserInOtherResponsesDto) + @ApiProperty({ type: UserInOtherResponsesDto }) user: UserInOtherResponsesDto; @ApiProperty() @Expose() diff --git a/ludos/backend/src/dtos/post/response/list.response.dto.ts b/ludos/backend/src/dtos/post/response/list.response.dto.ts index dd72c6c3..2a195b4d 100644 --- a/ludos/backend/src/dtos/post/response/list.response.dto.ts +++ b/ludos/backend/src/dtos/post/response/list.response.dto.ts @@ -1,6 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Expose, Type } from 'class-transformer'; import { UserInOtherResponsesDto } from '../../user/response/user-in-other-responses.dto'; +import { GameGetResponseDto } from '../../game/response/get.response'; export class PostListResponseDto { @Expose() @@ -9,11 +10,13 @@ export class PostListResponseDto { @Expose() @ApiProperty() title: string; - @ApiProperty({ - type: UserInOtherResponsesDto, - }) + @Expose() + @Type(() => GameGetResponseDto) + @ApiProperty({ type: GameGetResponseDto }) + game: GameGetResponseDto; @Expose() @Type(() => UserInOtherResponsesDto) + @ApiProperty({ type: UserInOtherResponsesDto }) user: UserInOtherResponsesDto; @Expose() @ApiProperty() diff --git a/ludos/backend/src/dtos/user/response/user-in-other-responses.dto.ts b/ludos/backend/src/dtos/user/response/user-in-other-responses.dto.ts index aff873ee..ab340633 100644 --- a/ludos/backend/src/dtos/user/response/user-in-other-responses.dto.ts +++ b/ludos/backend/src/dtos/user/response/user-in-other-responses.dto.ts @@ -2,6 +2,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { Expose } from 'class-transformer'; export class UserInOtherResponsesDto { + @ApiProperty() + @Expose() + id: string; @ApiProperty() @Expose() username: string; diff --git a/ludos/backend/src/entities/post.entity.ts b/ludos/backend/src/entities/post.entity.ts index 6884f9d6..e41837f0 100644 --- a/ludos/backend/src/entities/post.entity.ts +++ b/ludos/backend/src/entities/post.entity.ts @@ -10,6 +10,7 @@ import { VirtualColumn, } from 'typeorm'; import { User } from './user.entity'; +import { Game } from './game.entity'; @Entity('posts') export class Post { @@ -19,6 +20,9 @@ export class Post { @ManyToOne(() => User, { lazy: false }) user: User; + @ManyToOne(() => Game, { lazy: false }) + game: Game; + @Column({ nullable: true }) title: string; diff --git a/ludos/backend/src/repositories/post.repository.ts b/ludos/backend/src/repositories/post.repository.ts index ba39c4b2..c812de10 100644 --- a/ludos/backend/src/repositories/post.repository.ts +++ b/ludos/backend/src/repositories/post.repository.ts @@ -54,6 +54,7 @@ export class PostRepository extends Repository { limit: number, searchKey?: string, tags?: string[], + gameId?: string, ownerUserId?: string, userId?: string, // denotes current user, used for like and dislike isLiked?: boolean, @@ -62,7 +63,8 @@ export class PostRepository extends Repository { order: 'ASC' | 'DESC' = 'DESC', ): Promise> { const queryBuilder = this.createQueryBuilder('posts') - .leftJoinAndSelect('posts.user', 'user') // Assuming the relationship is named 'user' + .leftJoinAndSelect('posts.user', 'user') + .leftJoinAndSelect('posts.game', 'game') .where('1=1'); if (searchKey) { searchKey = searchKey.trim().replace(/ /g, ' & '); @@ -73,6 +75,9 @@ export class PostRepository extends Repository { if (tags) { queryBuilder.andWhere('posts.tags @> :tags', { tags }); } + if (gameId) { + queryBuilder.andWhere('posts.gameId = :gameId', { gameId }); + } if (ownerUserId) { queryBuilder.andWhere('posts.userId = :ownerUserId', { ownerUserId }); } diff --git a/ludos/backend/src/services/post.service.ts b/ludos/backend/src/services/post.service.ts index ecd25fb9..b504f11d 100644 --- a/ludos/backend/src/services/post.service.ts +++ b/ludos/backend/src/services/post.service.ts @@ -6,22 +6,30 @@ import { PostCreateResponseDto } from '../dtos/post/response/create.response.dto import { Post } from '../entities/post.entity'; import { PostRepository } from '../repositories/post.repository'; import { UserRepository } from '../repositories/user.repository'; +import { GameRepository } from '../repositories/game.repository'; @Injectable() export class PostService { constructor( private readonly postRepository: PostRepository, private readonly userRepository: UserRepository, + private readonly gameRepository: GameRepository, ) {} public async createPost( userId: string, input: PostCreateDto, ): Promise { + const { gameId, ...partialPost } = input; const user = await this.userRepository.findUserById(userId); + const game = await this.gameRepository.findGameById(gameId); + if (!game) { + throw new NotFoundException('Game not found'); + } const post = await this.postRepository.createPost({ user, - ...input, + game, + ...partialPost, }); return post; } @@ -30,10 +38,18 @@ export class PostService { input: PostUpdateDto, userId: string, ): Promise { + const { gameId, ...partialPost } = input; + const game = await this.gameRepository.findGameById(gameId); + if (!game) { + throw new NotFoundException('Game not found'); + } const updateResult = await this.postRepository.updatePostByIdAndUserId( id, userId, - input, + { + game, + ...partialPost, + }, ); if (updateResult.affected == 0) { throw new NotFoundException('Post not found or Forbidden'); @@ -115,6 +131,7 @@ export class PostService { limit: number, searchKey?: string, tags?: string, + gameId?: string, ownerUserId?: string, userId?: string, isLiked?: boolean, @@ -128,6 +145,7 @@ export class PostService { limit, searchKey, tagList, + gameId, ownerUserId, userId, isLiked, From 5bd4999afb989ce065dbcc7a32231a27367ec3e6 Mon Sep 17 00:00:00 2001 From: omersafakbebek Date: Tue, 21 Nov 2023 22:23:46 +0300 Subject: [PATCH 4/4] fixed conflicts --- .../src/dtos/user/response/get-user-info-response.dto.ts | 4 +++- ludos/backend/src/services/config/typeorm-config.service.ts | 3 +-- ludos/backend/src/services/user.service.ts | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts b/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts index d68ea66f..f8a33d76 100644 --- a/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts +++ b/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts @@ -14,7 +14,9 @@ export class GetUserInfoResponseDto { ratings: Rating[]; @ApiProperty() isNotificationEnabled: boolean; - @ApiProperty() + @ApiProperty({ + enum: UserType, + }) userType: UserType; @ApiProperty() fullName: string; diff --git a/ludos/backend/src/services/config/typeorm-config.service.ts b/ludos/backend/src/services/config/typeorm-config.service.ts index d5f6b2ce..fbf37604 100644 --- a/ludos/backend/src/services/config/typeorm-config.service.ts +++ b/ludos/backend/src/services/config/typeorm-config.service.ts @@ -21,8 +21,7 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory { password: this.configService.get('DB_PASSWORD'), port: this.configService.get('DB_PORT'), database: this.configService.get('DB_NAME'), - entities: [User, ResetPassword, Game, Review, Post], - entities: [User, ResetPassword, Game, Review, Rating, Comment], + entities: [User, ResetPassword, Game, Review, Rating, Comment, Post], synchronize: true, }; } diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index b89cc6f4..c0b2ddbd 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -207,7 +207,6 @@ export class UserService { response.avatar = user.avatar; response.aboutMe = user.aboutMe; response.steamUrl = user.steamUrl; - return response; } }