chore: add the access log to the ticket

This commit is contained in:
mahyargdz
2025-09-03 13:29:34 +03:30
parent 5e0bd3dce7
commit 76fd4f3435
2 changed files with 132 additions and 26 deletions
+109 -10
View File
@@ -6,8 +6,11 @@ import { DataSource, MoreThanOrEqual, Not, QueryRunner } from "typeorm";
import { TICKET } from "../constant"; import { TICKET } from "../constant";
import { ReferralService } from "./referral.service"; import { ReferralService } from "./referral.service";
import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator";
import { ParamDto } from "../../../common/DTO/param.dto"; import { ParamDto } from "../../../common/DTO/param.dto";
import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum"; 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 { DanakService } from "../../danak-services/entities/danak-service.entity";
import { NotificationQueue } from "../../notifications/queue/notification.queue"; import { NotificationQueue } from "../../notifications/queue/notification.queue";
import { SupportPlanFeatureKey } from "../../support-plans/enums/support-plan-feature-key.enum"; 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 referralService: ReferralService,
private readonly dataSource: DataSource, private readonly dataSource: DataSource,
private readonly supportPlansService: SupportPlansService, private readonly supportPlansService: SupportPlansService,
private readonly accessLogService: AccessLogService,
) {} ) {}
async onModuleInit() { async onModuleInit() {
@@ -67,7 +71,7 @@ export class TicketsService implements OnModuleInit {
return { categories, count, paginate: true }; return { categories, count, paginate: true };
} }
//******************************** */ //******************************** */
async createTicketCategory(createDto: CreateTicketCategoryDto) { async createTicketCategory(createDto: CreateTicketCategoryDto, userIpAndHeaders: IUserIpAndHeaders) {
const { userGroup } = await this.usersService.getUserGroupById(createDto.userGroupId); const { userGroup } = await this.usersService.getUserGroupById(createDto.userGroupId);
const existCategory = await this.ticketsCategoryRepository.findCategoryByTitle(createDto.title); const existCategory = await this.ticketsCategoryRepository.findCategoryByTitle(createDto.title);
@@ -76,15 +80,29 @@ export class TicketsService implements OnModuleInit {
...createDto, ...createDto,
group: userGroup, group: userGroup,
}); });
await this.ticketsCategoryRepository.save(ticketCategory); 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 { return {
message: TicketMessageEnum.CATEGORY_CREATED, 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); const category = await this.ticketsCategoryRepository.findCategoryById(paramDto.id);
if (!category) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); if (!category) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND);
@@ -101,18 +119,47 @@ export class TicketsService implements OnModuleInit {
...updateCategoryDto, ...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 { return {
message: CommonMessage.UPDATE_SUCCESS, message: CommonMessage.UPDATE_SUCCESS,
}; };
} }
//******************************** */ //******************************** */
async toggleCategoryStatus(paramDto: ParamDto) { async toggleCategoryStatus(paramDto: ParamDto, userIpAndHeaders: IUserIpAndHeaders) {
const category = await this.ticketsCategoryRepository.findCategoryById(paramDto.id); const category = await this.ticketsCategoryRepository.findCategoryById(paramDto.id);
if (!category) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND); if (!category) throw new BadRequestException(TicketMessageEnum.CATEGORY_NOT_FOUND);
// //
category.isActive = !category.isActive; category.isActive = !category.isActive;
await this.ticketsCategoryRepository.save(category); 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 { return {
message: CommonMessage.UPDATE_SUCCESS, message: CommonMessage.UPDATE_SUCCESS,
isActive: category.isActive, 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(); const queryRunner = this.dataSource.createQueryRunner();
try { try {
@@ -218,7 +265,7 @@ export class TicketsService implements OnModuleInit {
const [user, admin, ticketCategory] = await Promise.all([ const [user, admin, ticketCategory] = await Promise.all([
this.usersService.findOneByIdWithQueryRunner(createDto.userId, queryRunner), 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 } } }), queryRunner.manager.findOne(TicketCategory, { where: { id: createDto.categoryId }, relations: { group: { users: true } } }),
]); ]);
@@ -244,6 +291,19 @@ export class TicketsService implements OnModuleInit {
userEmail: user.email, 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(); await queryRunner.commitTransaction();
return { 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; let ticket: null | Ticket = null;
if (isAdmin) { if (isAdmin) {
ticket = await this.ticketsRepository.findTicketById(ticketId); ticket = await this.ticketsRepository.findTicketById(ticketId);
} else { } 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); if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
@@ -361,32 +421,58 @@ export class TicketsService implements OnModuleInit {
ticket.status = TicketStatus.CLOSED; ticket.status = TicketStatus.CLOSED;
await this.ticketsRepository.save(ticket); 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 { return {
message: TicketMessageEnum.CLOSED, message: TicketMessageEnum.CLOSED,
}; };
} }
//******************************** * / //******************************** * /
async openTicketByAdmin(ticketId: string) { async openTicketByAdmin(ticketId: string, userIpAndHeaders: IUserIpAndHeaders) {
const ticket = await this.ticketsRepository.findTicketById(ticketId); const ticket = await this.ticketsRepository.findTicketById(ticketId);
if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND); if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
ticket.status = TicketStatus.PENDING; ticket.status = TicketStatus.PENDING;
await this.ticketsRepository.save(ticket); 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 { return {
message: TicketMessageEnum.OPENED, message: TicketMessageEnum.OPENED,
}; };
} }
//******************************** */ //******************************** */
async referTicket(ticketId: string, userId: string, referDto: ReferTicketDto) { async referTicket(ticketId: string, referDto: ReferTicketDto, userIpAndHeaders: IUserIpAndHeaders) {
const queryRunner = this.dataSource.createQueryRunner(); const queryRunner = this.dataSource.createQueryRunner();
try { try {
await queryRunner.connect(); await queryRunner.connect();
await queryRunner.startTransaction(); 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); // if (!isSuperAdmin) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER);
console.log("isSuperAdmin", isSuperAdmin); console.log("isSuperAdmin", isSuperAdmin);
@@ -408,6 +494,19 @@ export class TicketsService implements OnModuleInit {
}); });
await queryRunner.commitTransaction(); 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 { return {
message: TicketMessageEnum.REFERRED, message: TicketMessageEnum.REFERRED,
ticket, ticket,
+23 -16
View File
@@ -12,7 +12,7 @@ import { TicketsService } from "./providers/tickets.service";
import { AdminRoute } from "../../common/decorators/admin.decorator"; import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { PermissionsDec } from "../../common/decorators/permission.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 { ParamDto } from "../../common/DTO/param.dto";
import { PermissionEnum } from "../users/enums/permission.enum"; import { PermissionEnum } from "../users/enums/permission.enum";
@Controller("tickets") @Controller("tickets")
@@ -26,20 +26,23 @@ export class TicketsController {
@ApiOperation({ summary: "Create ticket category => admin route" }) @ApiOperation({ summary: "Create ticket category => admin route" })
@PermissionsDec(PermissionEnum.TICKETS) @PermissionsDec(PermissionEnum.TICKETS)
@Post("category") @Post("category")
createTicketCategory(@Body() createDto: CreateTicketCategoryDto) { createTicketCategory(@Body() createDto: CreateTicketCategoryDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.ticketsService.createTicketCategory(createDto); return this.ticketsService.createTicketCategory(createDto, userIpAndHeaders);
} }
@ApiOperation({ summary: "Update ticket category => admin route" }) @ApiOperation({ summary: "Update ticket category => admin route" })
@PermissionsDec(PermissionEnum.TICKETS) @PermissionsDec(PermissionEnum.TICKETS)
@Patch("category/:id") @Patch("category/:id")
updateCategory(@Param() paramDto: ParamDto, @Body() updateCategoryDto: UpdateTicketCategoryDto) { updateCategory(
return this.ticketsService.updateCategory(paramDto, updateCategoryDto); @Param() paramDto: ParamDto,
@Body() updateCategoryDto: UpdateTicketCategoryDto,
@UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders,
) {
return this.ticketsService.updateCategory(paramDto, updateCategoryDto, userIpAndHeaders);
} }
@ApiOperation({ summary: "Get ticket categories " }) @ApiOperation({ summary: "Get ticket categories " })
@Get("categories") @Get("categories")
// @PermissionsDec(PermissionEnum.TICKETS)
getTicketCategories() { getTicketCategories() {
return this.ticketsService.getTicketCategories(); return this.ticketsService.getTicketCategories();
} }
@@ -57,8 +60,8 @@ export class TicketsController {
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@PermissionsDec(PermissionEnum.TICKETS) @PermissionsDec(PermissionEnum.TICKETS)
@Post("category/toggle-status/:id") @Post("category/toggle-status/:id")
toggleCategoryStatus(@Param() paramDto: ParamDto) { toggleCategoryStatus(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.ticketsService.toggleCategoryStatus(paramDto); return this.ticketsService.toggleCategoryStatus(paramDto, userIpAndHeaders);
} }
//----------------- ticket and ticket messages ------------------- //----------------- ticket and ticket messages -------------------
@@ -73,8 +76,8 @@ export class TicketsController {
@AdminRoute() @AdminRoute()
@PermissionsDec(PermissionEnum.TICKETS) @PermissionsDec(PermissionEnum.TICKETS)
@Post("admin") @Post("admin")
createTicketByAdmin(@Body() createDto: CreateTicketByAdminDto, @UserDec("id") adminId: string) { createTicketByAdmin(@Body() createDto: CreateTicketByAdminDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.ticketsService.createTicketByAdmin(createDto, adminId); return this.ticketsService.createTicketByAdmin(createDto, userIpAndHeaders);
} }
@ApiOperation({ summary: "Get all tickets of user ==> user route" }) @ApiOperation({ summary: "Get all tickets of user ==> user route" })
@@ -111,23 +114,27 @@ export class TicketsController {
@ApiOperation({ summary: "close ticket by user or admin" }) @ApiOperation({ summary: "close ticket by user or admin" })
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post(":id/close") @Post(":id/close")
closedTicketByUser(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @UserDec("isAdmin") isAdmin: boolean) { closedTicketByUser(
return this.ticketsService.closeTicketByUser(paramDto.id, userId, isAdmin); @Param() paramDto: ParamDto,
@UserDec("isAdmin") isAdmin: boolean,
@UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders,
) {
return this.ticketsService.closeTicketByUser(paramDto.id, isAdmin, userIpAndHeaders);
} }
@ApiOperation({ summary: "open ticket by admin" }) @ApiOperation({ summary: "open ticket by admin" })
@AdminRoute() @AdminRoute()
@PermissionsDec(PermissionEnum.TICKETS) @PermissionsDec(PermissionEnum.TICKETS)
@Post(":id/open") @Post(":id/open")
openTicketByAdmin(@Param() paramDto: ParamDto) { openTicketByAdmin(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.ticketsService.openTicketByAdmin(paramDto.id); return this.ticketsService.openTicketByAdmin(paramDto.id, userIpAndHeaders);
} }
@ApiOperation({ summary: "referer ticket ==> admin route" }) @ApiOperation({ summary: "referer ticket ==> admin route" })
@AdminRoute() @AdminRoute()
@PermissionsDec(PermissionEnum.TICKETS) @PermissionsDec(PermissionEnum.TICKETS)
@Post(":id/refer") @Post(":id/refer")
referTicket(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @Body() referDto: ReferTicketDto) { referTicket(@Param() paramDto: ParamDto, @Body() referDto: ReferTicketDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.ticketsService.referTicket(paramDto.id, userId, referDto); return this.ticketsService.referTicket(paramDto.id, referDto, userIpAndHeaders);
} }
} }