update: remove the blog from the response of the update or create blog
This commit is contained in:
@@ -21,3 +21,16 @@ export const UserDec = createParamDecorator((data: keyof ITokenPayload | undefin
|
||||
// const req = ctx.switchToHttp().getRequest<FastifyRequest>();
|
||||
// return req.user;
|
||||
// });
|
||||
|
||||
export const UserIpAndHeaders = createParamDecorator((_data: unknown, ctx: ExecutionContext) => {
|
||||
const req = ctx.switchToHttp().getRequest<FastifyRequest>();
|
||||
const user = req.user;
|
||||
|
||||
return { ip: req.ip, headers: req.headers, userId: user?.id };
|
||||
});
|
||||
|
||||
export interface IUserIpAndHeaders {
|
||||
ip: string;
|
||||
headers: Record<string, string>;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ export const enum CategoryMessage {
|
||||
ORDER_SHOULD_BE_INT = "ترتیب دسته بندی باید یک عدد صحیح باشد",
|
||||
CREATION_FAILED = "ایجاد دسته بندی موفیقیت آمیز نبود",
|
||||
UPDATE_FAILED = "به روز رسانی دسته بندی موفیقیت آمیز نبود",
|
||||
CATEGORY_HAS_SERVICES = "دسته بندی دارای سرویس میباشد",
|
||||
}
|
||||
|
||||
export const enum ServiceMessage {
|
||||
|
||||
@@ -13,7 +13,7 @@ export function databaseConfigs(): TypeOrmModuleAsyncOptions {
|
||||
username: configService.getOrThrow<string>("DB_USER"),
|
||||
password: configService.getOrThrow<string>("DB_PASS"),
|
||||
autoLoadEntities: true,
|
||||
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : false,
|
||||
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
||||
logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
||||
migrationsTableName: "typeorm_migrations",
|
||||
migrationsRun: false,
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { IsEnum, IsNumber, IsObject, IsOptional, IsString, IsUUID } from "class-validator";
|
||||
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { OperationType } from "../enums/operation-type.enum";
|
||||
import { UserType } from "../enums/user-type.enum";
|
||||
|
||||
export class CreateAccessLogDto {
|
||||
@IsEnum(OperationType)
|
||||
operationType: OperationType;
|
||||
|
||||
@IsEnum(UserType)
|
||||
userType: UserType;
|
||||
|
||||
@IsString()
|
||||
entityName: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
entityId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
actionDescription?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
oldValues?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
newValues?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
ipAddress?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
userAgent?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
endpoint?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
statusCode?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
errorMessage?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
metadata?: Record<string, any>;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
user?: Pick<User, "id">;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
requestId?: string;
|
||||
}
|
||||
@@ -13,42 +13,42 @@ export class AccessLog extends BaseEntity {
|
||||
@Column({ type: "enum", enum: UserType })
|
||||
userType: UserType;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
@Column({ type: "varchar", length: 255, nullable: false })
|
||||
entityName: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
entityId: string;
|
||||
entityId?: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
actionDescription: string;
|
||||
actionDescription?: string;
|
||||
|
||||
@Column({ type: "text", nullable: true })
|
||||
oldValues: string;
|
||||
oldValues?: string;
|
||||
|
||||
@Column({ type: "text", nullable: true })
|
||||
newValues: string;
|
||||
newValues?: string;
|
||||
|
||||
@Column({ type: "varchar", length: 45, nullable: true })
|
||||
ipAddress: string;
|
||||
ipAddress?: string;
|
||||
|
||||
@Column({ type: "varchar", length: 500, nullable: true })
|
||||
userAgent: string;
|
||||
userAgent?: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
endpoint: string;
|
||||
endpoint?: string;
|
||||
|
||||
@Column({ type: "integer", nullable: true })
|
||||
statusCode: number;
|
||||
statusCode?: number;
|
||||
|
||||
@Column({ type: "text", nullable: true })
|
||||
errorMessage: string;
|
||||
errorMessage?: string;
|
||||
|
||||
@Column({ type: "jsonb", nullable: true })
|
||||
metadata: Record<string, any>;
|
||||
|
||||
@ManyToOne(() => User, { nullable: true })
|
||||
user: User;
|
||||
metadata?: Record<string, unknown>;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
requestId: string;
|
||||
requestId?: string;
|
||||
|
||||
@ManyToOne(() => User, { nullable: true })
|
||||
user?: User;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { AccessLog } from "../entities/access-log.entity";
|
||||
|
||||
export interface IAccessLogData extends Partial<Omit<AccessLog, "id" | "createdAt" | "updatedAt" | "user">> {
|
||||
user?: Pick<User, "id">;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { FastifyRequest } from "fastify";
|
||||
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { OperationType } from "../enums/operation-type.enum";
|
||||
import { UserType } from "../enums/user-type.enum";
|
||||
|
||||
export interface ILogContext {
|
||||
operationType: OperationType;
|
||||
entityName: string;
|
||||
entityId?: string;
|
||||
actionDescription?: string;
|
||||
oldValues?: any;
|
||||
newValues?: any;
|
||||
user?: Pick<User, "id">;
|
||||
userType?: UserType;
|
||||
metadata?: Record<string, any>;
|
||||
request?: FastifyRequest;
|
||||
statusCode?: number;
|
||||
errorMessage?: string;
|
||||
}
|
||||
@@ -1,41 +1,18 @@
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { FastifyRequest } from "fastify";
|
||||
|
||||
import { AccessLogMessage } from "../../../common/enums/message.enum";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { AccessLogQueryDto, AccessLogStatsQueryDto, EntityIdQueryDto } from "../DTO/access-log-query.dto";
|
||||
import { CreateAccessLogDto } from "../DTO/create-access-log.dto";
|
||||
import { OperationType } from "../enums/operation-type.enum";
|
||||
import { UserType } from "../enums/user-type.enum";
|
||||
import { ILogContext } from "../interfaces/log-context.interface";
|
||||
import { IAccessLogData } from "../interfaces/access-log-data.interface";
|
||||
import { AccessLogRepository } from "../repositories/access-log.repository";
|
||||
|
||||
@Injectable()
|
||||
export class AccessLogService {
|
||||
constructor(private readonly accessLogRepository: AccessLogRepository) {}
|
||||
|
||||
async logOperation(context: ILogContext) {
|
||||
const logData: CreateAccessLogDto = {
|
||||
operationType: context.operationType,
|
||||
userType: context.userType || UserType.ADMIN,
|
||||
entityName: context.entityName,
|
||||
entityId: context.entityId,
|
||||
actionDescription: context.actionDescription,
|
||||
oldValues: context.oldValues ? JSON.stringify(context.oldValues) : undefined,
|
||||
newValues: context.newValues ? JSON.stringify(context.newValues) : undefined,
|
||||
user: context.user,
|
||||
metadata: context.metadata,
|
||||
statusCode: context.statusCode,
|
||||
errorMessage: context.errorMessage,
|
||||
};
|
||||
|
||||
// Extract request information if available
|
||||
if (context.request) {
|
||||
logData.ipAddress = this.getClientIp(context.request);
|
||||
logData.userAgent = context.request.headers["user-agent"];
|
||||
logData.endpoint = context.request.url;
|
||||
logData.requestId = context.request.headers["x-request-id"] as string;
|
||||
}
|
||||
|
||||
async logOperation(logData: IAccessLogData) {
|
||||
const accessLog = this.accessLogRepository.create({ ...logData });
|
||||
await this.accessLogRepository.save(accessLog);
|
||||
|
||||
@@ -43,81 +20,74 @@ export class AccessLogService {
|
||||
}
|
||||
|
||||
//########################################################################################
|
||||
async logCreate(entityName: string, entityId: string, newValues: Record<string, any>, context: Partial<ILogContext> = {}) {
|
||||
async logCreate(entityName: string, userType: UserType, logData: IAccessLogData = {}) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType: OperationType.CREATE,
|
||||
entityName,
|
||||
entityId,
|
||||
newValues,
|
||||
actionDescription: `Created ${entityName}`,
|
||||
...context,
|
||||
entityName,
|
||||
userType,
|
||||
});
|
||||
}
|
||||
|
||||
//########################################################################################
|
||||
async logUpdate(
|
||||
entityName: string,
|
||||
entityId: string,
|
||||
oldValues: Record<string, any>,
|
||||
newValues: Record<string, any>,
|
||||
context: Partial<ILogContext> = {},
|
||||
) {
|
||||
async logUpdate(entityName: string, entityId: string, userType: UserType, logData: IAccessLogData = {}) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType: OperationType.UPDATE,
|
||||
entityName,
|
||||
entityId,
|
||||
oldValues,
|
||||
newValues,
|
||||
actionDescription: `Updated ${entityName}`,
|
||||
...context,
|
||||
userType,
|
||||
});
|
||||
}
|
||||
//########################################################################################
|
||||
async logDelete(entityName: string, entityId: string, oldValues: Record<string, any>, context: Partial<ILogContext> = {}) {
|
||||
async logDelete(entityName: string, entityId: string, userType: UserType, logData: IAccessLogData = {}) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType: OperationType.DELETE,
|
||||
entityName,
|
||||
entityId,
|
||||
oldValues,
|
||||
actionDescription: `Deleted ${entityName}`,
|
||||
...context,
|
||||
userType,
|
||||
});
|
||||
}
|
||||
|
||||
//########################################################################################
|
||||
async logRead(entityName: string, entityId?: string, context: Partial<ILogContext> = {}) {
|
||||
async logRead(entityName: string, entityId: string, userType: UserType, logData: IAccessLogData = {}) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType: OperationType.READ,
|
||||
entityName,
|
||||
entityId,
|
||||
actionDescription: `Read ${entityName}`,
|
||||
...context,
|
||||
userType,
|
||||
});
|
||||
}
|
||||
|
||||
//########################################################################################
|
||||
async logLogin(userId: string, context: Partial<ILogContext> = {}) {
|
||||
return await this.logOperation({
|
||||
async logLogin(userId: string, userType: UserType, logData: IAccessLogData = {}) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType: OperationType.LOGIN,
|
||||
entityName: "User",
|
||||
entityId: userId,
|
||||
user: { id: userId },
|
||||
userType: UserType.USER,
|
||||
user: { id: userId } as User,
|
||||
userType,
|
||||
actionDescription: "User login",
|
||||
...context,
|
||||
});
|
||||
}
|
||||
|
||||
//########################################################################################
|
||||
async logLogout(userId: string, context: Partial<ILogContext> = {}) {
|
||||
return await this.logOperation({
|
||||
async logLogout(userId: string, userType: UserType, logData: IAccessLogData = {}) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType: OperationType.LOGOUT,
|
||||
entityName: "User",
|
||||
entityId: userId,
|
||||
user: { id: userId },
|
||||
userType: UserType.USER,
|
||||
user: { id: userId } as User,
|
||||
userType,
|
||||
actionDescription: "User logout",
|
||||
...context,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -127,15 +97,15 @@ export class AccessLogService {
|
||||
entityName: string,
|
||||
entityId: string,
|
||||
actionDescription: string,
|
||||
context: Partial<ILogContext> = {},
|
||||
logData: IAccessLogData = {},
|
||||
) {
|
||||
return await this.logOperation({
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType,
|
||||
entityName,
|
||||
entityId,
|
||||
actionDescription,
|
||||
userType: UserType.ADMIN,
|
||||
...context,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,15 +116,15 @@ export class AccessLogService {
|
||||
entityName: string,
|
||||
entityId: string,
|
||||
actionDescription: string,
|
||||
context: Partial<ILogContext> = {},
|
||||
logData: IAccessLogData = {},
|
||||
) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType,
|
||||
entityName,
|
||||
entityId,
|
||||
actionDescription,
|
||||
userType: UserType.USER,
|
||||
...context,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -164,27 +134,27 @@ export class AccessLogService {
|
||||
entityName: string,
|
||||
entityId: string,
|
||||
actionDescription: string,
|
||||
context: Partial<ILogContext> = {},
|
||||
logData: IAccessLogData = {},
|
||||
) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType,
|
||||
entityName,
|
||||
entityId,
|
||||
actionDescription,
|
||||
userType: UserType.SYSTEM,
|
||||
...context,
|
||||
});
|
||||
}
|
||||
|
||||
//########################################################################################
|
||||
async logError(operationType: OperationType, entityName: string, errorMessage: string, context: Partial<ILogContext> = {}) {
|
||||
async logError(operationType: OperationType, entityName: string, errorMessage: string, logData: IAccessLogData = {}) {
|
||||
return this.logOperation({
|
||||
...logData,
|
||||
operationType,
|
||||
entityName,
|
||||
errorMessage,
|
||||
actionDescription: `Error in ${operationType} operation on ${entityName}`,
|
||||
statusCode: 500,
|
||||
...context,
|
||||
});
|
||||
}
|
||||
//########################################################################################
|
||||
@@ -230,11 +200,4 @@ export class AccessLogService {
|
||||
if (!accessLog) throw new BadRequestException(AccessLogMessage.ACCESS_LOG_NOT_FOUND);
|
||||
return { accessLog };
|
||||
}
|
||||
|
||||
//########################################################################################
|
||||
private getClientIp(request: FastifyRequest): string {
|
||||
return (
|
||||
(request.headers["x-forwarded-for"] as string)?.split(",")[0] || (request.headers["x-real-ip"] as string) || request.ip || "unknown"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { UpdateAnnouncementDto } from "./DTO/update-announcement.dto";
|
||||
import { AnnouncementService } from "./providers/announcement.service";
|
||||
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";
|
||||
|
||||
@@ -19,22 +19,26 @@ export class AnnouncementController {
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS)
|
||||
@ApiProperty({ description: "Create a new announcement (admin route)" })
|
||||
@Post()
|
||||
create(@Body() createAnnouncementDto: CreateAnnouncementDto, @UserDec("id") userId: string) {
|
||||
return this.announcementService.createAnnouncement(createAnnouncementDto, userId);
|
||||
create(@Body() createAnnouncementDto: CreateAnnouncementDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.announcementService.createAnnouncement(createAnnouncementDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS)
|
||||
@ApiProperty({ description: "Update an announcement (admin route)" })
|
||||
@Patch(":id")
|
||||
update(@Param() paramDto: ParamDto, @Body() updateAnnouncementDto: UpdateAnnouncementDto, @UserDec("id") userId: string) {
|
||||
return this.announcementService.updateAnnouncement(paramDto.id, updateAnnouncementDto, userId);
|
||||
update(
|
||||
@Param() paramDto: ParamDto,
|
||||
@Body() updateAnnouncementDto: UpdateAnnouncementDto,
|
||||
@UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders,
|
||||
) {
|
||||
return this.announcementService.updateAnnouncement(paramDto.id, updateAnnouncementDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS)
|
||||
@ApiProperty({ description: "Delete an announcement (admin route)" })
|
||||
@Delete(":id")
|
||||
delete(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||
return this.announcementService.deleteAnnouncement(paramDto.id, userId);
|
||||
delete(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.announcementService.deleteAnnouncement(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiProperty({ description: "Get all announcements (admin route)" })
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { Queue } from "bullmq";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator";
|
||||
import { AnnouncementMessage } from "../../../common/enums/message.enum";
|
||||
import { UserType } from "../../access-logs/enums/user-type.enum";
|
||||
import { AccessLogService } from "../../access-logs/providers/access-log.service";
|
||||
@@ -27,7 +28,7 @@ export class AnnouncementService {
|
||||
|
||||
/***************************************** */
|
||||
|
||||
async createAnnouncement(createAnnouncementDto: CreateAnnouncementDto, userId: string) {
|
||||
async createAnnouncement(createAnnouncementDto: CreateAnnouncementDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const { serviceId, userIds } = createAnnouncementDto;
|
||||
|
||||
let isPublic = true;
|
||||
@@ -65,12 +66,19 @@ export class AnnouncementService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.accessLogService.logCreate("Announcement", announcement.id, announcement, {
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
await this.accessLogService.logCreate("Announcement", UserType.ADMIN, {
|
||||
entityId: announcement.id,
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/announcements",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created announcement: ${announcement.title}`,
|
||||
metadata: {
|
||||
serviceId: serviceId,
|
||||
userIp: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -210,7 +218,7 @@ export class AnnouncementService {
|
||||
}
|
||||
//************************************ */
|
||||
|
||||
async updateAnnouncement(id: string, updateAnnouncementDto: UpdateAnnouncementDto, userId: string) {
|
||||
async updateAnnouncement(id: string, updateAnnouncementDto: UpdateAnnouncementDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const announcement = await this.announcementRepository.findOneBy({ id });
|
||||
if (!announcement) throw new BadRequestException(AnnouncementMessage.NOT_FOUND);
|
||||
|
||||
@@ -255,12 +263,19 @@ export class AnnouncementService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.accessLogService.logUpdate("Announcement", announcement.id, announcement, {
|
||||
user: { id: userId },
|
||||
await this.accessLogService.logUpdate("Announcement", announcement.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/announcements",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
userType: UserType.ADMIN,
|
||||
actionDescription: `Admin updated announcement: ${announcement.title}`,
|
||||
metadata: {
|
||||
serviceId: serviceId,
|
||||
userIp: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -272,7 +287,7 @@ export class AnnouncementService {
|
||||
|
||||
//************************************ */
|
||||
|
||||
async deleteAnnouncement(id: string, userId: string) {
|
||||
async deleteAnnouncement(id: string, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const announcement = await this.announcementRepository.findOneBy({ id });
|
||||
if (!announcement) throw new BadRequestException(AnnouncementMessage.NOT_FOUND);
|
||||
|
||||
@@ -284,12 +299,19 @@ export class AnnouncementService {
|
||||
|
||||
await this.announcementRepository.delete(id);
|
||||
|
||||
await this.accessLogService.logDelete("Announcement", announcement.id, announcement, {
|
||||
user: { id: userId },
|
||||
await this.accessLogService.logDelete("Announcement", announcement.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/announcements",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
userType: UserType.ADMIN,
|
||||
actionDescription: `Admin deleted announcement: ${announcement.title}`,
|
||||
metadata: {
|
||||
serviceId: announcement.targetService.id,
|
||||
userIp: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Query } from "@nestjs/common";
|
||||
import { CacheInterceptor, CacheTTL } from "@nestjs/cache-manager";
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
|
||||
import { ApiOperation } from "@nestjs/swagger";
|
||||
|
||||
import { BlogCommentQueryDto } from "./DTO/blog-comment-query.dto";
|
||||
@@ -18,7 +19,7 @@ import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
import { Pagination } from "../../common/decorators/pagination.decorator";
|
||||
import { PermissionsDec } from "../../common/decorators/permission.decorator";
|
||||
import { SkipAuth } from "../../common/decorators/skip-auth.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 { CategoryListSearchQueryDto } from "../danak-services/DTO/category-search-query.dto";
|
||||
import { PermissionEnum } from "../users/enums/permission.enum";
|
||||
@@ -31,6 +32,8 @@ export class BlogsController {
|
||||
) {}
|
||||
|
||||
@ApiOperation({ summary: "Get all public and active blogs with category id" })
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheTTL(120 * 1000)
|
||||
@SkipAuth()
|
||||
@Get()
|
||||
getBlogsPublic(@Query() queryDto: BlogSearchQueryDto) {
|
||||
@@ -38,6 +41,8 @@ export class BlogsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get most visited and pinned blogs" })
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheTTL(120 * 1000)
|
||||
@SkipAuth()
|
||||
@Get("combined")
|
||||
getCombinedBlogs() {
|
||||
@@ -53,6 +58,8 @@ export class BlogsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get a blog by id" })
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheTTL(120 * 1000)
|
||||
@SkipAuth()
|
||||
@Get(":id")
|
||||
getBlog(@Param() paramDto: ParamDto, @UserDec("isAdmin") isAdmin: boolean) {
|
||||
@@ -60,6 +67,8 @@ export class BlogsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get a blog by slug" })
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheTTL(120 * 1000)
|
||||
@SkipAuth()
|
||||
@Get("slug/:slug")
|
||||
getBlogBySlug(@Param() paramDto: BlogSlugDto, @UserDec("isAdmin") isAdmin: boolean) {
|
||||
@@ -67,6 +76,8 @@ export class BlogsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get most visited blogs" })
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheTTL(120 * 1000)
|
||||
@SkipAuth()
|
||||
@Get("most-visited")
|
||||
getMostVisitedBlogs() {
|
||||
@@ -82,6 +93,8 @@ export class BlogsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get most pinned blogs" })
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheTTL(120 * 1000)
|
||||
@SkipAuth()
|
||||
@Get("pinned")
|
||||
getPinnedBlogs() {
|
||||
@@ -100,24 +113,24 @@ export class BlogsController {
|
||||
@Post()
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
createBlog(@Body() createBlogDto: CreateBlogDto, @UserDec("id") userId: string) {
|
||||
return this.blogsService.createBlog(createBlogDto, userId);
|
||||
createBlog(@Body() createBlogDto: CreateBlogDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.createBlog(createBlogDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Update a blog (admin route)" })
|
||||
@Patch(":id")
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
updateBlog(@Param() paramDto: ParamDto, @Body() updateBlogDto: UpdateBlogDto, @UserDec("id") userId: string) {
|
||||
return this.blogsService.updateBlog(paramDto.id, updateBlogDto, userId);
|
||||
updateBlog(@Param() paramDto: ParamDto, @Body() updateBlogDto: UpdateBlogDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.updateBlog(paramDto.id, updateBlogDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Delete a blog (admin route) " })
|
||||
@Delete(":id")
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
deleteBlog(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||
return this.blogsService.deleteBlog(paramDto.id, userId);
|
||||
deleteBlog(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.deleteBlog(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
//-------------- comments --------------
|
||||
@@ -200,15 +213,15 @@ export class BlogsController {
|
||||
@Delete("categories/:id")
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
deleteCategory(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||
return this.blogsService.deleteCategory(paramDto.id, userId);
|
||||
deleteCategory(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.deleteCategory(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "toggle status of categories (admin route)" })
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("categories/toggle-status/:id")
|
||||
toggleCategoryStatus(@Param() paramDto: ParamDto) {
|
||||
return this.blogsService.toggleCategoryStatus(paramDto.id);
|
||||
toggleCategoryStatus(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.toggleCategoryStatus(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { Not } from "typeorm";
|
||||
|
||||
import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator";
|
||||
import { AdminMessage, BlogMessage, CommonMessage } from "../../../common/enums/message.enum";
|
||||
import { UserType } from "../../access-logs/enums/user-type.enum";
|
||||
import { AccessLogService } from "../../access-logs/providers/access-log.service";
|
||||
@@ -49,13 +50,13 @@ export class BlogsService {
|
||||
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
category,
|
||||
categoryId: category.id,
|
||||
};
|
||||
}
|
||||
//*********************************** */
|
||||
|
||||
async deleteCategory(id: string, userId: string) {
|
||||
const isSuperAdmin = await this.usersService.isSuperAdmin(userId);
|
||||
async deleteCategory(id: string, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const isSuperAdmin = await this.usersService.isSuperAdmin(userIpAndHeaders.userId);
|
||||
if (!isSuperAdmin) throw new BadRequestException(AdminMessage.NOT_ALLOWED);
|
||||
|
||||
const category = await this.blogCategoriesRepository.findOneBy({ id });
|
||||
@@ -65,23 +66,45 @@ export class BlogsService {
|
||||
if (blogs.length > 0) throw new BadRequestException(BlogMessage.CATEGORY_HAS_BLOGS);
|
||||
|
||||
await this.blogCategoriesRepository.remove(category);
|
||||
|
||||
await this.accessLogService.logDelete("BlogCategory", category.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/blogs/categories",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin deleted blog category: ${category.title}`,
|
||||
metadata: {
|
||||
categoryId: category.id,
|
||||
},
|
||||
});
|
||||
return {
|
||||
message: CommonMessage.DELETED,
|
||||
};
|
||||
}
|
||||
//*********************************** */
|
||||
|
||||
async toggleCategoryStatus(id: string) {
|
||||
async toggleCategoryStatus(id: string, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const category = await this.findCategoryById(id);
|
||||
|
||||
//
|
||||
category.isActive = !category.isActive;
|
||||
await this.blogCategoriesRepository.save(category);
|
||||
|
||||
//
|
||||
await this.accessLogService.logUpdate("BlogCategory", category.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/blogs/categories",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin updated blog category status: ${category.title}`,
|
||||
metadata: {
|
||||
categoryId: category.id,
|
||||
},
|
||||
});
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
category,
|
||||
categoryId: category.id,
|
||||
};
|
||||
}
|
||||
//*********************************** */
|
||||
@@ -104,7 +127,7 @@ export class BlogsService {
|
||||
}
|
||||
//*********************************** */
|
||||
|
||||
async createBlog(createBlogDto: CreateBlogDto, authorId: string) {
|
||||
async createBlog(createBlogDto: CreateBlogDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const existingBlog = await this.blogsRepository.findOne({ where: { title: createBlogDto.title } });
|
||||
if (existingBlog) throw new BadRequestException(BlogMessage.BLOG_ALREADY_EXISTS);
|
||||
|
||||
@@ -117,38 +140,36 @@ export class BlogsService {
|
||||
|
||||
const blog = this.blogsRepository.create({
|
||||
...createBlogDto,
|
||||
author: { id: authorId },
|
||||
author: { id: userIpAndHeaders.userId },
|
||||
category,
|
||||
slug,
|
||||
});
|
||||
|
||||
await this.blogsRepository.save(blog);
|
||||
|
||||
await this.accessLogService.logCreate(
|
||||
"Blog",
|
||||
blog.id,
|
||||
{ blog: createBlogDto.title },
|
||||
{
|
||||
user: { id: authorId },
|
||||
userType: UserType.ADMIN,
|
||||
actionDescription: `Admin created blog: ${createBlogDto.title}`,
|
||||
metadata: {
|
||||
categoryId: createBlogDto.categoryId,
|
||||
slug: slug,
|
||||
authorId: authorId,
|
||||
},
|
||||
await this.accessLogService.logCreate("Blog", UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/blogs",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created blog: ${createBlogDto.title}`,
|
||||
metadata: {
|
||||
categoryId: createBlogDto.categoryId,
|
||||
slug: slug,
|
||||
authorId: userIpAndHeaders.userId,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
message: CommonMessage.CREATED,
|
||||
blog,
|
||||
blogId: blog.id,
|
||||
};
|
||||
}
|
||||
|
||||
//*********************************** */
|
||||
|
||||
async updateBlog(id: string, updateBlogDto: UpdateBlogDto, userId: string) {
|
||||
async updateBlog(id: string, updateBlogDto: UpdateBlogDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const blog = await this.findBlogById(id);
|
||||
|
||||
if (updateBlogDto.title) {
|
||||
@@ -172,30 +193,29 @@ export class BlogsService {
|
||||
slug: this.generateSlug(updateBlogDto.slug ?? blog.slug),
|
||||
});
|
||||
|
||||
await this.accessLogService.logUpdate(
|
||||
"Blog",
|
||||
blog.id,
|
||||
{ blog: blog.title },
|
||||
{ blog: updateBlogDto.title },
|
||||
{
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
actionDescription: `Admin updated blog: ${blog.title}`,
|
||||
metadata: {
|
||||
categoryId: updateBlogDto.categoryId,
|
||||
},
|
||||
await this.accessLogService.logUpdate("Blog", blog.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/blogs",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin updated blog: ${blog.title}`,
|
||||
metadata: {
|
||||
categoryId: updateBlogDto.categoryId,
|
||||
},
|
||||
);
|
||||
oldValues: JSON.stringify({ ...blog }),
|
||||
newValues: JSON.stringify({ ...updateBlogDto }),
|
||||
});
|
||||
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
blog,
|
||||
blogId: blog.id,
|
||||
};
|
||||
}
|
||||
//*********************************** */
|
||||
|
||||
async deleteBlog(id: string, userId: string) {
|
||||
const isSuperAdmin = await this.usersService.isSuperAdmin(userId);
|
||||
async deleteBlog(id: string, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const isSuperAdmin = await this.usersService.isSuperAdmin(userIpAndHeaders.userId);
|
||||
if (!isSuperAdmin) throw new BadRequestException(AdminMessage.NOT_ALLOWED);
|
||||
|
||||
const blog = await this.blogsRepository.findOneBy({ id });
|
||||
@@ -203,19 +223,19 @@ export class BlogsService {
|
||||
|
||||
await this.blogsRepository.softDelete({ id });
|
||||
|
||||
await this.accessLogService.logDelete(
|
||||
"Blog",
|
||||
blog.id,
|
||||
{ blog: blog.title },
|
||||
{
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
actionDescription: `Admin deleted blog: ${blog.title}`,
|
||||
metadata: {
|
||||
categoryId: blog.category.id,
|
||||
},
|
||||
await this.accessLogService.logDelete("Blog", blog.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/blogs",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin deleted blog: ${blog.title}`,
|
||||
metadata: {
|
||||
categoryId: blog.category.id,
|
||||
},
|
||||
);
|
||||
oldValues: JSON.stringify({ ...blog }),
|
||||
newValues: JSON.stringify({ ...blog }),
|
||||
});
|
||||
return {
|
||||
message: CommonMessage.DELETED,
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
import { Pagination } from "../../common/decorators/pagination.decorator";
|
||||
import { PermissionsDec } from "../../common/decorators/permission.decorator";
|
||||
import { StrictRateLimit } from "../../common/decorators/rate-limit.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";
|
||||
|
||||
@@ -40,8 +40,8 @@ export class DanakServicesController {
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@ApiOperation({ summary: "Create a new service category => admin route" })
|
||||
@Post("categories")
|
||||
createCategory(@Body() createDto: CreateDanakServiceCategoryDto) {
|
||||
return this.danakServicesService.createCategory(createDto);
|
||||
createCategory(@Body() createDto: CreateDanakServiceCategoryDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.danakServicesService.createCategory(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@@ -80,16 +80,20 @@ export class DanakServicesController {
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@ApiOperation({ summary: "update category by id => admin route" })
|
||||
@Patch("categories/:id")
|
||||
updateCategory(@Param() paramDto: ParamDto, @Body() updateDto: UpdateDanakServiceCategoryDto) {
|
||||
return this.danakServicesService.updateCategory(paramDto.id, updateDto);
|
||||
updateCategory(
|
||||
@Param() paramDto: ParamDto,
|
||||
@Body() updateDto: UpdateDanakServiceCategoryDto,
|
||||
@UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders,
|
||||
) {
|
||||
return this.danakServicesService.updateCategory(paramDto.id, updateDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@ApiOperation({ summary: "delete category by id => admin route" })
|
||||
@Delete("categories/:id")
|
||||
deleteCategoryById(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.deleteCategoryById(paramDto.id);
|
||||
deleteCategoryById(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.danakServicesService.deleteCategoryById(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@@ -97,16 +101,16 @@ export class DanakServicesController {
|
||||
@ApiOperation({ summary: "toggle status of categories => admin route" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("categories/toggle-status/:id")
|
||||
toggleCategoryStatus(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.toggleCategoryStatus(paramDto);
|
||||
toggleCategoryStatus(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.danakServicesService.toggleCategoryStatus(paramDto, userIpAndHeaders);
|
||||
}
|
||||
//-------------------- service management --------------------------
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@ApiOperation({ summary: "create new danak services => admin route" })
|
||||
@Post()
|
||||
createService(@Body() createDto: CreateServiceDto, @UserDec("id") userId: string) {
|
||||
return this.danakServicesService.createService(createDto, userId);
|
||||
createService(@Body() createDto: CreateServiceDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.danakServicesService.createService(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@@ -136,8 +140,8 @@ export class DanakServicesController {
|
||||
@ApiOperation({ summary: "toggle status of danak service => admin route" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("toggle-status/:id")
|
||||
toggleServiceStatus(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.toggleServiceStatus(paramDto);
|
||||
toggleServiceStatus(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.danakServicesService.toggleServiceStatus(paramDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@@ -190,8 +194,12 @@ export class DanakServicesController {
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@Patch(":id")
|
||||
updateDanakService(@Param() paramDto: ParamDto, @Body() updateDto: UpdateServiceDto, @UserDec("id") userId: string) {
|
||||
return this.danakServicesService.updateDanakService(paramDto.id, updateDto, userId);
|
||||
updateDanakService(
|
||||
@Param() paramDto: ParamDto,
|
||||
@Body() updateDto: UpdateServiceDto,
|
||||
@UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders,
|
||||
) {
|
||||
return this.danakServicesService.updateDanakService(paramDto.id, updateDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "delete danak service" })
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||
import { DataSource, FindOptionsWhere, In, IsNull, LessThan, MoreThan, Not, QueryRunner } from "typeorm";
|
||||
|
||||
import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator";
|
||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||
import { CategoryMessage, CommonMessage, ServiceMessage } from "../../../common/enums/message.enum";
|
||||
import { UserType } from "../../access-logs/enums/user-type.enum";
|
||||
@@ -48,7 +49,7 @@ export class DanakServicesService {
|
||||
) {}
|
||||
/******************************************** */
|
||||
|
||||
async createCategory(createDto: CreateDanakServiceCategoryDto) {
|
||||
async createCategory(createDto: CreateDanakServiceCategoryDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
@@ -68,6 +69,31 @@ export class DanakServicesService {
|
||||
await this.handleOrderCollision(queryRunner, category);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.accessLogService.logCreate("DanakServiceCategory", UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/danak-services/categories",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created category: ${createDto.title}`,
|
||||
metadata: {
|
||||
userIp: userIpAndHeaders.ip,
|
||||
referer: userIpAndHeaders.headers["referer"] || userIpAndHeaders.headers["referrer"],
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
acceptLanguage: userIpAndHeaders.headers["accept-language"],
|
||||
acceptEncoding: userIpAndHeaders.headers["accept-encoding"],
|
||||
contentType: userIpAndHeaders.headers["content-type"],
|
||||
origin: userIpAndHeaders.headers["origin"],
|
||||
host: userIpAndHeaders.headers["host"],
|
||||
xForwardedFor: userIpAndHeaders.headers["x-forwarded-for"],
|
||||
xRealIp: userIpAndHeaders.headers["x-real-ip"],
|
||||
timestamp: new Date().toISOString(),
|
||||
platform: userIpAndHeaders.headers["sec-ch-ua-platform"],
|
||||
mobile: userIpAndHeaders.headers["sec-ch-ua-mobile"],
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
message: CommonMessage.CREATED,
|
||||
category,
|
||||
@@ -112,7 +138,7 @@ export class DanakServicesService {
|
||||
|
||||
/******************************************** */
|
||||
|
||||
async updateCategory(categoryId: string, updateDto: UpdateDanakServiceCategoryDto) {
|
||||
async updateCategory(categoryId: string, updateDto: UpdateDanakServiceCategoryDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
@@ -135,8 +161,35 @@ export class DanakServicesService {
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.accessLogService.logUpdate("DanakServiceCategory", category.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/danak-services/categories",
|
||||
metadata: {
|
||||
userIp: userIpAndHeaders.ip,
|
||||
referer: userIpAndHeaders.headers["referer"] || userIpAndHeaders.headers["referrer"],
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
acceptLanguage: userIpAndHeaders.headers["accept-language"],
|
||||
acceptEncoding: userIpAndHeaders.headers["accept-encoding"],
|
||||
contentType: userIpAndHeaders.headers["content-type"],
|
||||
origin: userIpAndHeaders.headers["origin"],
|
||||
host: userIpAndHeaders.headers["host"],
|
||||
xForwardedFor: userIpAndHeaders.headers["x-forwarded-for"],
|
||||
xRealIp: userIpAndHeaders.headers["x-real-ip"],
|
||||
timestamp: new Date().toISOString(),
|
||||
platform: userIpAndHeaders.headers["sec-ch-ua-platform"],
|
||||
mobile: userIpAndHeaders.headers["sec-ch-ua-mobile"],
|
||||
},
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin updated category: ${updateDto.title}`,
|
||||
oldValues: JSON.stringify({ ...category }),
|
||||
newValues: JSON.stringify({ ...updateDto }),
|
||||
});
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
categoryId: category.id,
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
@@ -149,11 +202,37 @@ export class DanakServicesService {
|
||||
|
||||
/******************************************** */
|
||||
|
||||
async deleteCategoryById(categoryId: string) {
|
||||
async deleteCategoryById(categoryId: string, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const category = await this.danakServicesCategoryRepository.findOneById(categoryId);
|
||||
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
|
||||
|
||||
if (category.danakServices.length > 0) throw new BadRequestException(CategoryMessage.CATEGORY_HAS_SERVICES);
|
||||
//
|
||||
await this.danakServicesCategoryRepository.softDelete(categoryId);
|
||||
|
||||
await this.accessLogService.logDelete("DanakServiceCategory", categoryId, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/danak-services/categories",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin deleted category: ${category.title}`,
|
||||
metadata: {
|
||||
userIp: userIpAndHeaders.ip,
|
||||
referer: userIpAndHeaders.headers["referer"] || userIpAndHeaders.headers["referrer"],
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
acceptLanguage: userIpAndHeaders.headers["accept-language"],
|
||||
acceptEncoding: userIpAndHeaders.headers["accept-encoding"],
|
||||
contentType: userIpAndHeaders.headers["content-type"],
|
||||
origin: userIpAndHeaders.headers["origin"],
|
||||
host: userIpAndHeaders.headers["host"],
|
||||
xForwardedFor: userIpAndHeaders.headers["x-forwarded-for"],
|
||||
xRealIp: userIpAndHeaders.headers["x-real-ip"],
|
||||
timestamp: new Date().toISOString(),
|
||||
platform: userIpAndHeaders.headers["sec-ch-ua-platform"],
|
||||
mobile: userIpAndHeaders.headers["sec-ch-ua-mobile"],
|
||||
},
|
||||
});
|
||||
return {
|
||||
message: CommonMessage.DELETED,
|
||||
};
|
||||
@@ -235,7 +314,7 @@ export class DanakServicesService {
|
||||
|
||||
/******************************************** */
|
||||
|
||||
async toggleCategoryStatus(paramDto: ParamDto) {
|
||||
async toggleCategoryStatus(paramDto: ParamDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const category = await this.danakServicesCategoryRepository.findOneById(paramDto.id);
|
||||
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
|
||||
//
|
||||
@@ -243,14 +322,23 @@ export class DanakServicesService {
|
||||
//
|
||||
await this.danakServicesCategoryRepository.save(category);
|
||||
//
|
||||
await this.accessLogService.logUpdate("DanakServiceCategory", category.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/danak-services/categories",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
});
|
||||
//
|
||||
return {
|
||||
categoryId: category.id,
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
isActive: category.isActive,
|
||||
};
|
||||
}
|
||||
/******************************************** */
|
||||
|
||||
async toggleServiceStatus(paramDto: ParamDto) {
|
||||
async toggleServiceStatus(paramDto: ParamDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const service = await this.danakServicesRepository.findServiceById(paramDto.id);
|
||||
if (!service) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST);
|
||||
//
|
||||
@@ -258,6 +346,16 @@ export class DanakServicesService {
|
||||
//
|
||||
await this.danakServicesRepository.save(service);
|
||||
//
|
||||
await this.accessLogService.logUpdate("DanakService", service.id, UserType.ADMIN, {
|
||||
userType: UserType.ADMIN,
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/danak-services",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin updated service status: ${service.name}`,
|
||||
});
|
||||
//
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
isActive: service.isActive,
|
||||
@@ -278,7 +376,7 @@ export class DanakServicesService {
|
||||
};
|
||||
}
|
||||
/******************************************** */
|
||||
async createService(createDto: CreateServiceDto, userId: string) {
|
||||
async createService(createDto: CreateServiceDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const category = await this.danakServicesCategoryRepository.findOneById(createDto.categoryId);
|
||||
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
|
||||
|
||||
@@ -295,7 +393,7 @@ export class DanakServicesService {
|
||||
|
||||
if (createDto.audios) {
|
||||
audios = createDto.audios.map((audio) => {
|
||||
return { audioUrl: audio, createBy: { id: userId } as User };
|
||||
return { audioUrl: audio, createBy: { id: userIpAndHeaders.userId } as User };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -303,7 +401,7 @@ export class DanakServicesService {
|
||||
|
||||
if (createDto.videos) {
|
||||
videos = createDto.videos.map((video) => {
|
||||
return { videoUrl: video, createBy: { id: userId } as User };
|
||||
return { videoUrl: video, createBy: { id: userIpAndHeaders.userId } as User };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -311,9 +409,12 @@ export class DanakServicesService {
|
||||
await this.danakServicesRepository.save(service);
|
||||
|
||||
// Log the service creation
|
||||
await this.accessLogService.logCreate("DanakService", service.id, createDto, {
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
await this.accessLogService.logCreate("DanakService", UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/danak-services",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created service: ${createDto.name}`,
|
||||
metadata: {
|
||||
categoryId: createDto.categoryId,
|
||||
@@ -326,11 +427,11 @@ export class DanakServicesService {
|
||||
|
||||
return {
|
||||
message: CommonMessage.CREATED,
|
||||
service,
|
||||
serviceId: service.id,
|
||||
};
|
||||
}
|
||||
/******************************************** */
|
||||
async updateDanakService(serviceId: string, updateDto: UpdateServiceDto, userId: string) {
|
||||
async updateDanakService(serviceId: string, updateDto: UpdateServiceDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const service = await this.findServiceById(serviceId);
|
||||
|
||||
let images: DanakServiceImage[] = [];
|
||||
@@ -370,7 +471,7 @@ export class DanakServicesService {
|
||||
return this.danakServicesAudioRepository.create({
|
||||
audioUrl: audio,
|
||||
danakService: service,
|
||||
createBy: { id: userId } as User,
|
||||
createBy: { id: userIpAndHeaders.userId },
|
||||
});
|
||||
});
|
||||
await this.danakServicesAudioRepository.save(newAudios);
|
||||
@@ -385,7 +486,7 @@ export class DanakServicesService {
|
||||
return this.danakServicesVideoRepository.create({
|
||||
videoUrl: video,
|
||||
danakService: service,
|
||||
createBy: { id: userId } as User,
|
||||
createBy: { id: userIpAndHeaders.userId },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -407,9 +508,29 @@ export class DanakServicesService {
|
||||
audios: audios.length ? audios : service.audios,
|
||||
videos: videos.length ? videos : service.videos,
|
||||
});
|
||||
|
||||
await this.accessLogService.logUpdate("DanakService", serviceId, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/danak-services",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin updated service: ${service.name}`,
|
||||
metadata: {
|
||||
categoryId: updateDto.categoryId,
|
||||
slug: updateDto.slug,
|
||||
hasImages: updateDto.images && updateDto.images.length > 0,
|
||||
hasAudios: updateDto.audios && updateDto.audios.length > 0,
|
||||
hasVideos: updateDto.videos && updateDto.videos.length > 0,
|
||||
},
|
||||
|
||||
oldValues: JSON.stringify({ ...service }),
|
||||
newValues: JSON.stringify({ ...updateDto }),
|
||||
});
|
||||
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
service,
|
||||
serviceId: service.id,
|
||||
};
|
||||
}
|
||||
/******************************************** */
|
||||
|
||||
@@ -15,6 +15,6 @@ export class DanakServicesCategoryRepository extends Repository<DanakServiceCate
|
||||
}
|
||||
|
||||
async findOneById(id: string): Promise<DanakServiceCategory | null> {
|
||||
return this.findOneBy({ id, deletedAt: IsNull() });
|
||||
return this.findOne({ where: { id, deletedAt: IsNull() }, relations: { danakServices: true } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ApiKeyGuards } from "../../common/decorators/api-key-guard.decorator";
|
||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
import { PermissionsDec } from "../../common/decorators/permission.decorator";
|
||||
import { SkipAuth } from "../../common/decorators/skip-auth.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 { VerifyOtpWithUserId } from "../auth/DTO/verify-otp.dto";
|
||||
import { PermissionEnum } from "../users/enums/permission.enum";
|
||||
@@ -23,22 +23,22 @@ export class InvoicesController {
|
||||
@ApiOperation({ summary: "create an invoice ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.INVOICES)
|
||||
@Post()
|
||||
createInvoice(@Body() createDto: CreateInvoiceDto, @UserDec("id") userId: string) {
|
||||
return this.invoiceService.createInvoiceAdmin(createDto, userId);
|
||||
createInvoice(@Body() createDto: CreateInvoiceDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.invoiceService.createInvoiceAdmin(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "update an invoice ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.INVOICES)
|
||||
@Patch(":id")
|
||||
updateInvoice(@Param() paramDto: ParamDto, @Body() updateDto: UpdateInvoiceDto, @UserDec("id") userId: string) {
|
||||
return this.invoiceService.updateInvoiceAdmin(paramDto.id, updateDto, userId);
|
||||
updateInvoice(@Param() paramDto: ParamDto, @Body() updateDto: UpdateInvoiceDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.invoiceService.updateInvoiceAdmin(paramDto.id, updateDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "delete an invoice ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.INVOICES)
|
||||
@Delete(":id")
|
||||
deleteInvoice(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||
return this.invoiceService.deleteInvoiceAdmin(paramDto.id, userId);
|
||||
deleteInvoice(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.invoiceService.deleteInvoiceAdmin(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get all invoices ==> admin route" })
|
||||
@@ -62,20 +62,24 @@ export class InvoicesController {
|
||||
|
||||
@ApiOperation({ summary: "approve request invoice by user" })
|
||||
@Post(":id/approve/request")
|
||||
approveRequestInvoiceByUser(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||
return this.invoiceService.approveInvoiceRequest(paramDto.id, userId);
|
||||
approveRequestInvoiceByUser(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.invoiceService.approveInvoiceRequest(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "approve invoice by user" })
|
||||
@Patch(":id/approve")
|
||||
approveInvoiceByUser(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @Body() verifyOtpDto: VerifyOtpWithUserId) {
|
||||
return this.invoiceService.approveInvoiceByUser(paramDto.id, userId, verifyOtpDto);
|
||||
approveInvoiceByUser(
|
||||
@Param() paramDto: ParamDto,
|
||||
@UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders,
|
||||
@Body() verifyOtpDto: VerifyOtpWithUserId,
|
||||
) {
|
||||
return this.invoiceService.approveInvoiceByUser(paramDto.id, userIpAndHeaders, verifyOtpDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "pay invoice by user" })
|
||||
@Post(":id/pay")
|
||||
async payInvoice(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||
return this.invoiceService.payInvoice(paramDto.id, userId);
|
||||
async payInvoice(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.invoiceService.payInvoice(paramDto.id, userIpAndHeaders.userId, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "apply discount on invoice by user" })
|
||||
|
||||
@@ -5,6 +5,7 @@ import dayjs from "dayjs";
|
||||
import Decimal from "decimal.js";
|
||||
import { Between, DataSource, In, QueryRunner } from "typeorm";
|
||||
|
||||
import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator";
|
||||
import { AuthMessage, InvoiceMessage, WalletMessage } from "../../../common/enums/message.enum";
|
||||
import { OperationType } from "../../access-logs/enums/operation-type.enum";
|
||||
import { UserType } from "../../access-logs/enums/user-type.enum";
|
||||
@@ -56,7 +57,7 @@ export class InvoicesService {
|
||||
|
||||
///********************************** */
|
||||
|
||||
async createInvoiceAdmin(createDto: CreateInvoiceDto, userId: string) {
|
||||
async createInvoiceAdmin(createDto: CreateInvoiceDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
@@ -83,7 +84,7 @@ export class InvoicesService {
|
||||
const dueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate();
|
||||
|
||||
const invoice = queryRunner.manager.create(this.invoiceRepository.target, {
|
||||
user: { id: userId },
|
||||
user: { id: createDto.userId },
|
||||
totalPrice: totalPrice.add(tax).toNumber(),
|
||||
originalPrice: totalPrice.add(tax).toNumber(),
|
||||
items: invoiceItems,
|
||||
@@ -113,9 +114,12 @@ export class InvoicesService {
|
||||
|
||||
await this.scheduleInvoiceJobs(invoice, createDto);
|
||||
|
||||
await this.accessLogService.logCreate("Invoice", invoice.id, createDto, {
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
await this.accessLogService.logCreate("Invoice", UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/invoices",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created invoice: ${invoice.numericId}`,
|
||||
metadata: {
|
||||
items: invoiceItems.map((item) => item.name).join(", "),
|
||||
@@ -141,7 +145,7 @@ export class InvoicesService {
|
||||
|
||||
//********************************** */
|
||||
|
||||
async updateInvoiceAdmin(invoiceId: string, updateDto: UpdateInvoiceDto, userId: string) {
|
||||
async updateInvoiceAdmin(invoiceId: string, updateDto: UpdateInvoiceDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
@@ -204,10 +208,15 @@ export class InvoicesService {
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.accessLogService.logUpdate("Invoice", invoice.id, updateDto, {
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
await this.accessLogService.logUpdate("Invoice", invoice.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/invoices",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin updated invoice: ${invoice.numericId}`,
|
||||
oldValues: JSON.stringify({ ...invoice }),
|
||||
newValues: JSON.stringify({ ...updateDto }),
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -224,7 +233,7 @@ export class InvoicesService {
|
||||
|
||||
//********************************** */
|
||||
|
||||
async deleteInvoiceAdmin(invoiceId: string, userId: string) {
|
||||
async deleteInvoiceAdmin(invoiceId: string, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
@@ -245,10 +254,16 @@ export class InvoicesService {
|
||||
|
||||
await queryRunner.manager.remove(this.invoiceRepository.target, invoice);
|
||||
|
||||
await this.accessLogService.logDelete("Invoice", invoice.id, invoice, {
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
await this.accessLogService.logDelete("Invoice", invoice.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/invoices",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin deleted invoice: ${invoice.numericId}`,
|
||||
metadata: {
|
||||
items: invoice.items.map((item) => item.name).join(", "),
|
||||
},
|
||||
});
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
@@ -326,16 +341,16 @@ export class InvoicesService {
|
||||
|
||||
//********************************** */
|
||||
|
||||
async approveInvoiceRequest(invoiceId: string, userId: string) {
|
||||
async approveInvoiceRequest(invoiceId: string, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
|
||||
const user = await this.usersService.findOneByIdWithQueryRunner(userIpAndHeaders.userId, queryRunner);
|
||||
|
||||
const invoice = await this.validateInvoiceForApproval(invoiceId, userId, queryRunner);
|
||||
const invoice = await this.validateInvoiceForApproval(invoiceId, userIpAndHeaders.userId, queryRunner);
|
||||
|
||||
const existCode = await this.otpService.checkExistOtp(user.phone, "INVOICE_VERIFY");
|
||||
if (existCode) {
|
||||
@@ -363,8 +378,11 @@ export class InvoicesService {
|
||||
invoiceId,
|
||||
`user approved invoice request: ${invoice.numericId}`,
|
||||
{
|
||||
user: { id: userId },
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
userType: UserType.USER,
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -384,7 +402,7 @@ export class InvoicesService {
|
||||
|
||||
//********************************** */
|
||||
|
||||
async approveInvoiceByUser(invoiceId: string, userId: string, verifyOtpDto: VerifyOtpWithUserId) {
|
||||
async approveInvoiceByUser(invoiceId: string, userIpAndHeaders: IUserIpAndHeaders, verifyOtpDto: VerifyOtpWithUserId) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
@@ -392,17 +410,17 @@ export class InvoicesService {
|
||||
await queryRunner.startTransaction();
|
||||
const { code } = verifyOtpDto;
|
||||
|
||||
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
|
||||
const user = await this.usersService.findOneByIdWithQueryRunner(userIpAndHeaders.userId, queryRunner);
|
||||
|
||||
const isValid = await this.otpService.verifyOtp(user.phone, code, "INVOICE_VERIFY");
|
||||
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
||||
|
||||
const invoice = await this.validateInvoiceForApproval(invoiceId, userId, queryRunner);
|
||||
const invoice = await this.validateInvoiceForApproval(invoiceId, userIpAndHeaders.userId, queryRunner);
|
||||
|
||||
invoice.status = InvoiceStatus.WAIT_PAYMENT;
|
||||
await queryRunner.manager.save(Invoice, invoice);
|
||||
|
||||
await this.notificationQueue.addApprovedInvoiceNotification(userId, {
|
||||
await this.notificationQueue.addApprovedInvoiceNotification(userIpAndHeaders.userId, {
|
||||
invoiceId: invoice.numericId.toString(),
|
||||
dueDate: invoice.dueDate,
|
||||
createDate: invoice.createdAt,
|
||||
@@ -420,7 +438,10 @@ export class InvoicesService {
|
||||
invoiceId,
|
||||
`user approved invoice by user: ${invoice.numericId}`,
|
||||
{
|
||||
user: { id: userId },
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
userType: UserType.USER,
|
||||
},
|
||||
);
|
||||
@@ -681,7 +702,12 @@ export class InvoicesService {
|
||||
|
||||
//*********************************** */
|
||||
|
||||
async payInvoice(invoiceId: string, userId: string, queryRunner = this.dataSource.createQueryRunner()) {
|
||||
async payInvoice(
|
||||
invoiceId: string,
|
||||
userId: string,
|
||||
userIpAndHeaders?: IUserIpAndHeaders,
|
||||
queryRunner = this.dataSource.createQueryRunner(),
|
||||
) {
|
||||
let transactionStarted = false;
|
||||
|
||||
try {
|
||||
@@ -784,10 +810,15 @@ export class InvoicesService {
|
||||
paidAt: invoice.paidAt,
|
||||
});
|
||||
|
||||
await this.accessLogService.logUserAction(OperationType.UPDATE, "Invoice", invoiceId, `user paid invoice: ${invoice.numericId}`, {
|
||||
user: { id: userId },
|
||||
userType: UserType.USER,
|
||||
});
|
||||
if (userIpAndHeaders) {
|
||||
await this.accessLogService.logUserAction(OperationType.UPDATE, "Invoice", invoiceId, `user paid invoice: ${invoice.numericId}`, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
userType: UserType.USER,
|
||||
});
|
||||
}
|
||||
|
||||
if (transactionStarted) await queryRunner.commitTransaction();
|
||||
|
||||
|
||||
@@ -480,6 +480,6 @@ export class InvoiceProcessor extends WorkerProcessor {
|
||||
|
||||
userSupportPlan.status = UserSupportPlanStatus.ACTIVE;
|
||||
await queryRunner.manager.save(UserSupportPlan, userSupportPlan);
|
||||
await this.invoicesService.payInvoice(newInvoice.id, invoice.user.id, queryRunner);
|
||||
await this.invoicesService.payInvoice(newInvoice.id, invoice.user.id, undefined, queryRunner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ export class SubscriptionsService {
|
||||
await this.addProvisioningJob(userSubscription, plan, user);
|
||||
|
||||
if (isFree) {
|
||||
await this.invoicesService.payInvoice(invoice.id, user.id, queryRunner);
|
||||
await this.invoicesService.payInvoice(invoice.id, user.id, undefined, queryRunner);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
@@ -156,7 +156,7 @@ export class SupportPlansService {
|
||||
if (supportPlan.isFree) {
|
||||
userSupportPlan.status = UserSupportPlanStatus.ACTIVE;
|
||||
await queryRunner.manager.save(this.userSupportPlanRepo.target, userSupportPlan);
|
||||
await this.invoicesService.payInvoice(invoice.id, user.id, queryRunner);
|
||||
await this.invoicesService.payInvoice(invoice.id, user.id, undefined, queryRunner);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
@@ -256,7 +256,7 @@ export class SupportPlansService {
|
||||
if (newSupportPlan.isFree) {
|
||||
currentUserSupportPlan.status = UserSupportPlanStatus.ACTIVE;
|
||||
await queryRunner.manager.save(this.userSupportPlanRepo.target, currentUserSupportPlan);
|
||||
await this.invoicesService.payInvoice(invoice.id, user.id, queryRunner);
|
||||
await this.invoicesService.payInvoice(invoice.id, user.id, undefined, queryRunner);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
@@ -2,6 +2,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import slugify from "slugify";
|
||||
import { DataSource, In, Not, QueryRunner } from "typeorm";
|
||||
|
||||
import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator";
|
||||
import { AdminMessage, AdsMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||
import { UserType } from "../../access-logs/enums/user-type.enum";
|
||||
import { AccessLogService } from "../../access-logs/providers/access-log.service";
|
||||
@@ -28,7 +29,7 @@ export class AdminsService {
|
||||
private readonly accessLogService: AccessLogService,
|
||||
) {}
|
||||
|
||||
async createAdmin(createDto: CreateAdminDto, userId: string) {
|
||||
async createAdmin(createDto: CreateAdminDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
@@ -65,9 +66,12 @@ export class AdminsService {
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// Log the admin creation
|
||||
await this.accessLogService.logCreate("User", adminUser.id, createDto, {
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
await this.accessLogService.logCreate("User", UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/admins",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created new admin: ${createDto.email}`,
|
||||
metadata: {
|
||||
roleId: createDto.roleId,
|
||||
@@ -90,7 +94,7 @@ export class AdminsService {
|
||||
}
|
||||
/************************************************************ */
|
||||
|
||||
async updateAdmin(id: string, updateDto: UpdateAdminDto, userId: string) {
|
||||
async updateAdmin(id: string, updateDto: UpdateAdminDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
@@ -136,18 +140,20 @@ export class AdminsService {
|
||||
await queryRunner.manager.save(this.userRepository.target, { ...admin, ...updateDto, password: admin.password });
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.accessLogService.logUpdate(
|
||||
"User",
|
||||
admin.id,
|
||||
{ ...admin },
|
||||
{ ...admin },
|
||||
{
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
actionDescription: `Admin updated admin: ${admin.email}`,
|
||||
await this.accessLogService.logUpdate("User", admin.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/admins",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
userType: UserType.ADMIN,
|
||||
actionDescription: `Admin updated admin: ${admin.email}`,
|
||||
metadata: {
|
||||
roleId: updateDto.roleId,
|
||||
},
|
||||
);
|
||||
oldValues: JSON.stringify({ ...admin }),
|
||||
newValues: JSON.stringify({ ...updateDto }),
|
||||
});
|
||||
|
||||
return {
|
||||
message: AdminMessage.ADMIN_UPDATED,
|
||||
@@ -161,22 +167,25 @@ export class AdminsService {
|
||||
}
|
||||
/************************************************************ */
|
||||
|
||||
async deleteAdmin(id: string, userId: string) {
|
||||
async deleteAdmin(id: string, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const admin = await this.userRepository.findOne({ where: { id, roles: { isAdmin: true } } });
|
||||
if (!admin) throw new BadRequestException(AdminMessage.ADMIN_NOT_FOUND);
|
||||
|
||||
await this.userRepository.softDelete(id);
|
||||
|
||||
await this.accessLogService.logDelete(
|
||||
"User",
|
||||
admin.id,
|
||||
{ ...admin },
|
||||
{
|
||||
user: { id: userId },
|
||||
userType: UserType.ADMIN,
|
||||
actionDescription: `Admin deleted admin: ${admin.email}`,
|
||||
await this.accessLogService.logDelete("User", admin.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/admins",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin deleted admin: ${admin.email}`,
|
||||
metadata: {
|
||||
roleId: admin.roles[0].id,
|
||||
email: admin.email,
|
||||
phone: admin.phone,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
message: AdminMessage.ADMIN_DELETED,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||
import { DataSource, Not } from "typeorm";
|
||||
|
||||
import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator";
|
||||
import { CommonMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||
import { UserType } from "../../access-logs/enums/user-type.enum";
|
||||
import { AccessLogService } from "../../access-logs/providers/access-log.service";
|
||||
@@ -37,7 +38,7 @@ export class CustomersService {
|
||||
private readonly accessLogService: AccessLogService,
|
||||
) {}
|
||||
|
||||
async createCustomer(createDto: CreateCustomerDto) {
|
||||
async createCustomer(createDto: CreateCustomerDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
@@ -73,11 +74,14 @@ export class CustomersService {
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// Log the customer creation
|
||||
await this.accessLogService.logCreate("User", user.id, createDto, {
|
||||
userType: UserType.ADMIN,
|
||||
await this.accessLogService.logCreate("User", UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/customers",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created customer: ${createDto.email}`,
|
||||
metadata: {
|
||||
createdBy: "admin",
|
||||
email: createDto.email,
|
||||
phone: createDto.phone,
|
||||
},
|
||||
@@ -93,7 +97,7 @@ export class CustomersService {
|
||||
}
|
||||
}
|
||||
//************************************************ */
|
||||
async updateCustomer(customerId: string, updateDto: UpdateCustomerDto) {
|
||||
async updateCustomer(customerId: string, updateDto: UpdateCustomerDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
@@ -241,6 +245,21 @@ export class CustomersService {
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.accessLogService.logUpdate("User", customerId, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/customers",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin updated customer: ${customerId}`,
|
||||
metadata: {
|
||||
email: email,
|
||||
phone: phone,
|
||||
},
|
||||
oldValues: JSON.stringify({ ...user }),
|
||||
newValues: JSON.stringify({ ...updateDto }),
|
||||
});
|
||||
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
};
|
||||
|
||||
@@ -7,8 +7,6 @@ import slugify from "slugify";
|
||||
import { In, Not, QueryRunner } from "typeorm";
|
||||
|
||||
import { AuthMessage, CommonMessage, 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 { AddressService } from "../../address/providers/address.service";
|
||||
import { RequestOtpDto } from "../../auth/DTO/request-otp.dto";
|
||||
import { VerifyOtpDto } from "../../auth/DTO/verify-otp.dto";
|
||||
@@ -49,7 +47,6 @@ export class UsersService {
|
||||
private readonly smsService: SmsService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly referralsService: ReferralsService,
|
||||
private readonly accessLogService: AccessLogService,
|
||||
) {}
|
||||
|
||||
/************************************************************ */
|
||||
@@ -117,8 +114,6 @@ export class UsersService {
|
||||
const user = await this.userRepository.findOneBy({ id: userId });
|
||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||
|
||||
const oldUserData = { ...user };
|
||||
|
||||
if (updateProfileDto.cityId) {
|
||||
const { city: existCity } = await this.addressService.getCity(+updateProfileDto.cityId);
|
||||
if (!existCity) throw new BadRequestException(UserMessage.CITY_NOT_FOUND);
|
||||
@@ -132,15 +127,7 @@ export class UsersService {
|
||||
if (existUserName) throw new BadRequestException(UserMessage.USERNAME_EXIST);
|
||||
}
|
||||
|
||||
const updatedUser = await this.userRepository.save({ ...user, ...updateProfileDto, city });
|
||||
|
||||
// Log the profile update
|
||||
await this.accessLogService.logUpdate("User", userId, oldUserData, updatedUser, {
|
||||
user: { id: userId },
|
||||
userType: UserType.USER,
|
||||
actionDescription: `User updated profile: ${user.email}`,
|
||||
metadata: { updatedFields: Object.keys(updateProfileDto) },
|
||||
});
|
||||
await this.userRepository.save({ ...user, ...updateProfileDto, city });
|
||||
|
||||
return {
|
||||
message: CommonMessage.UPDATE_SUCCESS,
|
||||
|
||||
@@ -25,7 +25,7 @@ import { AdminRoute } from "../../common/decorators/admin.decorator";
|
||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
import { PermissionsDec } from "../../common/decorators/permission.decorator";
|
||||
import { SkipAuth } from "../../common/decorators/skip-auth.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 { RequestOtpDto } from "../auth/DTO/request-otp.dto";
|
||||
import { VerifyOtpDto } from "../auth/DTO/verify-otp.dto";
|
||||
@@ -136,14 +136,18 @@ export class UsersController {
|
||||
@ApiOperation({ summary: "Create customer" })
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@Post("customers")
|
||||
createCustomer(@Body() createDto: CreateCustomerDto) {
|
||||
return this.customersService.createCustomer(createDto);
|
||||
createCustomer(@Body() createDto: CreateCustomerDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.customersService.createCustomer(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Update customer" })
|
||||
@Patch("customers/:id")
|
||||
updateCustomer(@Param() paramDto: ParamDto, @Body() updateDto: UpdateCustomerDto) {
|
||||
return this.customersService.updateCustomer(paramDto.id, updateDto);
|
||||
updateCustomer(
|
||||
@Param() paramDto: ParamDto,
|
||||
@Body() updateDto: UpdateCustomerDto,
|
||||
@UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders,
|
||||
) {
|
||||
return this.customersService.updateCustomer(paramDto.id, updateDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get customers ==> admin route" })
|
||||
@@ -180,22 +184,22 @@ export class UsersController {
|
||||
@ApiOperation({ summary: "update admin ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
|
||||
@Patch("admins/:id")
|
||||
updateAdmin(@Param() paramDto: ParamDto, @Body() updateDto: UpdateAdminDto, @UserDec("id") userId: string) {
|
||||
return this.adminsService.updateAdmin(paramDto.id, updateDto, userId);
|
||||
updateAdmin(@Param() paramDto: ParamDto, @Body() updateDto: UpdateAdminDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.adminsService.updateAdmin(paramDto.id, updateDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "delete admin ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
|
||||
@Delete("admins/:id")
|
||||
deleteAdmin(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||
return this.adminsService.deleteAdmin(paramDto.id, userId);
|
||||
deleteAdmin(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.adminsService.deleteAdmin(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "create admin ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
|
||||
@Post("admins")
|
||||
createAdmin(@Body() createDto: CreateAdminDto, @UserDec("id") userId: string) {
|
||||
return this.adminsService.createAdmin(createDto, userId);
|
||||
createAdmin(@Body() createDto: CreateAdminDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.adminsService.createAdmin(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get admin permissions" })
|
||||
|
||||
Reference in New Issue
Block a user