This commit is contained in:
mahyargdz
2025-09-14 15:15:03 +03:30
commit be82059172
534 changed files with 51310 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
import { interfaces } from "inversify-express-utils";
const USER_KEY = "custom:user";
// Middleware to resolve parameters for custom decorators
export function resolveCustomDecorators(httpContext: interfaces.HttpContext, target: any, propertyKey: string | symbol, args: any[]) {
const metadata = Reflect.getMetadata(USER_KEY, target, propertyKey) || [];
for (const { parameterIndex, callback } of metadata) {
args[parameterIndex] = callback(httpContext);
}
}
export function createParamDecorator(callback: (httpContext: interfaces.HttpContext) => unknown): ParameterDecorator {
return (target, propertyKey, parameterIndex) => {
const existingMetadata = Reflect.getMetadata(USER_KEY, target, propertyKey as any) || [];
existingMetadata.push({ parameterIndex, callback });
Reflect.defineMetadata(USER_KEY, existingMetadata, target, propertyKey as any);
};
}
+91
View File
@@ -0,0 +1,91 @@
import "reflect-metadata";
// ApiOperation decorator
export function ApiOperation(summary: string): MethodDecorator {
return function (target, propertyKey) {
Reflect.defineMetadata("api:operation:summary", summary, target, propertyKey);
};
}
// ApiResponse decorator
export function ApiResponse(description: string, status: number = 200, example?: any): MethodDecorator {
return function (target, propertyKey) {
const existingResponses = Reflect.getMetadata("api:responses", target, propertyKey) || [];
existingResponses.push({ description, status, example });
Reflect.defineMetadata("api:responses", existingResponses, target, propertyKey);
};
}
// ApiTags decorator
export function ApiTags(...tags: string[]): ClassDecorator {
return function (target) {
Reflect.defineMetadata("api:tags", tags, target);
};
}
// ApiParam decorator
export function ApiParam(name: string, description: string, required: boolean = false): MethodDecorator {
return function (target, propertyKey) {
const existingParams = Reflect.getMetadata("api:params", target, propertyKey) || [];
existingParams.push({ name, description, required });
Reflect.defineMetadata("api:params", existingParams, target, propertyKey);
};
}
// ApiQuery decorator
export function ApiQuery(
name: string,
description: string,
required: boolean = false,
type: "string" | "array" = "string",
): MethodDecorator {
return function (target, propertyKey) {
const existingQueries = Reflect.getMetadata("api:queries", target, propertyKey) || [];
existingQueries.push({ name, description, required, type });
Reflect.defineMetadata("api:queries", existingQueries, target, propertyKey);
};
}
// ApiBody decorator
export function ApiBody(description: string): MethodDecorator {
return function (target, propertyKey) {
Reflect.defineMetadata("api:body", description, target, propertyKey);
};
}
export function ApiModel(dto: any): MethodDecorator {
return function (target, propertyKey) {
Reflect.defineMetadata("api:body:dto", dto, target, propertyKey);
};
}
export interface ApiPropertyOptions {
type: string;
description?: string;
example?: any;
required?: boolean;
}
export function ApiProperty(options: ApiPropertyOptions): PropertyDecorator {
return function (target, propertyKey) {
Reflect.defineMetadata("api:property", options, target, propertyKey);
};
}
export function ApiAuth(): MethodDecorator {
return function (target, propertyKey) {
Reflect.defineMetadata("api:auth", true, target, propertyKey);
};
}
export function ApiFile(description: string = "file", required: boolean = true, multiple: boolean = false): MethodDecorator {
return function (target, propertyKey) {
Reflect.defineMetadata("api:file", { description, required, multiple }, target, propertyKey);
};
}
export function AdminRoute(): MethodDecorator {
return function (target, propertyKey) {
Reflect.defineMetadata("api:isAdmin", true, target, propertyKey);
};
}
+9
View File
@@ -0,0 +1,9 @@
import "reflect-metadata";
import { HttpContext } from "inversify-express-utils";
import { createParamDecorator } from "./param.decorator";
// export function User(): ParameterDecorator {
// return createParamDecorator((httpContext: interfaces.HttpContext) => httpContext.user);
// }
export const User = () => createParamDecorator((ctx: HttpContext) => ctx.request.user);
@@ -0,0 +1,128 @@
import { ValidationArguments, ValidationOptions, registerDecorator } from "class-validator";
import moment from "jalali-moment";
import { Model, isValidObjectId } from "mongoose";
import { CommonMessage } from "../enums/message.enum";
// @ValidatorConstraint({ async: true })
// export class IsTypeValidConstraint implements ValidatorConstraintInterface {
// // eslint-disable-next-line no-unused-vars
// async validate(type: string, _args: ValidationArguments) {
// return type === process.env.USER_TYPE || type === process.env.SELLER_TYPE;
// }
// // eslint-disable-next-line no-unused-vars
// defaultMessage(_args: ValidationArguments) {
// return AuthMessage.TypeError;
// }
// }
// export function IsTypeValid(validationOptions?: ValidationOptions) {
// return function (object: object, propertyName: string) {
// registerDecorator({
// target: object.constructor,
// propertyName: propertyName,
// options: validationOptions,
// constraints: [],
// validator: IsTypeValidConstraint,
// });
// };
// }
// export function IsPhoneExists<T>(model: Model<T>, userId?: string, validationOptions?: ValidationOptions) {
// return function (object: object, propertyName: string) {
// registerDecorator({
// name: "IsPhoneExists",
// target: object.constructor,
// propertyName: propertyName,
// options: validationOptions,
// constraints: [userId],
// validator: {
// async validate(phoneNumber: string, args: ValidationArguments) {
// const [userId] = args.constraints;
// const user = await model.exists({ phoneNumber, _id: { $ne: userId } });
// return !user; // if user exists with a different ID, return false (validation fails)
// },
// // eslint-disable-next-line no-unused-vars
// defaultMessage(_args: ValidationArguments) {
// return AuthMessage.NumberExists;
// },
// },
// });
// };
// }
// export function IsEmailExists<T>(model: Model<T>, userId?: string, validationOptions?: ValidationOptions) {
// return function (object: object, propertyName: string) {
// registerDecorator({
// name: "IsEmailExists",
// target: object.constructor,
// propertyName: propertyName,
// options: validationOptions,
// constraints: [userId],
// validator: {
// async validate(email: string, args: ValidationArguments) {
// const [userId] = args.constraints;
// const user = await model.exists({ email, _id: { $ne: userId } });
// return !user; // if user exists with a different ID, return false (validation fails)
// },
// // eslint-disable-next-line no-unused-vars
// defaultMessage(_args: ValidationArguments) {
// return AuthMessage.EmailExists;
// },
// },
// });
// };
// }
export function IsValidId(model: Model<any> | Array<Model<any>>, validationOptions?: ValidationOptions) {
return function (object: object, propertyName: string) {
registerDecorator({
name: "IsValidId",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [model],
validator: {
// eslint-disable-next-line no-unused-vars
async validate(id: string | number, _args: ValidationArguments) {
if (typeof id === "string") {
if (!isValidObjectId(id)) return false;
}
// Normalize to an array for uniform processing
const models = Array.isArray(model) ? model : [model];
// Check the ID in each model
for (const m of models) {
const existId = await m.exists({ _id: id });
if (existId) return true; // ID exists in one of the models
}
return false; // ID does not exist in any of the models
},
defaultMessage(args: ValidationArguments) {
return `${CommonMessage.NotValidId} ${args.property}`;
},
},
});
};
}
export function IsValidPersianDate(validationOptions?: ValidationOptions) {
return function (object: object, propertyName: string) {
registerDecorator({
name: "IsValidPersianDate",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: {
validate(date: string) {
return moment(date, "jYYYY/jMM/jDD", true).isValid();
},
defaultMessage() {
return "Invalid Persian date format. Expected format is YYYY/MM/DD.";
},
},
});
};
}