diff --git a/src/modules/tickets/providers/tickets.service.ts b/src/modules/tickets/providers/tickets.service.ts index 786e1c6..f124f82 100755 --- a/src/modules/tickets/providers/tickets.service.ts +++ b/src/modules/tickets/providers/tickets.service.ts @@ -6,8 +6,11 @@ import { DataSource, MoreThanOrEqual, Not, QueryRunner } from "typeorm"; import { TICKET } from "../constant"; import { ReferralService } from "./referral.service"; +import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator"; import { ParamDto } from "../../../common/DTO/param.dto"; import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum"; +import { UserType } from "../../access-logs/enums/user-type.enum"; +import { AccessLogService } from "../../access-logs/providers/access-log.service"; import { DanakService } from "../../danak-services/entities/danak-service.entity"; import { NotificationQueue } from "../../notifications/queue/notification.queue"; import { SupportPlanFeatureKey } from "../../support-plans/enums/support-plan-feature-key.enum"; @@ -46,6 +49,7 @@ export class TicketsService implements OnModuleInit { private readonly referralService: ReferralService, private readonly dataSource: DataSource, private readonly supportPlansService: SupportPlansService, + private readonly accessLogService: AccessLogService, ) {} async onModuleInit() { @@ -67,7 +71,7 @@ export class TicketsService implements OnModuleInit { return { categories, count, paginate: true }; } //******************************** */ - async createTicketCategory(createDto: CreateTicketCategoryDto) { + async createTicketCategory(createDto: CreateTicketCategoryDto, userIpAndHeaders: IUserIpAndHeaders) { const { userGroup } = await this.usersService.getUserGroupById(createDto.userGroupId); const existCategory = await this.ticketsCategoryRepository.findCategoryByTitle(createDto.title); @@ -76,15 +80,29 @@ export class TicketsService implements OnModuleInit { ...createDto, group: userGroup, }); + await this.ticketsCategoryRepository.save(ticketCategory); + await this.accessLogService.logCreate("TicketCategory", UserType.ADMIN, { + user: { id: userIpAndHeaders.userId }, + endpoint: "/tickets/categories", + requestId: userIpAndHeaders.headers["x-request-id"], + ipAddress: userIpAndHeaders.ip, + userAgent: userIpAndHeaders.headers["user-agent"], + actionDescription: `Admin created ticket category: ${createDto.title}`, + metadata: { + categoryId: ticketCategory.id, + categoryTitle: ticketCategory.title, + }, + }); + return { message: TicketMessageEnum.CATEGORY_CREATED, }; } //******************************** */ - async updateCategory(paramDto: ParamDto, updateCategoryDto: UpdateTicketCategoryDto) { + async updateCategory(paramDto: ParamDto, updateCategoryDto: UpdateTicketCategoryDto, userIpAndHeaders: IUserIpAndHeaders) { const category = await this.ticketsCategoryRepository.findCategoryById(paramDto.id); if (!category) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); @@ -101,18 +119,47 @@ export class TicketsService implements OnModuleInit { ...updateCategoryDto, }); + await this.accessLogService.logUpdate("TicketCategory", paramDto.id, UserType.ADMIN, { + user: { id: userIpAndHeaders.userId }, + endpoint: "/tickets/categories", + requestId: userIpAndHeaders.headers["x-request-id"], + ipAddress: userIpAndHeaders.ip, + userAgent: userIpAndHeaders.headers["user-agent"], + actionDescription: `Admin updated ticket category: ${category.title}`, + metadata: { + categoryId: category.id, + categoryTitle: category.title, + }, + oldValues: JSON.stringify({ ...category }), + newValues: JSON.stringify({ ...updateCategoryDto }), + }); + return { message: CommonMessage.UPDATE_SUCCESS, }; } //******************************** */ - async toggleCategoryStatus(paramDto: ParamDto) { + async toggleCategoryStatus(paramDto: ParamDto, userIpAndHeaders: IUserIpAndHeaders) { const category = await this.ticketsCategoryRepository.findCategoryById(paramDto.id); if (!category) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); // category.isActive = !category.isActive; await this.ticketsCategoryRepository.save(category); // + await this.accessLogService.logUpdate("TicketCategory", paramDto.id, UserType.ADMIN, { + user: { id: userIpAndHeaders.userId }, + endpoint: "/tickets/categories", + requestId: userIpAndHeaders.headers["x-request-id"], + ipAddress: userIpAndHeaders.ip, + userAgent: userIpAndHeaders.headers["user-agent"], + actionDescription: `Admin toggled ticket category status: ${category.title}`, + metadata: { + categoryId: category.id, + categoryTitle: category.title, + isActive: category.isActive, + }, + }); + return { message: CommonMessage.UPDATE_SUCCESS, isActive: category.isActive, @@ -209,7 +256,7 @@ export class TicketsService implements OnModuleInit { } //******************************** */ - async createTicketByAdmin(createDto: CreateTicketByAdminDto, adminId: string) { + async createTicketByAdmin(createDto: CreateTicketByAdminDto, userIpAndHeaders: IUserIpAndHeaders) { const queryRunner = this.dataSource.createQueryRunner(); try { @@ -218,7 +265,7 @@ export class TicketsService implements OnModuleInit { const [user, admin, ticketCategory] = await Promise.all([ this.usersService.findOneByIdWithQueryRunner(createDto.userId, queryRunner), - this.usersService.findOneByIdWithQueryRunner(adminId, queryRunner), + this.usersService.findOneByIdWithQueryRunner(userIpAndHeaders.userId, queryRunner), queryRunner.manager.findOne(TicketCategory, { where: { id: createDto.categoryId }, relations: { group: { users: true } } }), ]); @@ -244,6 +291,19 @@ export class TicketsService implements OnModuleInit { userEmail: user.email, }); + await this.accessLogService.logCreate("Ticket", UserType.ADMIN, { + user: { id: userIpAndHeaders.userId }, + endpoint: "/tickets", + requestId: userIpAndHeaders.headers["x-request-id"], + ipAddress: userIpAndHeaders.ip, + userAgent: userIpAndHeaders.headers["user-agent"], + actionDescription: `Admin created ticket: ${ticket.subject}`, + metadata: { + ticketId: ticket.id, + ticketSubject: ticket.subject, + }, + }); + await queryRunner.commitTransaction(); return { @@ -347,13 +407,13 @@ export class TicketsService implements OnModuleInit { //******************************** */ - async closeTicketByUser(ticketId: string, userId: string, isAdmin: boolean) { + async closeTicketByUser(ticketId: string, isAdmin: boolean, userIpAndHeaders: IUserIpAndHeaders) { let ticket: null | Ticket = null; if (isAdmin) { ticket = await this.ticketsRepository.findTicketById(ticketId); } else { - ticket = await this.ticketsRepository.findTicketById(ticketId, userId); + ticket = await this.ticketsRepository.findTicketById(ticketId, userIpAndHeaders.userId); } if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); @@ -361,32 +421,58 @@ export class TicketsService implements OnModuleInit { ticket.status = TicketStatus.CLOSED; await this.ticketsRepository.save(ticket); + await this.accessLogService.logUpdate("Ticket", ticketId, UserType.ADMIN, { + user: { id: userIpAndHeaders.userId }, + endpoint: "/tickets", + requestId: userIpAndHeaders.headers["x-request-id"], + ipAddress: userIpAndHeaders.ip, + userAgent: userIpAndHeaders.headers["user-agent"], + actionDescription: `Admin closed ticket: ${ticket.subject}`, + metadata: { + ticketId: ticket.id, + ticketSubject: ticket.subject, + }, + }); + return { message: TicketMessageEnum.CLOSED, }; } //******************************** * / - async openTicketByAdmin(ticketId: string) { + async openTicketByAdmin(ticketId: string, userIpAndHeaders: IUserIpAndHeaders) { const ticket = await this.ticketsRepository.findTicketById(ticketId); if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); ticket.status = TicketStatus.PENDING; await this.ticketsRepository.save(ticket); + await this.accessLogService.logUpdate("Ticket", ticketId, UserType.ADMIN, { + user: { id: userIpAndHeaders.userId }, + endpoint: "/tickets", + requestId: userIpAndHeaders.headers["x-request-id"], + ipAddress: userIpAndHeaders.ip, + userAgent: userIpAndHeaders.headers["user-agent"], + actionDescription: `Admin opened ticket: ${ticket.subject}`, + metadata: { + ticketId: ticket.id, + ticketSubject: ticket.subject, + }, + }); + return { message: TicketMessageEnum.OPENED, }; } //******************************** */ - async referTicket(ticketId: string, userId: string, referDto: ReferTicketDto) { + async referTicket(ticketId: string, referDto: ReferTicketDto, userIpAndHeaders: IUserIpAndHeaders) { const queryRunner = this.dataSource.createQueryRunner(); try { await queryRunner.connect(); await queryRunner.startTransaction(); - const isSuperAdmin = await this.usersService.isSuperAdmin(userId); + const isSuperAdmin = await this.usersService.isSuperAdmin(userIpAndHeaders.userId); // if (!isSuperAdmin) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER); console.log("isSuperAdmin", isSuperAdmin); @@ -408,6 +494,19 @@ export class TicketsService implements OnModuleInit { }); await queryRunner.commitTransaction(); + + await this.accessLogService.logUpdate("Ticket", ticketId, UserType.ADMIN, { + user: { id: userIpAndHeaders.userId }, + endpoint: "/tickets", + requestId: userIpAndHeaders.headers["x-request-id"], + ipAddress: userIpAndHeaders.ip, + userAgent: userIpAndHeaders.headers["user-agent"], + actionDescription: `Admin referred ticket: ${ticket.subject}`, + metadata: { + ticketId: ticket.id, + ticketSubject: ticket.subject, + }, + }); return { message: TicketMessageEnum.REFERRED, ticket, diff --git a/src/modules/tickets/tickets.controller.ts b/src/modules/tickets/tickets.controller.ts index 586e760..5b6a50a 100755 --- a/src/modules/tickets/tickets.controller.ts +++ b/src/modules/tickets/tickets.controller.ts @@ -12,7 +12,7 @@ import { TicketsService } from "./providers/tickets.service"; import { AdminRoute } from "../../common/decorators/admin.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; -import { UserDec } from "../../common/decorators/user.decorator"; +import { IUserIpAndHeaders, UserDec, UserIpAndHeaders } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("tickets") @@ -26,20 +26,23 @@ export class TicketsController { @ApiOperation({ summary: "Create ticket category => admin route" }) @PermissionsDec(PermissionEnum.TICKETS) @Post("category") - createTicketCategory(@Body() createDto: CreateTicketCategoryDto) { - return this.ticketsService.createTicketCategory(createDto); + createTicketCategory(@Body() createDto: CreateTicketCategoryDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) { + return this.ticketsService.createTicketCategory(createDto, userIpAndHeaders); } @ApiOperation({ summary: "Update ticket category => admin route" }) @PermissionsDec(PermissionEnum.TICKETS) @Patch("category/:id") - updateCategory(@Param() paramDto: ParamDto, @Body() updateCategoryDto: UpdateTicketCategoryDto) { - return this.ticketsService.updateCategory(paramDto, updateCategoryDto); + updateCategory( + @Param() paramDto: ParamDto, + @Body() updateCategoryDto: UpdateTicketCategoryDto, + @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders, + ) { + return this.ticketsService.updateCategory(paramDto, updateCategoryDto, userIpAndHeaders); } @ApiOperation({ summary: "Get ticket categories " }) @Get("categories") - // @PermissionsDec(PermissionEnum.TICKETS) getTicketCategories() { return this.ticketsService.getTicketCategories(); } @@ -57,8 +60,8 @@ export class TicketsController { @HttpCode(HttpStatus.OK) @PermissionsDec(PermissionEnum.TICKETS) @Post("category/toggle-status/:id") - toggleCategoryStatus(@Param() paramDto: ParamDto) { - return this.ticketsService.toggleCategoryStatus(paramDto); + toggleCategoryStatus(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) { + return this.ticketsService.toggleCategoryStatus(paramDto, userIpAndHeaders); } //----------------- ticket and ticket messages ------------------- @@ -73,8 +76,8 @@ export class TicketsController { @AdminRoute() @PermissionsDec(PermissionEnum.TICKETS) @Post("admin") - createTicketByAdmin(@Body() createDto: CreateTicketByAdminDto, @UserDec("id") adminId: string) { - return this.ticketsService.createTicketByAdmin(createDto, adminId); + createTicketByAdmin(@Body() createDto: CreateTicketByAdminDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) { + return this.ticketsService.createTicketByAdmin(createDto, userIpAndHeaders); } @ApiOperation({ summary: "Get all tickets of user ==> user route" }) @@ -111,23 +114,27 @@ export class TicketsController { @ApiOperation({ summary: "close ticket by user or admin" }) @HttpCode(HttpStatus.OK) @Post(":id/close") - closedTicketByUser(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @UserDec("isAdmin") isAdmin: boolean) { - return this.ticketsService.closeTicketByUser(paramDto.id, userId, isAdmin); + closedTicketByUser( + @Param() paramDto: ParamDto, + @UserDec("isAdmin") isAdmin: boolean, + @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders, + ) { + return this.ticketsService.closeTicketByUser(paramDto.id, isAdmin, userIpAndHeaders); } @ApiOperation({ summary: "open ticket by admin" }) @AdminRoute() @PermissionsDec(PermissionEnum.TICKETS) @Post(":id/open") - openTicketByAdmin(@Param() paramDto: ParamDto) { - return this.ticketsService.openTicketByAdmin(paramDto.id); + openTicketByAdmin(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) { + return this.ticketsService.openTicketByAdmin(paramDto.id, userIpAndHeaders); } @ApiOperation({ summary: "referer ticket ==> admin route" }) @AdminRoute() @PermissionsDec(PermissionEnum.TICKETS) @Post(":id/refer") - referTicket(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @Body() referDto: ReferTicketDto) { - return this.ticketsService.referTicket(paramDto.id, userId, referDto); + referTicket(@Param() paramDto: ParamDto, @Body() referDto: ReferTicketDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) { + return this.ticketsService.referTicket(paramDto.id, referDto, userIpAndHeaders); } }