chore: add criritisims module
This commit is contained in:
@@ -18,6 +18,7 @@ import { AnnouncementsModule } from "./modules/announcements/announcement.module
|
||||
import { AuthModule } from "./modules/auth/auth.module";
|
||||
import { BusinessesModule } from "./modules/businesses/businesses.module";
|
||||
import { CompaniesModule } from "./modules/companies/companies.module";
|
||||
import { CriticismModule } from "./modules/criticisms/criticisms.module";
|
||||
import { IndustriesModule } from "./modules/industries/industries.module";
|
||||
import { InvoicesModule } from "./modules/invoices/invoices.module";
|
||||
import { NotificationModule } from "./modules/notifications/notifications.module";
|
||||
@@ -55,6 +56,7 @@ import { UtilsModule } from "./modules/utils/utils.module";
|
||||
InvoicesModule,
|
||||
BusinessesModule,
|
||||
AnnouncementsModule,
|
||||
CriticismModule,
|
||||
],
|
||||
// providers: [
|
||||
// {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Cascade, Collection, Entity, EntityRepositoryType, OneToMany, Property,
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { Announcement } from "../../announcements/entities/announcement.entity";
|
||||
import { Company } from "../../companies/entities/company.entity";
|
||||
import { Criticism } from "../../criticisms/entities/criticism.entity";
|
||||
import { Industry } from "../../industries/entities/industry.entity";
|
||||
import { Invoice } from "../../invoices/entities/invoice.entity";
|
||||
import { TicketCategory } from "../../tickets/entities/ticket-category.entity";
|
||||
@@ -39,5 +40,8 @@ export class Business extends BaseEntity {
|
||||
@OneToMany(() => Announcement, (announcement) => announcement.business)
|
||||
announcements = new Collection<Announcement>(this);
|
||||
|
||||
@OneToMany(() => Criticism, (criticism) => criticism.business)
|
||||
criticisms = new Collection<Criticism>(this);
|
||||
|
||||
[EntityRepositoryType]?: BusinessRepository;
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsOptional, IsString, IsUrl } from "class-validator";
|
||||
|
||||
import { CriticismMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
export class CreateCriticismDto {
|
||||
@IsNotEmpty({ message: CriticismMessage.TITLE_IS_REQUIRED })
|
||||
@IsString({ message: CriticismMessage.TITLE_STRING })
|
||||
@ApiProperty({ type: String, description: "title of criticism", example: "انتقاد بابت مشکلات" })
|
||||
title: string;
|
||||
|
||||
@IsNotEmpty({ message: CriticismMessage.CONTENT_IS_REQUIRED })
|
||||
@IsString({ message: CriticismMessage.CONTENT_IS_STRING })
|
||||
@ApiProperty({ type: String, description: "description of criticism", example: "متن انتقاد" })
|
||||
content: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNotEmpty({ message: CriticismMessage.FILES_IS_REQUIRED })
|
||||
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: CriticismMessage.FILES_SHOULD_BE_URL, each: true })
|
||||
@ApiProperty({
|
||||
description: "The ulr of the criticism files",
|
||||
example: ["https://example.com/image1.png", "https://example.com/image2.png"],
|
||||
})
|
||||
files: string[];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post, Query, UseInterceptors } from "@nestjs/common";
|
||||
import { ApiOperation } 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 { BusinessDec } from "../../common/decorators/business.decorator";
|
||||
import { UserDec } from "../../common/decorators/user.decorator";
|
||||
import { ParamDto } from "../../common/DTO/param.dto";
|
||||
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
||||
import { Business } from "../businesses/entities/business.entity";
|
||||
|
||||
@Controller("criticisms")
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@AuthGuards()
|
||||
export class CriticismController {
|
||||
constructor(private readonly criticismService: CriticismsService) {}
|
||||
|
||||
@ApiOperation({ summary: "Create a new criticism" })
|
||||
@Post()
|
||||
create(@Body() createDto: CreateCriticismDto, @UserDec("id") userId: string, @BusinessDec() business: Business) {
|
||||
return this.criticismService.create(createDto, userId, business);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get all Criticism (admin)" })
|
||||
@Get()
|
||||
findAll(@Query() queryDto: SearchCriticismQueryDto, @BusinessDec("id") businessId: string) {
|
||||
return this.criticismService.findAll(queryDto, businessId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get one Criticism with id (admin)" })
|
||||
@Get(":id")
|
||||
fineOne(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
|
||||
return this.criticismService.findOneAndMarkAsRead(paramDto.id, businessId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Delete one Criticism with id (admin)" })
|
||||
@Delete(":id")
|
||||
delete(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
|
||||
return this.criticismService.delete(paramDto.id, businessId);
|
||||
}
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
import { MikroOrmModule } from "@mikro-orm/nestjs";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { CriticismController } from "./criticisms.controller";
|
||||
import { CriticismFile } from "./entities/criticism-file.entity";
|
||||
import { Criticism } from "./entities/criticism.entity";
|
||||
import { CriticismsService } from "./providers/criticisms.service";
|
||||
import { BusinessesModule } from "../businesses/businesses.module";
|
||||
import { UsersModule } from "../users/users.module";
|
||||
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([Criticism, CriticismFile]), UsersModule, BusinessesModule],
|
||||
providers: [CriticismsService],
|
||||
controllers: [CriticismController],
|
||||
exports: [CriticismsService],
|
||||
})
|
||||
export class CriticismModule {}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Entity, EntityRepositoryType, ManyToOne, Property } from "@mikro-orm/postgresql";
|
||||
|
||||
import { Criticism } from "./criticism.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { CriticismFileRepository } from "../repositories/criticisms-file.repository";
|
||||
|
||||
@Entity({ repository: () => CriticismFileRepository })
|
||||
export class CriticismFile extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 255 })
|
||||
fileUrl!: string;
|
||||
|
||||
@ManyToOne(() => Criticism, { nullable: false, deleteRule: "cascade" })
|
||||
criticism!: Criticism;
|
||||
|
||||
[EntityRepositoryType]?: CriticismFileRepository;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { Cascade, Collection, Entity, EntityRepositoryType, Index, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core";
|
||||
|
||||
import { CriticismFile } from "./criticism-file.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { Business } from "../../businesses/entities/business.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { CriticismRepository } from "../repositories/criticisms.repository";
|
||||
|
||||
@Entity({ repository: () => CriticismRepository })
|
||||
export class Criticism extends BaseEntity {
|
||||
@Property({ type: "int", autoincrement: true })
|
||||
numericId!: number & Opt;
|
||||
|
||||
@Index()
|
||||
@Property({ type: "varchar", length: 150, nullable: false })
|
||||
title!: string;
|
||||
|
||||
@Property({ type: "text", nullable: false })
|
||||
content!: string;
|
||||
|
||||
@ManyToOne(() => User, { nullable: false, deleteRule: "cascade" })
|
||||
user!: User;
|
||||
|
||||
@Property({ type: "boolean", default: false })
|
||||
isRead!: boolean & Opt;
|
||||
|
||||
@OneToMany(() => CriticismFile, (criticismFile) => criticismFile.criticism, { eager: true, cascade: [Cascade.PERSIST, Cascade.REMOVE] })
|
||||
files = new Collection<CriticismFile>(this);
|
||||
|
||||
@ManyToOne(() => Business, { nullable: false, deleteRule: "cascade" })
|
||||
business!: Business;
|
||||
|
||||
[EntityRepositoryType]?: CriticismRepository;
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import { EntityManager, QueryOrder } from "@mikro-orm/postgresql";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
|
||||
import { CommonMessage, CriticismMessage } from "../../../common/enums/message.enum";
|
||||
import { Business } from "../../businesses/entities/business.entity";
|
||||
import { UsersService } from "../../users/services/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 { Criticism } from "../entities/criticism.entity";
|
||||
import { CriticismRepository } from "../repositories/criticisms.repository";
|
||||
|
||||
@Injectable()
|
||||
export class CriticismsService {
|
||||
constructor(
|
||||
private readonly criticismRepository: CriticismRepository,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly em: EntityManager,
|
||||
) {}
|
||||
|
||||
async create(createDto: CreateCriticismDto, userId: string, business: Business) {
|
||||
const em = this.em.fork();
|
||||
try {
|
||||
em.begin();
|
||||
|
||||
const user = await this.usersService.findOneByIdWithEntityManager(userId, em);
|
||||
const files =
|
||||
createDto.files?.map((file) => {
|
||||
return { fileUrl: file };
|
||||
}) || [];
|
||||
const criticism = em.create(Criticism, { ...createDto, user, files, business });
|
||||
await em.persistAndFlush(criticism);
|
||||
|
||||
await em.commit();
|
||||
return {
|
||||
message: CommonMessage.CREATED,
|
||||
criticism,
|
||||
};
|
||||
} catch (error) {
|
||||
await em.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//##################################### ##
|
||||
|
||||
async findAll(queryDto: SearchCriticismQueryDto, businessId: string) {
|
||||
const { limit, skip } = PaginationUtils(queryDto);
|
||||
|
||||
const queryBuilder = this.criticismRepository.createQueryBuilder("criticism");
|
||||
|
||||
queryBuilder.where({ business: { id: businessId } });
|
||||
|
||||
if (queryDto.since) {
|
||||
queryBuilder.andWhere({ createdAt: { $gte: queryDto.since } });
|
||||
}
|
||||
|
||||
if (queryDto.q) {
|
||||
queryBuilder.andWhere({ title: { $ilike: `%${queryDto.q}%` } });
|
||||
queryBuilder.andWhere({ content: { $ilike: `%${queryDto.q}%` } });
|
||||
}
|
||||
|
||||
queryBuilder.orderBy({ createdAt: QueryOrder.DESC });
|
||||
|
||||
const [criticisms, count] = await queryBuilder.offset(skip).limit(limit).getResultAndCount();
|
||||
|
||||
return {
|
||||
criticisms,
|
||||
count,
|
||||
paginate: true,
|
||||
};
|
||||
}
|
||||
|
||||
//##################################### ##
|
||||
|
||||
async findOneAndMarkAsRead(id: string, businessId: string) {
|
||||
const criticism = await this.criticismRepository.findOne({ id, business: { id: businessId } });
|
||||
if (!criticism) throw new BadRequestException(CriticismMessage.NOT_FOUND);
|
||||
criticism.isRead = true;
|
||||
|
||||
await this.em.flush();
|
||||
return {
|
||||
criticism,
|
||||
};
|
||||
}
|
||||
|
||||
//##################################### ##
|
||||
|
||||
async delete(id: string, businessId: string) {
|
||||
const criticism = await this.criticismRepository.findOne({ id, business: { id: businessId } });
|
||||
if (!criticism) throw new BadRequestException(CriticismMessage.NOT_FOUND);
|
||||
|
||||
await this.em.removeAndFlush(criticism);
|
||||
|
||||
return {
|
||||
message: CommonMessage.DELETED,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository } from "@mikro-orm/postgresql";
|
||||
|
||||
import { CriticismFile } from "../entities/criticism-file.entity";
|
||||
|
||||
export class CriticismFileRepository extends EntityRepository<CriticismFile> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository } from "@mikro-orm/postgresql";
|
||||
|
||||
import { Criticism } from "../entities/criticism.entity";
|
||||
|
||||
export class CriticismRepository extends EntityRepository<Criticism> {}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DecimalType, Entity, EntityRepositoryType, ManyToOne, Property } from "@mikro-orm/postgresql";
|
||||
import { DecimalType, Entity, EntityRepositoryType, ManyToOne, Property } from "@mikro-orm/core";
|
||||
import Decimal from "decimal.js";
|
||||
|
||||
import { Invoice } from "./invoice.entity";
|
||||
|
||||
@@ -9,7 +9,6 @@ import { SearchTicketCategoryDto } from "./DTO/search-ticket-category.dto";
|
||||
import { SearchTicketQueryDto } from "./DTO/search-ticket-query.dto";
|
||||
import { UpdateTicketCategoryDto } from "./DTO/update-ticket-category.dto";
|
||||
import { TicketsService } from "./providers/tickets.service";
|
||||
import { AdminRoute } from "../../common/decorators/admin.decorator";
|
||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
import { BusinessDec } from "../../common/decorators/business.decorator";
|
||||
import { UserDec } from "../../common/decorators/user.decorator";
|
||||
@@ -51,14 +50,12 @@ export class TicketsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get ticket categories list (admin)" })
|
||||
@AdminRoute()
|
||||
@Get("categories-list")
|
||||
getCategoriesList(@Query() queryDto: SearchTicketCategoryDto, @BusinessDec("id") businessId: string) {
|
||||
return this.ticketsService.getCategoriesList(queryDto, businessId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "toggle status of categories (admin)" })
|
||||
@AdminRoute()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("category/toggle-status/:id")
|
||||
toggleCategoryStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
|
||||
@@ -80,7 +77,6 @@ export class TicketsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get all tickets (admin)" })
|
||||
@AdminRoute()
|
||||
@Get("admin")
|
||||
getAllTickets(@Query() queryDto: SearchTicketQueryDto, @UserDec("id") adminId: string, @BusinessDec("id") businessId: string) {
|
||||
return this.ticketsService.getTicketForAdmin(queryDto, adminId, businessId);
|
||||
@@ -122,14 +118,12 @@ export class TicketsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "open ticket by admin" })
|
||||
@AdminRoute()
|
||||
@Post(":id/open")
|
||||
openTicketByAdmin(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
|
||||
return this.ticketsService.openTicketByAdmin(paramDto.id, businessId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "referer ticket ==> admin route" })
|
||||
@AdminRoute()
|
||||
@Post(":id/refer")
|
||||
referTicket(
|
||||
@Param() paramDto: ParamDto,
|
||||
|
||||
Reference in New Issue
Block a user