chore: complete ticket service but not tested
This commit is contained in:
@@ -146,14 +146,34 @@ export const enum TicketMessageEnum {
|
|||||||
TITLE_REQUIRED = "عنوان تیکت مورد نیاز است",
|
TITLE_REQUIRED = "عنوان تیکت مورد نیاز است",
|
||||||
TITLE_STRING = "عنوان تیکت باید یک رشته باشد",
|
TITLE_STRING = "عنوان تیکت باید یک رشته باشد",
|
||||||
TITLE_LENGTH = "عنوان تیکت باید بین ۳ تا ۱۵۰ کاراکتر باشد",
|
TITLE_LENGTH = "عنوان تیکت باید بین ۳ تا ۱۵۰ کاراکتر باشد",
|
||||||
|
SUBJECT_REQUIRED = "موضوع تیکت اجباری میباشد",
|
||||||
|
SUBJECT_STRING = "موضوع تیکت باید رشته باشد",
|
||||||
|
SUBJECT_LENGTH = "موضوع تیکت باید بین ۳ تا ۱۵۰ کاراکتر باشد",
|
||||||
|
PRIORITY_REQUIRED = "اولویت تیکت اجباری میباشد",
|
||||||
|
PRIORITY_INVALID = "اولویت تیکت باید یکی از مقادیر LOW، MEDIUM یا HIGH باشد",
|
||||||
|
CATEGORY_ID_REQUIRED = "شناسه دستهبندی نمیتواند خالی باشد",
|
||||||
|
CATEGORY_ID_SHOULD_BE_UUID = "شناسه دستهبندی باید یک UUID معتبر باشد",
|
||||||
|
DANAK_SERVICE_ID_REQUIRED = "شناسه سرویس داناک اجباری است",
|
||||||
|
DANAK_SERVICE_ID_SHOULD_BE_UUID = "شناسه سرویس داناک باید یک UUID معتبر باشد",
|
||||||
|
MESSAGE_REQUIRED = "متن پیام تیکت اجباری است",
|
||||||
|
MESSAGE_STRING = "متن پیام باید یک رشته باشد",
|
||||||
|
MESSAGE_LENGTH = "متن پیام باید بین ۵ تا ۵۰۰ کاراکتر باشد",
|
||||||
|
CATEGORY_EXIST = "دستهبندی با این نام قبلاً ثبت شده است",
|
||||||
|
CATEGORY_CREATED = "دستهبندی با موفقیت ایجاد شد",
|
||||||
|
CATEGORY_NOT_FOUND = "دستهبندی یافت نشد",
|
||||||
USER_GROUP_ID_NOT_EMPTY = "شناسه گروه کاربری نمیتواند خالی باشد",
|
USER_GROUP_ID_NOT_EMPTY = "شناسه گروه کاربری نمیتواند خالی باشد",
|
||||||
|
USER_GROUP_ID_SHOULD_BE_UUID = "شناسه گروه کاربری باید یک UUID معتبر باشد",
|
||||||
|
IS_ACTIVE_REQUIRED = "وضعیت تیکت مورد نیاز است",
|
||||||
|
IS_ACTIVE_SHOULD_BE_BOOLEAN = "وضعیت تیکت باید یک مقدار بولی (true/false) باشد",
|
||||||
|
CREATED = "تیکت با موفقیت ساخته شد",
|
||||||
DESCRIPTION_REQUIRED = "توضیحات تیکت مورد نیاز است",
|
DESCRIPTION_REQUIRED = "توضیحات تیکت مورد نیاز است",
|
||||||
DESCRIPTION_STRING = "توضیحات تیکت باید یک رشته باشد",
|
DESCRIPTION_STRING = "توضیحات تیکت باید یک رشته باشد",
|
||||||
DESCRIPTION_LENGTH = "توضیحات تیکت باید بین ۳ تا ۵۰۰ کاراکتر باشد",
|
DESCRIPTION_LENGTH = "توضیحات تیکت باید بین ۳ تا ۵۰۰ کاراکتر باشد",
|
||||||
USER_GROUP_ID_SHOULD_BE_UUID = "شناسه گروه کاربری باید یک یو یو آی دی باشد",
|
CONTENT_REQUIRED = "محتوای پیام تیکت مورد نیاز است",
|
||||||
IS_ACTIVE_REQUIRED = "وضعیت تیکت مورد نیاز است",
|
ATTACHMENT_REQUIRED = "آدرس فایل پیوست تیکت مورد نیاز است",
|
||||||
IS_ACTIVE_SHOULD_BE_BOOLEAN = "وضعیت تیکت باید یک بولین باشد",
|
ATTACHMENT_SHOULD_BE_URL = "آدرس فایل پیوست تیکت باید یوآرال باشد",
|
||||||
CATEGORY_EXIST = "دسته بندی با این نام قبلا ثبت شده است",
|
CONTENT_STRING = "محتوای پیام تیکت باید یک رشته باشد",
|
||||||
CATEGORY_CREATED = "دسته بندی با موفقیت ایجاد شد",
|
MESSAGE_CREATED = "پیام تیکت با موفقیت ایجاد شد",
|
||||||
CATEGORY_NOT_FOUND = "دسته بندی یافت نشد",
|
TICKET_NOT_FOUND = "تیکت مورد نظر یافت نشد",
|
||||||
|
CLOSED = "تیکت با موفقیت بسته شد",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
|
import { IsEnum, IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
|
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||||
|
import { CategoryMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { ServiceStatus } from "../enums/service-status.enum";
|
||||||
|
|
||||||
|
export class DanakServicesSearchQueryDto extends PaginationDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: CategoryMessage.CAT_ID_SHOULD_BE_UUID })
|
||||||
|
@ApiPropertyOptional({ description: "Category id", example: "8b1e8b1e-8b1e-8b1e-8b1e-8b1e8b1e8b1e" })
|
||||||
|
categoryId?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(ServiceStatus)
|
||||||
|
@ApiPropertyOptional({ description: "services status", enum: ServiceStatus, example: ServiceStatus.ACTIVE })
|
||||||
|
status?: ServiceStatus;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: CategoryMessage.SEARCH_QUERY_STRING })
|
||||||
|
@ApiPropertyOptional({ description: "Search query", example: "search query" })
|
||||||
|
q?: string;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
|||||||
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "./DTO/category-search-query.dto";
|
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "./DTO/category-search-query.dto";
|
||||||
import { CreateCategoryDto } from "./DTO/create-category.dto";
|
import { CreateCategoryDto } from "./DTO/create-category.dto";
|
||||||
import { CreateServiceDto } from "./DTO/create-service.dto";
|
import { CreateServiceDto } from "./DTO/create-service.dto";
|
||||||
|
import { DanakServicesSearchQueryDto } from "./DTO/danak-services-search-query.dto";
|
||||||
import { DanakServicesService } from "./providers/danak-services.service";
|
import { DanakServicesService } from "./providers/danak-services.service";
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
import { Pagination } from "../../common/decorators/pagination.decorator";
|
import { Pagination } from "../../common/decorators/pagination.decorator";
|
||||||
@@ -61,6 +62,14 @@ export class DanakServicesController {
|
|||||||
return this.danakServicesService.createService(createDto);
|
return this.danakServicesService.createService(createDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AuthGuards()
|
||||||
|
@ApiOperation({ summary: "get all danak services ==> admin route" })
|
||||||
|
@Roles(RoleEnum.ADMIN)
|
||||||
|
@Get("list")
|
||||||
|
getServices(@Query() queryDto: DanakServicesSearchQueryDto) {
|
||||||
|
return this.danakServicesService.getServicesList(queryDto);
|
||||||
|
}
|
||||||
|
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Roles(RoleEnum.USER)
|
@Roles(RoleEnum.USER)
|
||||||
@ApiOperation({ summary: "get all user purchased danak services" })
|
@ApiOperation({ summary: "get all user purchased danak services" })
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export class DanakService extends BaseEntity {
|
|||||||
})
|
})
|
||||||
category: DanakServiceCategory;
|
category: DanakServiceCategory;
|
||||||
|
|
||||||
@OneToMany(() => DanakServiceImage, (danakServiceImage) => danakServiceImage.danakService, { eager: true, cascade: true })
|
@OneToMany(() => DanakServiceImage, (danakServiceImage) => danakServiceImage.danakService, { cascade: true })
|
||||||
images: DanakServiceImage[];
|
images: DanakServiceImage[];
|
||||||
|
|
||||||
@OneToMany(() => Announcement, (announcement) => announcement.service)
|
@OneToMany(() => Announcement, (announcement) => announcement.service)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
import { FindOptionsWhere, IsNull } from "typeorm";
|
import { Brackets, FindOptionsWhere, IsNull } from "typeorm";
|
||||||
|
|
||||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||||
import { CategoryMessage, CommonMessage, ServiceMessage } from "../../../common/enums/message.enum";
|
import { CategoryMessage, CommonMessage, ServiceMessage } from "../../../common/enums/message.enum";
|
||||||
@@ -7,6 +7,7 @@ import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
|||||||
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto";
|
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto";
|
||||||
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
||||||
import { CreateServiceDto } from "../DTO/create-service.dto";
|
import { CreateServiceDto } from "../DTO/create-service.dto";
|
||||||
|
import { DanakServicesSearchQueryDto } from "../DTO/danak-services-search-query.dto";
|
||||||
import { DanakServiceCategory } from "../entities/danak-service-category.entity";
|
import { DanakServiceCategory } from "../entities/danak-service-category.entity";
|
||||||
import { DanakServicesCategoryRepository } from "../repositories/danak-services-category.repository";
|
import { DanakServicesCategoryRepository } from "../repositories/danak-services-category.repository";
|
||||||
import { DanakServicesRepository } from "../repositories/danak-services.repository";
|
import { DanakServicesRepository } from "../repositories/danak-services.repository";
|
||||||
@@ -120,6 +121,43 @@ export class DanakServicesService {
|
|||||||
}
|
}
|
||||||
/******************************************** */
|
/******************************************** */
|
||||||
|
|
||||||
|
async getServicesList(queryDto: DanakServicesSearchQueryDto) {
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const queryBuilder = this.danakServicesRepository
|
||||||
|
.createQueryBuilder("service")
|
||||||
|
.leftJoinAndSelect("service.category", "category")
|
||||||
|
.orderBy("service.createdAt", "DESC")
|
||||||
|
.skip(skip)
|
||||||
|
.take(limit);
|
||||||
|
|
||||||
|
if (queryDto.status) {
|
||||||
|
queryBuilder.andWhere("service.status = :status", { status: queryDto.status });
|
||||||
|
}
|
||||||
|
if (queryDto.categoryId) {
|
||||||
|
queryBuilder.andWhere("service.categoryId = :categoryId", { categoryId: queryDto.categoryId });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryDto.q) {
|
||||||
|
queryBuilder.andWhere(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.where("service.name LIKE :q", { q: `%${queryDto.q}%` })
|
||||||
|
.orWhere("service.description LIKE :q", { q: `%${queryDto.q}%` })
|
||||||
|
.orWhere("service.softwareLanguage LIKE :q", { q: `%${queryDto.q}%` })
|
||||||
|
.orWhere("service.author LIKE :q", { q: `%${queryDto.q}%` });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const [services, count] = await queryBuilder.getManyAndCount();
|
||||||
|
|
||||||
|
return {
|
||||||
|
services,
|
||||||
|
count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************** */
|
||||||
|
|
||||||
async getServiceByName(name: string) {
|
async getServiceByName(name: string) {
|
||||||
const danakService = await this.danakServicesRepository.findOneByName(name);
|
const danakService = await this.danakServicesRepository.findOneByName(name);
|
||||||
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST);
|
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsOptional, IsString, IsUrl } from "class-validator";
|
||||||
|
|
||||||
|
import { TicketMessageEnum } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class CreateTicketMessageDto {
|
||||||
|
@IsNotEmpty({ message: TicketMessageEnum.CONTENT_REQUIRED })
|
||||||
|
@IsString({ message: TicketMessageEnum.CONTENT_STRING })
|
||||||
|
@ApiProperty({ description: "the content of message", example: "test message" })
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty({ message: TicketMessageEnum.ATTACHMENT_REQUIRED })
|
||||||
|
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: TicketMessageEnum.ATTACHMENT_SHOULD_BE_URL })
|
||||||
|
@ApiProperty({ description: "attachment url", example: "https://example.com/test.png" })
|
||||||
|
attachmentUrl: string;
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
|
||||||
import { IsEnum, IsNotEmpty, IsString, Length } from "class-validator";
|
|
||||||
|
|
||||||
import { TicketPriority } from "../enums/ticket-priority.enum";
|
|
||||||
|
|
||||||
export class CreateTicketDto {
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
@Length(3, 150)
|
|
||||||
@ApiProperty()
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
@Length(3, 150)
|
|
||||||
@ApiProperty()
|
|
||||||
subject: string;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsEnum(TicketPriority)
|
|
||||||
@ApiProperty({ enum: TicketPriority, example: TicketPriority.LOW, description: "LOW, MEDIUM, HIGH" })
|
|
||||||
priority: TicketPriority;
|
|
||||||
|
|
||||||
danakServiceId: string;
|
|
||||||
|
|
||||||
categoryId: string;
|
|
||||||
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID, Length } from "class-validator";
|
||||||
|
|
||||||
|
import { TicketMessageEnum } from "../../../common/enums/message.enum";
|
||||||
|
import { TicketPriority } from "../enums/ticket-priority.enum";
|
||||||
|
|
||||||
|
export class CreateTicketDto {
|
||||||
|
@IsNotEmpty({ message: TicketMessageEnum.TITLE_REQUIRED })
|
||||||
|
@IsString({ message: TicketMessageEnum.TITLE_STRING })
|
||||||
|
@Length(3, 150, { message: TicketMessageEnum.TITLE_LENGTH })
|
||||||
|
@ApiProperty({ description: "The ticket title", example: "Test title" })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: TicketMessageEnum.SUBJECT_REQUIRED })
|
||||||
|
@IsString({ message: TicketMessageEnum.SUBJECT_STRING })
|
||||||
|
@Length(3, 150, { message: TicketMessageEnum.SUBJECT_LENGTH })
|
||||||
|
@ApiProperty({ description: "Subject of the ticket", example: "Subject" })
|
||||||
|
subject: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: TicketMessageEnum.PRIORITY_REQUIRED })
|
||||||
|
@IsEnum(TicketPriority, { message: TicketMessageEnum.PRIORITY_INVALID })
|
||||||
|
@ApiProperty({ enum: TicketPriority, example: TicketPriority.LOW, description: "LOW, MEDIUM, HIGH" })
|
||||||
|
priority: TicketPriority;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty({ message: TicketMessageEnum.DANAK_SERVICE_ID_REQUIRED })
|
||||||
|
@IsUUID("4", { message: TicketMessageEnum.DANAK_SERVICE_ID_SHOULD_BE_UUID })
|
||||||
|
@ApiProperty({ description: "Service ID related to the ticket", example: "550e8400-e29b-41d4-a716-446655440000" })
|
||||||
|
danakServiceId?: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: TicketMessageEnum.CATEGORY_ID_REQUIRED })
|
||||||
|
@IsUUID("4", { message: TicketMessageEnum.CATEGORY_ID_SHOULD_BE_UUID })
|
||||||
|
@ApiProperty({ description: "Category ID of the ticket", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||||
|
categoryId: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: TicketMessageEnum.MESSAGE_REQUIRED })
|
||||||
|
@IsString({ message: TicketMessageEnum.MESSAGE_STRING })
|
||||||
|
@Length(5, 500, { message: TicketMessageEnum.MESSAGE_LENGTH })
|
||||||
|
@ApiProperty({ description: "The message content of the ticket", example: "This is a detailed description of the issue." })
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
|
import { IsEnum, IsOptional } from "class-validator";
|
||||||
|
|
||||||
|
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||||
|
import { TicketStatus } from "../enums/ticket-status.enum";
|
||||||
|
|
||||||
|
export class SearchTicketQueryDto extends PaginationDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(TicketStatus)
|
||||||
|
@ApiPropertyOptional({ enum: TicketStatus, description: "ticket status", example: TicketStatus.PENDING })
|
||||||
|
status: TicketStatus;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from "typeorm";
|
import { Column, Entity, ManyToOne, OneToMany } from "typeorm";
|
||||||
|
|
||||||
import { TicketCategory } from "./ticket-category.entity";
|
import { TicketCategory } from "./ticket-category.entity";
|
||||||
import { TicketMessage } from "./ticket-message.entity";
|
import { TicketMessage } from "./ticket-message.entity";
|
||||||
@@ -25,21 +25,19 @@ export class Ticket extends BaseEntity {
|
|||||||
@Column({ type: "enum", enum: TicketPriority, nullable: false })
|
@Column({ type: "enum", enum: TicketPriority, nullable: false })
|
||||||
priority: TicketPriority;
|
priority: TicketPriority;
|
||||||
|
|
||||||
@ManyToOne(() => DanakService, (danakService) => danakService.tickets, { onDelete: "RESTRICT", nullable: false })
|
//TODO:set this to not nullable
|
||||||
|
@ManyToOne(() => DanakService, (danakService) => danakService.tickets, { onDelete: "RESTRICT", nullable: true })
|
||||||
danakService: DanakService;
|
danakService: DanakService;
|
||||||
|
|
||||||
@JoinColumn()
|
|
||||||
@ManyToOne(() => User, (user) => user.tickets, { nullable: true })
|
@ManyToOne(() => User, (user) => user.tickets, { nullable: true })
|
||||||
assignedTo: User;
|
assignedTo: User;
|
||||||
|
|
||||||
@JoinColumn()
|
|
||||||
@ManyToOne(() => User, (user) => user.tickets, { onDelete: "CASCADE", nullable: false })
|
@ManyToOne(() => User, (user) => user.tickets, { onDelete: "CASCADE", nullable: false })
|
||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
@JoinColumn()
|
|
||||||
@ManyToOne(() => TicketCategory, (category) => category.tickets, { onDelete: "RESTRICT", nullable: false })
|
@ManyToOne(() => TicketCategory, (category) => category.tickets, { onDelete: "RESTRICT", nullable: false })
|
||||||
category: TicketCategory;
|
category: TicketCategory;
|
||||||
|
|
||||||
@OneToMany(() => TicketMessage, (message) => message.ticket)
|
@OneToMany(() => TicketMessage, (message) => message.ticket, { cascade: true })
|
||||||
messages: TicketMessage[];
|
messages: TicketMessage[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,23 @@ import { CommonMessage, TicketMessageEnum } from "../../../common/enums/message.
|
|||||||
import { UsersService } from "../../users/providers/users.service";
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
import { CreateTicketCategoryDto } from "../DTO/create-ticket-category.dto";
|
import { CreateTicketCategoryDto } from "../DTO/create-ticket-category.dto";
|
||||||
|
import { CreateTicketMessageDto } from "../DTO/create-ticket-message.dto";
|
||||||
|
import { CreateTicketDto } from "../DTO/create-ticket.dto";
|
||||||
import { SearchTicketCategoryDto } from "../DTO/search-ticket-category.dto";
|
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 { UpdateTicketCategoryDto } from "../DTO/update-ticket-category.dto";
|
||||||
|
import { TicketMessage } from "../entities/ticket-message.entity";
|
||||||
|
import { TicketStatus } from "../enums/ticket-status.enum";
|
||||||
import { TicketCategoryRepository } from "../repositories/tickets-category.repository";
|
import { TicketCategoryRepository } from "../repositories/tickets-category.repository";
|
||||||
|
import { TicketMessagesRepository } from "../repositories/tickets-message.repository";
|
||||||
|
import { TicketsRepository } from "../repositories/tickets.repository";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TicketsService {
|
export class TicketsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly ticketsCategoryRepository: TicketCategoryRepository,
|
private readonly ticketsCategoryRepository: TicketCategoryRepository,
|
||||||
|
private readonly ticketsRepository: TicketsRepository,
|
||||||
|
private readonly ticketMessagesRepository: TicketMessagesRepository,
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
) {}
|
) {}
|
||||||
//******************************** */
|
//******************************** */
|
||||||
@@ -93,5 +102,117 @@ export class TicketsService {
|
|||||||
|
|
||||||
//******************************** */
|
//******************************** */
|
||||||
|
|
||||||
async createTicket() {}
|
async createTicket(createDto: CreateTicketDto, userId: string) {
|
||||||
|
const { user } = await this.usersService.findOneById(userId);
|
||||||
|
const ticketCategory = await this.ticketsCategoryRepository.findCategoryById(createDto.categoryId);
|
||||||
|
if (!ticketCategory) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND);
|
||||||
|
|
||||||
|
//
|
||||||
|
const ticketMessage: Partial<TicketMessage> = { content: createDto.message, author: user };
|
||||||
|
const ticket = this.ticketsRepository.create({ ...createDto, user, category: ticketCategory, messages: [ticketMessage] });
|
||||||
|
|
||||||
|
await this.ticketsRepository.save(ticket);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: TicketMessageEnum.CREATED,
|
||||||
|
ticket,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//******************************** */
|
||||||
|
|
||||||
|
async getTickets(queryDto: SearchTicketQueryDto, userId: string) {
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const findOptions = {
|
||||||
|
where: {
|
||||||
|
user: { id: userId },
|
||||||
|
...(queryDto.status && { status: queryDto.status }),
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
category: true,
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
createdAt: "DESC" as const,
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [tickets, count] = await this.ticketsRepository.findAndCount(findOptions);
|
||||||
|
|
||||||
|
return {
|
||||||
|
tickets,
|
||||||
|
count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************************** */
|
||||||
|
|
||||||
|
async createTicketMessage(ticketId: string, createDto: CreateTicketMessageDto, userId: string) {
|
||||||
|
const ticket = await this.ticketsRepository.findTicketById(ticketId, userId);
|
||||||
|
if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
||||||
|
//
|
||||||
|
const { user } = await this.usersService.findOneById(userId);
|
||||||
|
|
||||||
|
const ticketMessage = this.ticketMessagesRepository.create({ ...createDto, author: user, ticket });
|
||||||
|
|
||||||
|
await this.ticketMessagesRepository.save(ticketMessage);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: TicketMessageEnum.MESSAGE_CREATED,
|
||||||
|
ticketMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************************** */
|
||||||
|
|
||||||
|
async getTicketMessages(ticketId: string, userId: string) {
|
||||||
|
const ticket = await this.ticketsRepository.findTicketById(ticketId, userId);
|
||||||
|
if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
||||||
|
|
||||||
|
const messages = await this.ticketMessagesRepository.findMessagesByTicketId(ticketId);
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************************** */
|
||||||
|
|
||||||
|
async closeTicketByUser(ticketId: string, userId: string) {
|
||||||
|
const ticket = await this.ticketsRepository.findTicketById(ticketId, userId);
|
||||||
|
if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
||||||
|
|
||||||
|
ticket.status = TicketStatus.CLOSED;
|
||||||
|
await this.ticketsRepository.save(ticket);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: TicketMessageEnum.CLOSED,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************************** */
|
||||||
|
}
|
||||||
|
|
||||||
|
// async getTickets(queryDto: SearchTicketQueryDto, userId: string) {
|
||||||
|
// const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
// //TODO:add danak-service to query
|
||||||
|
// const queryBuilder = this.ticketsRepository
|
||||||
|
// .createQueryBuilder("ticket")
|
||||||
|
// .leftJoinAndSelect("ticket.category", "category")
|
||||||
|
// .leftJoinAndSelect("ticket.user", "user")
|
||||||
|
// .where("user.id = :userId", { userId })
|
||||||
|
// .orderBy("ticket.createdAt", "DESC");
|
||||||
|
|
||||||
|
// if (queryDto.status) {
|
||||||
|
// queryBuilder.andWhere("ticket.status = :status", { status: queryDto.status });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const [tickets, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// tickets,
|
||||||
|
// count,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|||||||
@@ -9,4 +9,26 @@ export class TicketMessagesRepository extends Repository<TicketMessage> {
|
|||||||
constructor(@InjectRepository(TicketMessage) ticketMessagesRepository: Repository<TicketMessage>) {
|
constructor(@InjectRepository(TicketMessage) ticketMessagesRepository: Repository<TicketMessage>) {
|
||||||
super(ticketMessagesRepository.target, ticketMessagesRepository.manager, ticketMessagesRepository.queryRunner);
|
super(ticketMessagesRepository.target, ticketMessagesRepository.manager, ticketMessagesRepository.queryRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findMessagesByTicketId(ticketId: string): Promise<TicketMessage[]> {
|
||||||
|
return this.find({
|
||||||
|
relations: {
|
||||||
|
author: true,
|
||||||
|
ticket: true,
|
||||||
|
},
|
||||||
|
// select: {
|
||||||
|
// id: true,
|
||||||
|
// content: true,
|
||||||
|
// attachmentUrl: true,
|
||||||
|
// createdAt: true,
|
||||||
|
// author: {
|
||||||
|
// id: true,
|
||||||
|
// firstName: true,
|
||||||
|
// lastName: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
where: { ticket: { id: ticketId } },
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,8 @@ export class TicketsRepository extends Repository<Ticket> {
|
|||||||
constructor(@InjectRepository(Ticket) ticketsRepository: Repository<Ticket>) {
|
constructor(@InjectRepository(Ticket) ticketsRepository: Repository<Ticket>) {
|
||||||
super(ticketsRepository.target, ticketsRepository.manager, ticketsRepository.queryRunner);
|
super(ticketsRepository.target, ticketsRepository.manager, ticketsRepository.queryRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findTicketById(id: string, userId: string): Promise<Ticket | null> {
|
||||||
|
return this.findOneBy({ id, user: { id: userId } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,18 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Param, Patch, Post, Query
|
|||||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { CreateTicketCategoryDto } from "./DTO/create-ticket-category.dto";
|
import { CreateTicketCategoryDto } from "./DTO/create-ticket-category.dto";
|
||||||
|
import { CreateTicketMessageDto } from "./DTO/create-ticket-message.dto";
|
||||||
|
import { CreateTicketDto } from "./DTO/create-ticket.dto";
|
||||||
import { SearchTicketCategoryDto } from "./DTO/search-ticket-category.dto";
|
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 { UpdateTicketCategoryDto } from "./DTO/update-ticket-category.dto";
|
||||||
import { TicketsService } from "./providers/tickets.service";
|
import { TicketsService } from "./providers/tickets.service";
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
import { Pagination } from "../../common/decorators/pagination.decorator";
|
import { Pagination } from "../../common/decorators/pagination.decorator";
|
||||||
import { Roles } from "../../common/decorators/roles.decorator";
|
import { Roles } from "../../common/decorators/roles.decorator";
|
||||||
|
import { UserDec } from "../../common/decorators/user.decorator";
|
||||||
import { ParamDto } from "../../common/DTO/param.dto";
|
import { ParamDto } from "../../common/DTO/param.dto";
|
||||||
|
import { User } from "../users/entities/user.entity";
|
||||||
import { RoleEnum } from "../users/enums/role.enum";
|
import { RoleEnum } from "../users/enums/role.enum";
|
||||||
|
|
||||||
@Controller("tickets")
|
@Controller("tickets")
|
||||||
@@ -16,6 +21,8 @@ import { RoleEnum } from "../users/enums/role.enum";
|
|||||||
export class TicketsController {
|
export class TicketsController {
|
||||||
constructor(private readonly ticketsService: TicketsService) {}
|
constructor(private readonly ticketsService: TicketsService) {}
|
||||||
|
|
||||||
|
//----------------- ticket categories -------------------
|
||||||
|
|
||||||
@ApiOperation({ summary: "Create ticket category => admin route" })
|
@ApiOperation({ summary: "Create ticket category => admin route" })
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Roles(RoleEnum.ADMIN)
|
@Roles(RoleEnum.ADMIN)
|
||||||
@@ -57,4 +64,47 @@ export class TicketsController {
|
|||||||
toggleCategoryStatus(@Param() paramDto: ParamDto) {
|
toggleCategoryStatus(@Param() paramDto: ParamDto) {
|
||||||
return this.ticketsService.toggleCategoryStatus(paramDto);
|
return this.ticketsService.toggleCategoryStatus(paramDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------- ticket and ticket messages -------------------
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "create ticket ==> user route" })
|
||||||
|
@AuthGuards()
|
||||||
|
@Roles(RoleEnum.USER)
|
||||||
|
@Post()
|
||||||
|
createTicket(@Body() createDto: CreateTicketDto, @UserDec() user: User) {
|
||||||
|
return this.ticketsService.createTicket(createDto, user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get all tickets of user ==> user route" })
|
||||||
|
@AuthGuards()
|
||||||
|
@Roles(RoleEnum.USER)
|
||||||
|
@Pagination()
|
||||||
|
@Get()
|
||||||
|
getTickets(@Query() queryDto: SearchTicketQueryDto, @UserDec() user: User) {
|
||||||
|
return this.ticketsService.getTickets(queryDto, user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "create ticket messages" })
|
||||||
|
@AuthGuards()
|
||||||
|
@Roles(RoleEnum.USER, RoleEnum.ADMIN)
|
||||||
|
@Post(":id/messages")
|
||||||
|
createTicketMessage(@Param() paramDto: ParamDto, @Body() createDto: CreateTicketMessageDto, @UserDec() user: User) {
|
||||||
|
return this.ticketsService.createTicketMessage(paramDto.id, createDto, user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get all ticket messages of user ==> user route" })
|
||||||
|
@AuthGuards()
|
||||||
|
@Roles(RoleEnum.USER)
|
||||||
|
@Get(":id/messages")
|
||||||
|
getTicketMessages(@Param() paramDto: ParamDto, @UserDec() user: User) {
|
||||||
|
return this.ticketsService.getTicketMessages(paramDto.id, user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "close ticket by user ==> user route" })
|
||||||
|
@AuthGuards()
|
||||||
|
@Roles(RoleEnum.USER)
|
||||||
|
@Get(":id/close")
|
||||||
|
closedTicketByUser(@Param() paramDto: ParamDto, @UserDec() user: User) {
|
||||||
|
return this.ticketsService.closeTicketByUser(paramDto.id, user.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user