first
This commit is contained in:
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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.";
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user