diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 4ee232a..2ca536e 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -132,4 +132,5 @@ export const enum CriticismMessage { CONTENT_IS_REQUIRED = "متن انتقادات مورد نیاز است", CONTENT_IS_STRING = "متن انتقادات باید رشته باشد", NOT_FOUND = "انتقادی با این ایدی یافت نشد", + SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد", } diff --git a/src/modules/criticisms/DTO/search-criticism-query.dto.ts b/src/modules/criticisms/DTO/search-criticism-query.dto.ts new file mode 100644 index 0000000..10abcd1 --- /dev/null +++ b/src/modules/criticisms/DTO/search-criticism-query.dto.ts @@ -0,0 +1,17 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { IsDateString, IsOptional, IsString } from "class-validator"; + +import { PaginationDto } from "../../../common/DTO/pagination.dto"; +import { CriticismMessage } from "../../../common/enums/message.enum"; + +export class SearchCriticismQueryDto extends PaginationDto { + @IsOptional() + @IsDateString() + @ApiPropertyOptional({ description: "since query", example: "2025-01-27" }) + since?: string; + + @IsOptional() + @IsString({ message: CriticismMessage.SEARCH_QUERY_STRING }) + @ApiPropertyOptional({ description: "Search query", example: "search query" }) + q?: string; +} diff --git a/src/modules/criticisms/criticisms.controller.ts b/src/modules/criticisms/criticisms.controller.ts index a80de8a..61991bf 100644 --- a/src/modules/criticisms/criticisms.controller.ts +++ b/src/modules/criticisms/criticisms.controller.ts @@ -1,11 +1,14 @@ -import { Body, Controller, Delete, Get, Param, Post } from "@nestjs/common"; +import { Body, Controller, Delete, Get, Param, Post, Query } from "@nestjs/common"; import { ApiOperation, ApiTags } from "@nestjs/swagger"; import { CreateCriticismDto } from "./DTO/create-criticism.dto"; +import { SearchCriticismQueryDto } from "./DTO/search-criticism-query.dto"; import { CriticismsService } from "./providers/criticisms.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Roles } from "../../common/decorators/roles.decorator"; +import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; +import { User } from "../users/entities/user.entity"; import { RoleEnum } from "../users/enums/role.enum"; @Controller("criticisms") @@ -13,18 +16,19 @@ import { RoleEnum } from "../users/enums/role.enum"; export class CriticismController { constructor(private readonly criticismService: CriticismsService) {} + @AuthGuards() @ApiOperation({ summary: "Create a new criticism" }) @Post() - create(@Body() createDto: CreateCriticismDto) { - return this.criticismService.create(createDto); + create(@Body() createDto: CreateCriticismDto, @UserDec() user: User) { + return this.criticismService.create(createDto, user.id); } @AuthGuards() @Roles(RoleEnum.ADMIN) @ApiOperation({ summary: "Get all Criticism ===> login as admin" }) @Get() - findAll() { - return this.criticismService.findAll(); + findAll(@Query() queryDto: SearchCriticismQueryDto) { + return this.criticismService.findAll(queryDto); } @AuthGuards() diff --git a/src/modules/criticisms/criticisms.module.ts b/src/modules/criticisms/criticisms.module.ts index ea3110e..ed3aae4 100644 --- a/src/modules/criticisms/criticisms.module.ts +++ b/src/modules/criticisms/criticisms.module.ts @@ -7,9 +7,10 @@ import { Criticism } from "./entities/criticism.entity"; import { CriticismsService } from "./providers/criticisms.service"; import { CriticismFileRepository } from "./repositories/criticisms-file.repository"; import { CriticismRepository } from "./repositories/criticisms.repository"; +import { UsersModule } from "../users/users.module"; @Module({ - imports: [TypeOrmModule.forFeature([Criticism, CriticismFile])], + imports: [TypeOrmModule.forFeature([Criticism, CriticismFile]), UsersModule], providers: [CriticismsService, CriticismRepository, CriticismFileRepository], controllers: [CriticismController], exports: [], diff --git a/src/modules/criticisms/entities/criticism.entity.ts b/src/modules/criticisms/entities/criticism.entity.ts index 70672f5..19ef458 100644 --- a/src/modules/criticisms/entities/criticism.entity.ts +++ b/src/modules/criticisms/entities/criticism.entity.ts @@ -1,19 +1,24 @@ -import { Column, Entity, OneToMany } from "typeorm"; +import { Column, Entity, Index, ManyToOne, OneToMany } from "typeorm"; import { CriticismFile } from "./criticism-file.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; +import { User } from "../../users/entities/user.entity"; @Entity() export class Criticism extends BaseEntity { @Column({ type: "int", generated: "identity", insert: false }) numericId: number; + @Index("title") @Column({ type: "varchar", length: 150, nullable: false }) title: string; @Column({ type: "text", nullable: false }) content: string; + @ManyToOne(() => User, (user) => user.criticisms, { nullable: false, onDelete: "CASCADE" }) + user: User; + @Column({ type: "boolean", default: false }) isRead: boolean; diff --git a/src/modules/criticisms/providers/criticisms.service.ts b/src/modules/criticisms/providers/criticisms.service.ts index 6ed55c8..84ac167 100644 --- a/src/modules/criticisms/providers/criticisms.service.ts +++ b/src/modules/criticisms/providers/criticisms.service.ts @@ -1,19 +1,27 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import { CommonMessage, CriticismMessage } from "../../../common/enums/message.enum"; +import { UsersService } from "../../users/providers/users.service"; +import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { CreateCriticismDto } from "../DTO/create-criticism.dto"; +import { SearchCriticismQueryDto } from "../DTO/search-criticism-query.dto"; import { CriticismRepository } from "../repositories/criticisms.repository"; @Injectable() export class CriticismsService { - constructor(private readonly criticismRepository: CriticismRepository) {} + constructor( + private readonly criticismRepository: CriticismRepository, + private readonly usersService: UsersService, + ) {} - async create(createDto: CreateCriticismDto) { + async create(createDto: CreateCriticismDto, userId: string) { + const { user } = await this.usersService.findOneById(userId); const files = createDto.files.map((file) => { return { fileUrl: file }; }); const criticism = this.criticismRepository.create({ ...createDto, + user, files, }); await this.criticismRepository.save(criticism); @@ -25,10 +33,25 @@ export class CriticismsService { //##################################### ## - async findAll() { - const criticisms = await this.criticismRepository.find(); + async findAll(queryDto: SearchCriticismQueryDto) { + const { limit, skip } = PaginationUtils(queryDto); + + const queryBuilder = this.criticismRepository.createQueryBuilder("criticism"); + + if (queryDto.since) { + queryBuilder.andWhere("criticism.createdAt >= :since", { since: queryDto.since }); + } + + if (queryDto.q) { + queryBuilder.andWhere("criticism.title LIKE :q", { q: `%${queryDto.q}%` }); + } + queryBuilder.orderBy("criticism.createdAt", "DESC"); + + const [criticisms, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); + return { criticisms, + count, }; } diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index fafcd14..d729541 100644 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -3,6 +3,7 @@ import { Column, Entity, ManyToMany, ManyToOne, OneToMany } from "typeorm"; import { Role } from "./role.entity"; import { UserGroup } from "./user-group.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; +import { Criticism } from "../../criticisms/entities/criticism.entity"; import { TicketMessage } from "../../tickets/entities/ticket-message.entity"; import { Ticket } from "../../tickets/entities/ticket.entity"; @@ -43,4 +44,7 @@ export class User extends BaseEntity { @ManyToMany(() => UserGroup, (group) => group.users) groups: UserGroup[]; + + @OneToMany(() => Criticism, (criticism) => criticism.user) + criticisms: Criticism[]; } diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index 2aef12f..d4b8c04 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Injectable, Logger } from "@nestjs/common"; +import { BadRequestException, Injectable } from "@nestjs/common"; import slugify from "slugify"; import { In, Not } from "typeorm"; @@ -31,7 +31,6 @@ export class UsersService { name: true, }, }; - private logger = new Logger(UsersService.name); constructor( private userRepository: UserRepository, private roleRepository: RoleRepository, @@ -40,8 +39,15 @@ export class UsersService { /************************************************************ */ + async findOneById(id: string) { + const user = await this.userRepository.findOneBy({ id }); + if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + return { user }; + } + + /************************************************************ */ + async getMe(userId: string) { - this.logger.debug("sss"); const user = await this.userRepository.findOne({ where: { id: userId }, select: this.userSelect }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return { user };