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
@@ -0,0 +1,84 @@
import { Expose } from "class-transformer";
import { IsArray, IsBoolean, IsMongoId, IsNotEmpty, IsNumber, IsOptional, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidPersianDate } from "../../../common/decorator/validation.decorator";
export class CreateCouponDTO {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 10, description: "Discount percentage for the coupon" })
discountPercentage?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 10000, description: "Flat discount amount" })
discountAmount?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 5, description: "Maximum number of uses for the coupon" })
usageLimit: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 5, description: "Maximum number of uses for the coupon per user" })
userUsageLimit: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 100000, description: "Minimum purchase amount to use the coupon in toman" })
minPurchaseAmount: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsArray()
@IsMongoId({ each: true })
@ApiProperty({ type: "Array", example: ["60d21b4967d0d8992e610c85"], description: "categories for the coupon" })
category?: string[];
@Expose()
@IsOptional()
@IsNotEmpty()
@IsArray()
@ApiProperty({ type: "Array", example: [100001, 100102], description: "List of included product IDs" })
includedProducts?: number[];
@Expose()
@IsOptional()
@IsNotEmpty()
@IsArray()
@ApiProperty({ type: "Array", example: [100001, 100102], description: "List of excluded product IDs" })
excludedProducts?: number[];
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", example: "کد تخفیف", description: "Description of the coupon" })
description: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ type: "boolean", example: false, description: "Whether the coupon applies to special sales" })
includeSpecialSale: boolean;
@Expose()
@IsNotEmpty()
@IsValidPersianDate()
@ApiProperty({ type: "string", example: "1401/06/05", description: "Coupon expiration date in jalali format" })
expirationDate: string;
}
@@ -0,0 +1,63 @@
import { Expose } from "class-transformer";
import { IsArray, IsMongoId, IsNotEmpty, IsNumber, IsOptional } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
export class UpdateCouponDTO {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 10, description: "Discount percentage for the coupon" })
discountPercentage?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 10000, description: "Flat discount amount" })
discountAmount?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 5, description: "Maximum number of uses for the coupon" })
usageLimit?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 5, description: "Maximum number of uses for the coupon per user" })
userUsageLimit?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", example: 100000, description: "Minimum purchase amount to use the coupon in toman" })
minPurchaseAmount?: number;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsArray()
@IsMongoId({ each: true })
@ApiProperty({ type: "Array", example: ["60d21b4967d0d8992e610c85"], description: "categories for the coupon" })
category?: string[];
@Expose()
@IsOptional()
@IsNotEmpty()
@IsArray()
@ApiProperty({ type: "Array", example: [100001, 100102], description: "List of included product IDs" })
includedProducts?: number[];
@Expose()
@IsOptional()
@IsNotEmpty()
@IsArray()
@ApiProperty({ type: "Array", example: [100001, 100102], description: "List of excluded product IDs" })
excludedProducts?: number[];
}
@@ -0,0 +1,20 @@
import { Expose } from "class-transformer";
import { IsNotEmpty, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { CartShipmentItemModel } from "../../cart/models/cartShipmentItem.model";
export class ValidateCouponDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", example: "DISCOUNT2024" })
code: string;
@Expose()
@IsNotEmpty()
@IsValidId(CartShipmentItemModel)
@ApiProperty({ type: "string", example: "5f7b1b1b1b1b1b1b1b1b1b1b1", description: "cartShipment item id" })
cartShipmentItemId: string;
}
+96
View File
@@ -0,0 +1,96 @@
import { Request } from "express";
import rateLimit from "express-rate-limit";
import { inject } from "inversify";
import { controller, httpGet, httpPatch, httpPost, request, requestBody, requestParam } from "inversify-express-utils";
import { CouponService } from "./coupon.service";
import { HttpStatus } from "../../common";
import { CreateCouponDTO } from "./DTO/createCoupon.dto";
import { UpdateCouponDTO } from "./DTO/updateCoupon.dto";
import { ValidateCouponDTO } from "./DTO/validateCoupon.dto";
import { BaseController } from "../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
import { appConfig } from "../../core/config/app.config";
import { Guard } from "../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../IOC/ioc.types";
import { ISeller } from "../seller/models/Abstraction/ISeller";
import { OwnerRef } from "../shop/models/Abstraction/IShop";
import { IUser } from "../user/models/Abstraction/IUser";
@controller("/coupon")
@ApiTags("Coupon")
class CouponController extends BaseController {
@inject(IOCTYPES.CouponService) couponService: CouponService;
@ApiOperation("get all seller created coupon ==> login as seller")
@ApiResponse("successful")
@ApiAuth()
@httpGet("", Guard.authSeller())
public async getSellerCoupons(@request() req: Request) {
const seller = req.user as ISeller;
const data = await this.couponService.getSellerCoupons(seller._id.toString(), OwnerRef.SELLER);
return this.response(data);
}
@ApiOperation("get a single coupon")
@ApiParam("id", "coupon id", true)
@ApiAuth()
@httpGet("/:id", Guard.authSeller())
public async getSingleCoupon(@requestParam("id") couponId: string) {
const data = await this.couponService.getSingleCoupon(couponId);
return this.response(data);
}
@ApiOperation("create a Coupon ==> login as seller")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateCouponDTO)
@ApiAuth()
@httpPost("", rateLimit(appConfig.rate), Guard.authSeller(), ValidationMiddleware.validateInput(CreateCouponDTO))
public async createCoupon(@requestBody() createDto: CreateCouponDTO, @request() req: Request) {
const seller = req.user as ISeller;
const data = await this.couponService.createCoupon(createDto, seller._id.toString(), OwnerRef.SELLER);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("deactivate a coupon ==> login as seller")
@ApiResponse("successful")
@ApiAuth()
@ApiParam("id", "coupon id", true)
@httpPatch("/:id/status", Guard.authSeller())
public async changeCouponStatus(@request() req: Request, @requestParam("id") couponId: string) {
const seller = req.user as ISeller;
const data = await this.couponService.changeCouponStatus(seller._id.toString(), couponId, OwnerRef.SELLER);
return this.response(data);
}
@ApiOperation("update a seller coupon ==> login as seller")
@ApiResponse("successful")
@ApiModel(UpdateCouponDTO)
@ApiParam("id", "coupon id", true)
@ApiAuth()
@httpPatch("/:id", Guard.authSeller(), ValidationMiddleware.validateInput(UpdateCouponDTO))
public async updateSellerCoupon(
@requestBody() updateDto: UpdateCouponDTO,
@request() req: Request,
@requestParam("id") couponId: string,
) {
const seller = req.user as ISeller;
const data = await this.couponService.updateCoupon(updateDto, seller._id.toString(), couponId, OwnerRef.SELLER);
return this.response(data);
}
@ApiOperation("add a coupon to user cart ===> login as user")
@ApiResponse("successful")
@ApiResponse("bad request", HttpStatus.BadRequest)
@ApiModel(ValidateCouponDTO)
@ApiAuth()
@httpPost("/add-coupon", Guard.authUser(), ValidationMiddleware.validateInput(ValidateCouponDTO))
public async addCoupon(@requestBody() validateDto: ValidateCouponDTO, @request() req: Request) {
const user = req.user as IUser;
const data = await this.couponService.applyCouponDiscount(validateDto, user._id.toString());
return this.response(data);
}
}
export { CouponController };
+24
View File
@@ -0,0 +1,24 @@
import { ICoupon } from "./models/Abstraction/ICoupon";
import { ICouponUsage } from "./models/Abstraction/ICouponUsage";
import { CouponModel } from "./models/coupon.model";
import { CouponUsageModel } from "./models/couponUsage.model";
import { BaseRepository } from "../../common/base/repository";
export class CouponRepo extends BaseRepository<ICoupon> {
constructor() {
super(CouponModel);
}
}
export function createCouponRepo(): CouponRepo {
return new CouponRepo();
}
export class CouponUsageRepo extends BaseRepository<ICouponUsage> {
constructor() {
super(CouponUsageModel);
}
}
export function createCouponUsageRepo(): CouponUsageRepo {
return new CouponUsageRepo();
}
+287
View File
@@ -0,0 +1,287 @@
import { randomBytes } from "crypto";
import { inject, injectable } from "inversify";
import { ClientSession, isValidObjectId, startSession } from "mongoose";
import { CouponRepo, CouponUsageRepo } from "./coupon.repository";
import { CreateCouponDTO } from "./DTO/createCoupon.dto";
import { UpdateCouponDTO } from "./DTO/updateCoupon.dto";
import { ValidateCouponDTO } from "./DTO/validateCoupon.dto";
import { CartMessage, CommonMessage, CouponMessage, ShopMessage } from "../../common/enums/message.enum";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
import { TimeService } from "../../utils/time.service";
import { CartRepository, CartShipItemRepo } from "../cart/cart.repository";
import { CartService } from "../cart/cart.service";
import { ICartShipmentItem } from "../cart/models/Abstraction/ICartShipmentItem";
import { PaymentService } from "../payment/providers/payment.service";
import { OwnerRef } from "../shop/models/Abstraction/IShop";
import { ShopRepo } from "../shop/shop.repository";
@injectable()
class CouponService {
@inject(IOCTYPES.CouponRepo) couponRepo: CouponRepo;
@inject(IOCTYPES.CouponUsageRepo) couponUsageRepo: CouponUsageRepo;
@inject(IOCTYPES.PaymentService) paymentService: PaymentService;
@inject(IOCTYPES.CartRepository) cartRepo: CartRepository;
@inject(IOCTYPES.CartService) cartService: CartService;
@inject(IOCTYPES.ShopRepo) shopRepo: ShopRepo;
@inject(IOCTYPES.CartShipItemRepo) cartShipItemRepo: CartShipItemRepo;
async getSellersCoupons() {
const coupons = await this.couponRepo.model.aggregate([
{
$match: {
deleted: false,
},
},
{
$lookup: {
from: "shops",
localField: "shopId",
foreignField: "_id",
as: "shop",
},
},
{
$unwind: "$shop",
},
{
$match: {
"shop.ownerRef": OwnerRef.SELLER,
"shop.deleted": false,
},
},
{
$project: {
shop: 0,
},
},
]);
return { coupons };
}
async getSellerCoupons(ownerId: string, ownerRef: OwnerRef) {
const shop = await this.shopRepo.model.findOne({ owner: ownerId, ownerRef });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
return {
coupons: await this.couponRepo.model.find({ shopId: shop._id, deleted: false }),
};
}
async getSingleCoupon(couponId: string) {
if (!isValidObjectId(couponId)) throw new BadRequestError(CouponMessage.InvalidId);
const coupon = await this.couponRepo.model.findById(couponId);
if (!coupon) throw new BadRequestError(CouponMessage.InvalidId);
return { coupon };
}
//##############################
async createCoupon(createDto: CreateCouponDTO, ownerId: string, ownerRef: OwnerRef) {
const { discountAmount, discountPercentage, includedProducts, excludedProducts } = createDto;
if (!discountAmount && !discountPercentage) throw new BadRequestError(CouponMessage.DiscountAmountOrPercentageRequired);
if (discountAmount && discountPercentage) throw new BadRequestError(CouponMessage.BothDiscountOrPercentageShouldNotSend);
if (includedProducts && excludedProducts) throw new BadRequestError(CouponMessage.BothIncludedAndExcludedProductsShouldNotSend);
const shop = await this.shopRepo.model.findOne({ owner: ownerId, ownerRef });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const code = this.generateCouponCode();
const coupon = await this.couponRepo.model.create({
shopId: shop._id,
code,
...createDto,
});
return { coupon };
}
//##############################
async updateCoupon(updateDto: UpdateCouponDTO, ownerId: string, couponId: string, ownerRef: OwnerRef) {
if (!isValidObjectId(couponId)) throw new BadRequestError(CouponMessage.InvalidId);
const shop = await this.shopRepo.model.findOne({ owner: ownerId, ownerRef });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const { discountAmount, discountPercentage, includedProducts, excludedProducts } = updateDto;
if (!discountAmount && !discountPercentage) throw new BadRequestError(CouponMessage.DiscountAmountOrPercentageRequired);
if (discountAmount && discountPercentage) throw new BadRequestError(CouponMessage.BothDiscountOrPercentageShouldNotSend);
if (includedProducts && excludedProducts) throw new BadRequestError(CouponMessage.BothIncludedAndExcludedProductsShouldNotSend);
const updatedCoupon = await this.couponRepo.model.findOneAndUpdate(
{ _id: couponId, shopId: shop._id },
{ ...updateDto },
{ new: true },
);
if (!updatedCoupon) throw new BadRequestError(CouponMessage.InvalidId);
return {
message: CommonMessage.Updated,
coupon: updatedCoupon,
};
}
//##############################
async changeCouponStatus(ownerId: string, couponId: string, ownerRef: OwnerRef) {
if (!isValidObjectId(couponId)) throw new BadRequestError(CouponMessage.InvalidId);
const shop = await this.shopRepo.model.findOne({ owner: ownerId, ownerRef });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const coupon = await this.couponRepo.model.findOne({ _id: couponId, shopId: shop._id });
if (!coupon) throw new BadRequestError(CouponMessage.InvalidId);
const updatedCoupon = await this.couponRepo.model.findOneAndUpdate(
{ _id: couponId, shopId: shop._id },
{ isActive: !coupon.isActive },
{ new: true },
);
return {
message: CommonMessage.Updated,
coupon: updatedCoupon,
};
}
//##############################
async applyCouponDiscount(validateDto: ValidateCouponDTO, userId: string) {
const session = await startSession();
session.startTransaction();
try {
const { price, cartShipmentItems } = await this.paymentService.getPaymentInfoS(userId);
const userCart = await this.cartRepo.model.findOne({ user: userId }).session(session);
if (!userCart) throw new BadRequestError(CartMessage.CartNotFound);
// if (cart.coupon_discount) throw new BadRequestError(CouponMessage.CouponAlreadyInCart);
const cartShipmentItem = cartShipmentItems.find((item) => item._id.toString() === validateDto.cartShipmentItemId);
if (!cartShipmentItem) throw new BadRequestError(CartMessage.InvalidCartShipmentItem);
const validCartShipmentItem = await this.cartShipItemRepo.model.findById(cartShipmentItem._id).session(session);
if (!validCartShipmentItem) throw new BadRequestError(CartMessage.InvalidCartShipmentItem);
if (cartShipmentItem.totalCouponDiscount) throw new BadRequestError(CouponMessage.CouponAlreadyInCart);
const { total_payable_price } = price;
const { coupon } = await this.validateCoupon(validateDto.code, cartShipmentItem.totalPaymentPrice, userId, session);
let discount = 0;
if (coupon.discountAmount) {
discount = coupon.discountAmount;
} else if (coupon.discountPercentage) {
discount = (total_payable_price * coupon.discountPercentage) / 100;
}
if (coupon.category && coupon.category.length > 0) {
await this.checkCartItemCategory(
userId,
coupon.category.map((cat) => cat._id.toString()),
);
}
if (coupon.includedProducts && coupon.includedProducts.length > 0) {
await this.checkCartItemIncludedProducts(cartShipmentItem, coupon.includedProducts);
}
if (coupon.excludedProducts && coupon.excludedProducts.length > 0) {
await this.checkCartItemExcludedProducts(cartShipmentItem, coupon.excludedProducts);
}
await this.couponUsageRepo.model.findOneAndUpdate(
{ user: userId, coupon: coupon._id },
{ $inc: { usageCount: 1 } },
{ new: true, upsert: true, session },
);
cartShipmentItem.totalCouponDiscount = discount;
cartShipmentItem.totalPaymentPrice = cartShipmentItem.totalPaymentPrice - discount;
userCart.coupon_discount = discount;
coupon.usageCount += 1;
await validCartShipmentItem.save({ session });
await userCart.save({ session });
await coupon.save({ session });
await session.commitTransaction();
await session.endSession();
return { message: CouponMessage.CouponAdded, afterDiscount: total_payable_price - discount };
} catch (error) {
await session.abortTransaction();
await session.endSession();
throw error;
}
}
//================================> helper method
private generateCouponCode(length = 8) {
return randomBytes(length).toString("hex").slice(0, length).toUpperCase();
}
async checkCartItemExcludedProducts(shipmentItem: ICartShipmentItem, excludedProducts: number[]) {
const items = shipmentItem.shipmentItems.filter((item) => excludedProducts.includes(item.product));
console.log({ items });
if (items.length > 0) throw new BadRequestError(CouponMessage.InvalidProduct);
return true;
}
async checkCartItemIncludedProducts(shipmentItem: ICartShipmentItem, includedProducts: number[]) {
const items = shipmentItem.shipmentItems.filter((item) => includedProducts.includes(item.product));
if (!items.length) throw new BadRequestError(CouponMessage.InvalidProduct);
return true;
}
async checkCartItemCategory(userId: string, category: string[]) {
const { cart } = await this.cartService.getCartS(userId);
const items = cart.items.filter((item) => category.includes(item.product.category._id));
if (items.length === 0) throw new BadRequestError(CouponMessage.InvalidCategory);
return true;
}
async removeCouponByAdmin(couponId: string) {
const deletedCoupon = await this.couponRepo.model.findByIdAndUpdate(couponId, { deleted: true }, { new: true });
if (!deletedCoupon) throw new BadRequestError(CouponMessage.NotFound);
return { message: CommonMessage.Deleted };
}
//##############################
private async validateCoupon(couponCode: string, totalPrice: number, userId: string, session: ClientSession) {
const coupon = await this.couponRepo.model.findOne({ code: couponCode, isActive: true, deleted: false }).session(session);
if (!coupon) throw new BadRequestError(CouponMessage.NotFound);
const expired = TimeService.isPersianDateExpired(coupon.expirationDate);
if (expired) throw new BadRequestError(CouponMessage.Expired);
if (coupon.usageLimit && coupon.usageCount >= coupon.usageLimit) throw new BadRequestError(CouponMessage.UsageLimit);
const userUsage = await this.couponUsageRepo.model.findOne({ coupon: coupon._id, user: userId }).session(session);
if (userUsage && userUsage.usageCount >= coupon.userUsageLimit) throw new BadRequestError(CouponMessage.UserCouponUsageLimit);
if (coupon.minPurchaseAmount && totalPrice < coupon.minPurchaseAmount) {
throw new BadRequestError([
CouponMessage.AmountIsNotSatisfied,
`حداقل مبلغ سبد خرید برای استفاده از این کد تخفیف ${coupon.minPurchaseAmount}`,
]);
}
return { coupon };
}
}
export { CouponService };
@@ -0,0 +1,20 @@
import { Types } from "mongoose";
export interface ICoupon {
shopId: Types.ObjectId;
discountAmount: number;
discountPercentage: number;
usageLimit: number;
usageCount: number;
userUsageLimit: number;
code: string;
minPurchaseAmount: number;
category: Types.ObjectId[];
includedProducts: number[];
excludedProducts: number[];
description: string;
isActive: boolean;
includeSpecialSale: boolean;
expirationDate: string;
deleted: boolean;
}
@@ -0,0 +1,7 @@
import { Types } from "mongoose";
export interface ICouponUsage {
coupon: Types.ObjectId;
user: Types.ObjectId;
usageCount: number;
}
+29
View File
@@ -0,0 +1,29 @@
import { Schema, model } from "mongoose";
import { ICoupon } from "./Abstraction/ICoupon";
const CouponSchema = new Schema<ICoupon>(
{
shopId: { type: Schema.Types.ObjectId, ref: "Shop", required: true },
discountPercentage: { type: Number, default: 0 },
discountAmount: { type: Number, default: 0 },
usageLimit: { type: Number, default: null },
usageCount: { type: Number, default: 0 },
userUsageLimit: { type: Number, default: null },
code: { type: String, required: true, unique: true },
minPurchaseAmount: { type: Number, default: null },
category: { type: [Schema.Types.ObjectId], ref: "Category", default: [] },
includedProducts: { type: [Number], ref: "Product", default: [] },
excludedProducts: { type: [Number], ref: "Product", default: [] },
description: { type: String, default: null },
isActive: { type: Boolean, default: true },
includeSpecialSale: { type: Boolean, default: true },
expirationDate: { type: String, required: true },
deleted: { type: Boolean, default: false },
},
{ timestamps: true, toJSON: { versionKey: false }, id: false },
);
const CouponModel = model<ICoupon>("Coupon", CouponSchema);
export { CouponModel };
@@ -0,0 +1,16 @@
import { Schema, model } from "mongoose";
import { ICouponUsage } from "./Abstraction/ICouponUsage";
const CouponUsageSchema = new Schema<ICouponUsage>(
{
coupon: { type: Schema.Types.ObjectId, ref: "Coupon", required: true },
user: { type: Schema.Types.ObjectId, ref: "User", required: true },
usageCount: { type: Number, required: true },
},
{ timestamps: true, toJSON: { versionKey: false }, id: false },
);
CouponUsageSchema.index({ coupon: 1, user: 1 }, { unique: true });
const CouponUsageModel = model<ICouponUsage>("CouponUsage", CouponUsageSchema);
export { CouponUsageModel };
+52
View File
@@ -0,0 +1,52 @@
import { inject, injectable } from "inversify";
import { AsanPardakhtGateway } from "./gateways/asanpardakht";
import { ZarinPalGateway } from "./gateways/zarinpal";
import { IPaymentGateway, NewPaymentData, VerifyPaymentData } from "./interface/IPG";
import { GatewayProvider } from "../../common/enums/payment.enum";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
@injectable()
class PaymentGateway implements IPaymentGateway {
@inject(IOCTYPES.ZarinPalGateway) zarinPalGateway: ZarinPalGateway;
@inject(IOCTYPES.AsanPardakhtGateway) asanpardakhtGateway: AsanPardakhtGateway;
public async requestPayment(gatewayType: GatewayProvider, data: NewPaymentData): Promise<any> {
switch (gatewayType) {
case GatewayProvider.Zarinpal:
return await this.zarinPalGateway.processPayment(data.amount, data.description, data.email, data.mobile, data.callbackPath);
case GatewayProvider.Asanpardakht:
//TODO:fix this
return this.asanpardakhtGateway.token();
default:
throw new BadRequestError(`Payment provider ${gatewayType} is not supported`);
}
}
public async verifyPayment(gatewayType: GatewayProvider, data: VerifyPaymentData): Promise<any> {
switch (gatewayType) {
case GatewayProvider.Zarinpal:
return await this.zarinPalGateway.verifyPayment(data.authority, data.amount);
case GatewayProvider.Asanpardakht:
//TODO:fix this
return this.asanpardakhtGateway.time();
default:
throw new BadRequestError(`Payment provider ${gatewayType} is not supported`);
}
}
public async retryPayment(gatewayType: GatewayProvider, authority: string) {
switch (gatewayType) {
case GatewayProvider.Zarinpal:
return await this.zarinPalGateway.retryPayment(authority);
case GatewayProvider.Asanpardakht:
//TODO:fix this
return this.asanpardakhtGateway.token();
default:
throw new BadRequestError(`Payment provider ${gatewayType} is not supported`);
}
}
}
export { PaymentGateway };
+141
View File
@@ -0,0 +1,141 @@
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { injectable } from "inversify";
@injectable()
class AsanPardakhtGateway {
private invoiceId: string | null = null;
private amount: number | null = null;
private callBackURL = `${process.env.SITE_URL}/verify/ap`;
private readonly GatewayAuth = {
user: process.env.ASANPARDAKHT_USER,
password: process.env.ASANPARDAKHT_PASS,
merchant_id: process.env.ASANPARDAKHT_MERCHANT_ID,
merchantConfigID: process.env.ASANPARDAKHT_MERCHANT_CONFIG_ID,
};
// API endpoint URLs
private readonly TokenURL = "v1/Token";
private readonly TimeURL = "v1/Time";
private readonly TranResultURL = "v1/TranResult";
private readonly CardHashURL = "v1/CardHash";
private readonly SettlementURL = "v1/Settlement";
private readonly VerifyURL = "v1/Verify";
private readonly CancelURL = "v1/Cancel";
private readonly ReverseURL = "v1/Reverse";
private readonly GatewayApiURL = "https://ipgrest.asanpardakht.ir";
setInvoiceId(id: string): this {
this.invoiceId = id;
return this;
}
setAmount(amount: number): this {
this.amount = amount;
return this;
}
async time(): Promise<{ code: number; content: any }> {
return await this.callAPI("GET", this.TimeURL);
}
async TranResult(): Promise<{ code: number; content: any }> {
const res = await this.callAPI(
"GET",
`${this.TranResultURL}?${new URLSearchParams({
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
localInvoiceId: this.invoiceId!,
}).toString()}`,
);
return { code: res.code, content: JSON.parse(res.content) };
}
async CardHash(): Promise<{ code: number; content: any }> {
return await this.callAPI(
"GET",
`${this.CardHashURL}?${new URLSearchParams({
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
localInvoiceId: this.invoiceId!,
}).toString()}`,
);
}
async token(): Promise<{ code: number; content: any }> {
if (this.callBackURL) {
this.callBackURL += this.callBackURL.includes("?") ? "&" : "?";
}
const currentDate = new Date().toISOString().split("T").join(" ").split(".")[0].replace(/[-:]/g, "");
return await this.callAPI("POST", this.TokenURL, {
serviceTypeId: 1,
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
localInvoiceId: this.invoiceId!,
amountInRials: this.amount!,
localDate: currentDate,
callbackURL: `${this.callBackURL}${new URLSearchParams({ invoice: this.invoiceId! }).toString()}`,
paymentId: 0,
additionalData: "",
});
}
async settlement(transId: string): Promise<{ code: number; content: any }> {
return await this.callAPI("POST", this.SettlementURL, {
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
payGateTranId: transId,
});
}
async verify(transId: string): Promise<{ code: number; content: any }> {
return await this.callAPI("POST", this.VerifyURL, {
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
payGateTranId: transId,
});
}
async reverse(transId: string): Promise<{ code: number; content: any }> {
return await this.callAPI("POST", this.ReverseURL, {
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
payGateTranId: transId,
});
}
async cancel(transId: string): Promise<{ code: number; content: any }> {
return await this.callAPI("POST", this.CancelURL, {
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
payGateTranId: transId,
});
}
/**
* Internal method to call the API.
* @param method - The HTTP method to use.
* @param endpoint - The API endpoint to call.
* @param data - Optional data to send with the request.
* @returns A promise resolving to the API response.
*/
private async callAPI(method: "GET" | "POST", endpoint: string, data: any = null): Promise<{ code: number; content: any }> {
const url = `${this.GatewayApiURL}/${endpoint}`;
const options: AxiosRequestConfig = {
method,
url,
headers: {
Accept: "application/json",
Usr: this.GatewayAuth.user || "",
Pwd: this.GatewayAuth.password || "",
"Content-Type": "application/json",
},
data: data ? JSON.stringify(data) : null,
};
try {
const response: AxiosResponse = await axios(options);
return { content: response.data, code: response.status };
} catch (error: any) {
if (error.response) {
return { content: error.response.data, code: error.response.status };
} else {
return { content: error.message, code: 500 };
}
}
}
}
export { AsanPardakhtGateway };
+64
View File
@@ -0,0 +1,64 @@
import axios from "axios";
import { injectable } from "inversify";
import { ZarinPalPGNewArgs, ZarinPalPGNewRequestData, ZarinPalPGVerifyData } from "../../../common/types/paymentGateway.type";
import { InternalError } from "../../../core/app/app.errors";
import { Logger } from "../../../core/logging/logger";
@injectable()
class ZarinPalGateway {
private logger = new Logger(ZarinPalGateway.name);
private gatewayApiUrl: string = `https://${process.env.IPG_TYPE}.zarinpal.com/pg`; //or api subdomain
private callBackURL = `${process.env.SITE_URL}/verify/Zarinpal/`;
private MERCHANT_ID: string = process.env.ZARINPAL_MERCHANT_ID;
private requestHeader = { "Content-Type": "application/json", Accept: "application/json" };
async processPayment(amount: number, description: string, email: string, mobile: string, callBackPath: string) {
try {
const purchaseData: ZarinPalPGNewArgs = {
merchant_id: this.MERCHANT_ID,
amount,
callback_url: `${this.callBackURL}${callBackPath}`,
description,
currency: "IRT" as const,
metadata: { email, mobile },
};
const response = await axios.post(`${this.gatewayApiUrl}/v4/payment/request.json`, purchaseData, { headers: this.requestHeader });
const { data } = response.data as ZarinPalPGNewRequestData;
return {
redirectUrl: `${this.gatewayApiUrl}/StartPay/${data.authority}`,
message: data.message,
authority: data.authority,
};
} catch (error) {
this.logger.error("error in payment process", error);
throw new InternalError(["error processing payment"]);
}
}
async verifyPayment(authority: string, amount: number) {
try {
//TODO:check if amount should be in toman or riyal
const verifyData = { authority, amount, merchant_id: this.MERCHANT_ID };
const response = await axios.post(`${this.gatewayApiUrl}/v4/payment/verify.json`, verifyData, { headers: this.requestHeader });
const { data } = response.data as ZarinPalPGVerifyData;
return data;
} catch (error) {
this.logger.error("error in payment verification", error);
throw new InternalError(["error in payment verification"]);
}
}
async retryPayment(authority: string) {
return {
redirectUrl: `${this.gatewayApiUrl}/StartPay/${authority}`,
authority: authority,
};
}
}
export { ZarinPalGateway };
+37
View File
@@ -0,0 +1,37 @@
import { GatewayProvider } from "../../../common/enums/payment.enum";
export interface IPaymentGateway {
/**
*
* @param gatewayType
* @param data
*/
requestPayment(gatewayType: GatewayProvider, data: NewPaymentData): Promise<any>;
/**
*
* @param gatewayType
* @param data
*/
verifyPayment(gatewayType: GatewayProvider, data: VerifyPaymentData): Promise<any>;
/**
*
* @param gatewayType
* @param authority
*/
retryPayment(gatewayType: GatewayProvider, authority: string): Promise<any>;
}
export interface NewPaymentData {
amount: number;
description: string;
email: string;
mobile: string;
callbackPath: string;
}
export interface VerifyPaymentData {
authority: string;
amount: number;
}
@@ -0,0 +1,34 @@
import { Expose, plainToClass } from "class-transformer";
import { IsNotEmpty, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IAboutUs } from "../model/Abstractions/IAboutUs";
export class CreateAboutUsDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "title of about us", example: "عنوان ۱" })
title: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "description of about us", example: "توضیحات درباره ما" })
description: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "image url of about us", example: "https://loremflickr.com/454/340" })
imageUrl: string;
public static transformAboutUs(data: IAboutUs): CreateAboutUsDTO {
const AboutUsDto = plainToClass(CreateAboutUsDTO, data, {
excludeExtraneousValues: true,
enableImplicitConversion: true,
});
return AboutUsDto;
}
}
@@ -0,0 +1,22 @@
import { inject } from "inversify";
import { controller, httpGet } from "inversify-express-utils";
import { AboutUsService } from "./aboutUs.service";
import { HttpStatus } from "../../common";
import { BaseController } from "../../common/base/controller";
import { ApiOperation, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
import { IOCTYPES } from "../../IOC/ioc.types";
@controller("/about-us")
@ApiTags("AboutUs")
export class AboutUsController extends BaseController {
@inject(IOCTYPES.AboutUsService) aboutUsService: AboutUsService;
@ApiOperation("get a list of about us")
@ApiResponse("successful", HttpStatus.Ok)
@httpGet("")
public async getAllAboutUs() {
const data = await this.aboutUsService.getAllAboutUs();
return this.response(data, HttpStatus.Ok);
}
}
@@ -0,0 +1,13 @@
import { AboutUsModel } from "./model/aboutUs.model";
import { IAboutUs } from "./model/Abstractions/IAboutUs";
import { BaseRepository } from "../../common/base/repository";
export class AboutUsRepo extends BaseRepository<IAboutUs> {
constructor() {
super(AboutUsModel);
}
}
export function CreateAboutUsRepo(): AboutUsRepo {
return new AboutUsRepo();
}
+45
View File
@@ -0,0 +1,45 @@
import { inject, injectable } from "inversify";
import { isValidObjectId } from "mongoose";
import { AboutUsRepo } from "./aboutUs.repository";
import { CreateAboutUsDTO } from "./DTO/createAboutUsRequest.dto";
import { CommonMessage } from "../../common/enums/message.enum";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
@injectable()
export class AboutUsService {
@inject(IOCTYPES.AboutUsRepo) aboutUsRepo: AboutUsRepo;
async createAboutUs(createDto: CreateAboutUsDTO) {
await this.aboutUsRepo.model.create({
...createDto,
});
return {
message: CommonMessage.Created,
};
}
async getAllAboutUs() {
const aboutUs = await this.aboutUsRepo.model.find();
return { aboutUs };
}
async getById(aboutId: string) {
if (!isValidObjectId(aboutId)) throw new BadRequestError(CommonMessage.NotValidId);
const aboutUs = await this.aboutUsRepo.model.findById(aboutId);
if (!aboutUs) throw new BadRequestError(CommonMessage.NotFoundById);
return {
aboutUs,
};
}
async delete(aboutId: string) {
if (!isValidObjectId(aboutId)) throw new BadRequestError(CommonMessage.NotValidId);
await this.aboutUsRepo.delete(aboutId);
return {
message: CommonMessage.Deleted,
};
}
}
@@ -0,0 +1,5 @@
export interface IAboutUs {
title: string;
description: string;
imageUrl: string;
}
@@ -0,0 +1,19 @@
import { Schema, model } from "mongoose";
import { IAboutUs } from "./Abstractions/IAboutUs";
const AboutUsSchema = new Schema<IAboutUs>(
{
title: { type: String, required: true },
description: { type: String, required: true },
imageUrl: { type: String, required: true },
},
{
timestamps: true,
toJSON: { versionKey: false, virtuals: true },
id: false,
},
);
const AboutUsModel = model<IAboutUs>("AboutUs", AboutUsSchema);
export { AboutUsModel };
+59
View File
@@ -0,0 +1,59 @@
import { Expose, Type, plainToInstance } from "class-transformer";
import { IAddress } from "../models/Abstraction/IAddress";
export class CityDTO {
@Expose()
_id: number;
@Expose()
name: string;
@Expose()
province: number;
}
export class ProvinceDTO {
@Expose()
_id: number;
@Expose()
name: string;
}
export class AddressDTO {
@Expose()
_id: string;
@Expose()
address: string;
@Expose()
@Type(() => CityDTO)
city: CityDTO;
@Expose()
@Type(() => ProvinceDTO)
province: ProvinceDTO;
@Expose()
postalCode: string;
@Expose()
plaque: string;
@Expose()
lat: string;
@Expose()
lon: string;
public static transformAddress(data: IAddress): AddressDTO {
const addressDTO = plainToInstance(AddressDTO, data, {
excludeExtraneousValues: true,
enableImplicitConversion: true,
});
return addressDTO;
}
}
@@ -0,0 +1,99 @@
import { Expose } from "class-transformer";
import { IsNotEmpty, IsNumber, IsNumberString, IsOptional, IsString, Length } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { CommonMessage } from "../../../common/enums/message.enum";
export class SaveUserAddressDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "province", example: "34.406485" })
lat: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "province", example: "49.159527" })
lon: string;
@Expose()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", description: "province", example: 25 })
provinceId: number;
@Expose()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", description: "city", example: 569 })
cityId: number;
@Expose()
@IsNotEmpty()
@IsNumberString({ no_symbols: true }, { message: CommonMessage.PostalIncorrect })
@Length(10, 10, { message: CommonMessage.PostalIncorrect })
@ApiProperty({ type: "string", description: "iran postal code format (10 char)", example: "5869568592" })
postalCode: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "address of user ", example: "خ ملک" })
address: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "plaque ", example: "25" })
plaque: string;
}
export class SaveSellerAddressDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "province", example: "34.406485" })
lat: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "province", example: "49.159527" })
lon: string;
@Expose()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", description: "province", example: 25 })
provinceId: number;
@Expose()
@IsNotEmpty()
@IsNumber()
@ApiProperty({ type: "number", description: "city", example: 569 })
cityId: number;
// @Expose()
// @IsNotEmpty()
// @IsNumberString({ no_symbols: true }, { message: CommonMessage.PostalIncorrect })
// @Length(10, 10, { message: CommonMessage.PostalIncorrect })
// @ApiProperty({ type: "string", description: "iran postal code format (10 char)", example: "5869568592" })
// postalCode: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "address of user ", example: "خ ملک" })
address: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "plaque ", example: "25" })
plaque: string;
}
+54
View File
@@ -0,0 +1,54 @@
import { Request } from "express";
import { inject } from "inversify";
import { controller, httpGet, httpPost, queryParam, request, requestBody } from "inversify-express-utils";
import { AddressService } from "./address.service";
import { HttpStatus } from "../../common";
import { SaveSellerAddressDTO, SaveUserAddressDTO } from "./DTO/userAddress.dto";
import { BaseController } from "../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiQuery, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
import { Guard } from "../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../IOC/ioc.types";
import { ISeller } from "../seller/models/Abstraction/ISeller";
import { IUser } from "../user/models/Abstraction/IUser";
@controller("/address")
@ApiTags("Address")
class AddressController extends BaseController {
@inject(IOCTYPES.AddressService) addressService: AddressService;
@ApiOperation("get user location with geo coordinate")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("lat", "latitude", true)
@ApiQuery("lon", "longitude", true)
@httpGet("/location/reverse")
public async getLocationWithGeo(@queryParam("lat") lat: string, @queryParam("lon") lon: string) {
const data = await this.addressService.getLocationWithGeo({ lat, lon });
return this.response(data);
}
@ApiOperation("set user address of current session user ==> login as user")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(SaveUserAddressDTO)
@ApiAuth()
@httpPost("/user/save", Guard.authUser(), ValidationMiddleware.validateInput(SaveUserAddressDTO))
public async saveUserAddress(@requestBody() addressDto: SaveUserAddressDTO, @request() req: Request) {
const user = req.user as IUser;
const data = await this.addressService.setUserAddress(user._id.toString(), addressDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("set shop address of current session seller ==> login as seller")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(SaveSellerAddressDTO)
@ApiAuth()
@httpPost("/seller/save", Guard.authSeller(), ValidationMiddleware.validateInput(SaveSellerAddressDTO))
public async saveSellerShopAddress(@requestBody() addressDto: SaveSellerAddressDTO, @request() req: Request) {
const seller = req.user as ISeller;
const data = await this.addressService.setSellerShopAddress(seller._id.toString(), addressDto);
return this.response(data, HttpStatus.Created);
}
}
export { AddressController };
+35
View File
@@ -0,0 +1,35 @@
import { IAddress } from "./models/Abstraction/IAddress";
import { AddressModel } from "./models/address.model";
import { CityModel, ICity } from "./models/city.model";
import { IProvince, ProvinceModel } from "./models/province.model";
import { BaseRepository } from "../../common/base/repository";
export class AddressRepo extends BaseRepository<IAddress> {
constructor() {
super(AddressModel);
}
}
export class CityRepo extends BaseRepository<ICity> {
constructor() {
super(CityModel);
}
}
export class ProvinceRepo extends BaseRepository<IProvince> {
constructor() {
super(ProvinceModel);
}
}
export function createAddressRepo(): AddressRepo {
return new AddressRepo();
}
export function createCityRepo(): CityRepo {
return new CityRepo();
}
export function createProvinceRepo(): ProvinceRepo {
return new ProvinceRepo();
}
+179
View File
@@ -0,0 +1,179 @@
import axios from "axios";
import { inject, injectable } from "inversify";
import { startSession } from "mongoose";
import { AddressRepo, CityRepo, ProvinceRepo } from "./address.repository";
import { SaveSellerAddressDTO, SaveUserAddressDTO } from "./DTO/userAddress.dto";
import { INominatimResponse } from "./interfaces/ILocation";
import { AddressMessage, CommonMessage, ShopMessage, UserMessage } from "../../common/enums/message.enum";
import { BadRequestError, InternalError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
import { UserRepository } from "../user/user.repository";
import { ICity } from "./models/city.model";
import { IProvince } from "./models/province.model";
import { OwnerRef } from "../shop/models/Abstraction/IShop";
import { ShopRepo } from "../shop/shop.repository";
@injectable()
class AddressService {
private NOMINATIM_URL = "https://nominatim.openstreetmap.org/reverse";
// https://nominatim.openstreetmap.org/reverse?lat=34.198770&lon=49.663600&format=json&accept-language=fa
// private MAP_IR_URL = "https://map.ir/reverse";
// private MAP_IR_API_KEY = process.env.MAP_API_KEY;
// private NEHSNAN_URL = "https://api.neshan.org/v5/reverse";
// private NESHAN_API_KEY = process.env.NESHAN_API_KEY;
@inject(IOCTYPES.UserRepository) private userRepository: UserRepository;
@inject(IOCTYPES.AddressRepo) addressRepo: AddressRepo;
@inject(IOCTYPES.CityRepo) private cityRepo: CityRepo;
@inject(IOCTYPES.ProvinceRepo) private provinceRepo: ProvinceRepo;
@inject(IOCTYPES.ShopRepo) shopRepo: ShopRepo;
//#############################################################
//#############################################################
async getLocationWithGeo(geo: { lat: string; lon: string }) {
const location = await this.getAddressFromCoordinates(geo.lat, geo.lon);
let province: IProvince | null;
let city: ICity | null;
//TODO:fix this
const provinceName =
location?.address?.province?.replace(/^استان\s*/, "").trim() ?? location?.address?.state?.replace(/^استان\s*/, "").trim();
const cityName = location.address?.town ? location.address?.town?.replace(/^شهر\s*/, "").trim() : location?.address?.city;
if (!provinceName || !cityName) throw new BadRequestError(AddressMessage.InvalidLocation);
province = await this.provinceRepo.model.findOne({ name: provinceName });
if (!province) province = await this.provinceRepo.model.create({ name: provinceName });
city = await this.cityRepo.model.findOne({ name: cityName });
if (!city) city = await this.cityRepo.model.create({ name: cityName, province: province._id });
const displayNameArray = location.display_name.split(",").map((item) => item.trim());
const filteredDisplayNameArray = location.address.postcode
? displayNameArray.filter((item) => !item.includes(location.address.postcode as string))
: displayNameArray;
const address = filteredDisplayNameArray.reverse().join(",").trim();
return {
province: provinceName,
provinceId: province._id,
city: city.name,
cityId: city._id,
address,
region: location.address.suburb,
};
}
//#############################################################
//#############################################################
async getUserAddress(addressId: string) {
const userAddress = await this.addressRepo.findById(addressId);
if (!userAddress) throw new BadRequestError([AddressMessage.UserShouldHaveAddress, AddressMessage.NotFound]);
return userAddress;
}
//#############################################################
//#############################################################
async setUserAddress(userId: string, addressDto: SaveUserAddressDTO) {
const session = await startSession();
session.startTransaction();
try {
const address = await this.addressRepo.model.create(
[
{
address: addressDto.address,
city: addressDto.cityId,
province: addressDto.provinceId,
plaque: addressDto.plaque,
postalCode: addressDto.postalCode,
lat: addressDto.lat,
lon: addressDto.lon,
},
],
{ session },
);
const updatedUser = await this.userRepository.model.findByIdAndUpdate(userId, { address: address[0]._id }, { session });
if (!updatedUser) throw new BadRequestError(CommonMessage.NotValid);
await session.commitTransaction();
return {
message: UserMessage.UserUpdated,
address: address[0],
};
//
} catch (error) {
await session.abortTransaction();
throw error;
//
} finally {
await session.endSession();
}
}
//
async setSellerShopAddress(sellerId: string, addressDto: SaveSellerAddressDTO) {
const session = await startSession();
session.startTransaction();
try {
const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER }).session(session);
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
const address = await this.addressRepo.model.create(
[
{
address: addressDto.address,
city: addressDto.cityId,
province: addressDto.provinceId,
plaque: addressDto.plaque,
// postalCode: addressDto.postalCode,
lat: addressDto.lat,
lon: addressDto.lon,
},
],
{ session },
);
shop.address = address[0]._id;
await shop.save({ session });
await session.commitTransaction();
return {
message: AddressMessage.Updated,
address: address[0],
};
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
await session.endSession();
}
}
//helpers
//##########################
private async getAddressFromCoordinates(lat: string, lon: string) {
try {
const response = await axios.get(this.NOMINATIM_URL, {
params: { lat, lon, format: "json", "accept-language": "fa" },
headers: {
"User-Agent": "Mozilla/5.0",
},
});
const location = response.data as INominatimResponse;
if (!location) throw new BadRequestError("Address not found for the given coordinates.");
return location;
} catch (error) {
console.log(error);
throw new InternalError([`Failed to get address from coordinates: ${error}`]);
}
}
}
export { AddressService };
@@ -0,0 +1,75 @@
export interface IMapIrResponse {
address: string;
postal_address: string;
address_compact: string;
primary: string;
name: string;
poi: string;
penult: string;
country: string;
province: string;
county: string;
district: string;
rural_district: string;
city: string;
village: string;
region: string;
neighbourhood: string;
last: string;
plaque: string;
postal_code: string;
geom: {
type: string;
coordinates: string[];
};
}
export interface INeshanResponse {
status: string;
formatted_address: string;
route_name: string;
route_type: string;
neighbourhood: string;
city: string;
state: string;
place: null;
municipality_zone: string;
in_traffic_zone: string;
in_odd_even_zone: string;
village: string;
county: string;
district: string;
}
export interface INominatimResponse {
place_id: number;
licence: string;
osm_type: string;
osm_id: number;
lat: string;
lon: string;
class: string;
type: string;
place_rank: number;
importance: number;
addresstype: string;
name: string;
display_name: string;
address: {
road?: string;
province?: string;
village?: string;
neighbourhood?: string;
suburb?: string;
town?: string;
city: string;
district: string;
county: string;
state: string;
"ISO3166-2-lvl4": string;
postcode?: string;
country: string;
country_code: string;
};
boundingbox: string[];
}
@@ -0,0 +1,9 @@
export interface IAddress {
address: string;
city: number;
province: number;
postalCode: string;
plaque: string;
lat: string;
lon: string;
}
@@ -0,0 +1,24 @@
import { Schema, model } from "mongoose";
import { IAddress } from "./Abstraction/IAddress";
const AddressSchema = new Schema<IAddress>(
{
address: { type: String, required: true },
city: { type: Number, ref: "City", required: true },
province: { type: Number, ref: "Province", required: true },
postalCode: { type: String, default: null },
plaque: { type: String, default: null },
lat: { type: String, default: null },
lon: { type: String, default: null },
},
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
);
AddressSchema.pre(["find", "findOne"], function (next) {
this.populate([{ path: "city" }, { path: "province" }]);
next();
});
const AddressModel = model<IAddress>("Address", AddressSchema);
export { AddressModel };
+24
View File
@@ -0,0 +1,24 @@
import mongoose, { Schema, model } from "mongoose";
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
const AutoIncrement = require("mongoose-sequence")(mongoose);
export interface ICity {
_id: number;
name: string;
province: number;
}
const citySchema = new Schema<ICity>(
{
_id: Number,
name: { type: String, required: true, index: true },
province: { type: Number, ref: "Province", required: true },
},
{ timestamps: true, toJSON: { versionKey: false, virtuals: true }, _id: false, id: false },
);
citySchema.plugin(AutoIncrement, { id: "city_id", inc_field: "_id" });
const CityModel = model<ICity>("City", citySchema);
export { CityModel };
@@ -0,0 +1,22 @@
import mongoose, { Schema, model } from "mongoose";
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
const AutoIncrement = require("mongoose-sequence")(mongoose);
export interface IProvince {
_id: number;
name: string;
}
const ProvinceSchema = new Schema<IProvince>(
{
_id: Number,
name: { type: String, required: true, unique: true, index: true },
},
{ timestamps: true, toJSON: { versionKey: false, virtuals: true }, _id: false, id: false },
);
ProvinceSchema.plugin(AutoIncrement, { id: "province_id", inc_field: "_id" });
const ProvinceModel = model<IProvince>("Province", ProvinceSchema);
export { ProvinceModel };
@@ -0,0 +1,23 @@
import { Expose } from "class-transformer";
import { IsEmail, IsNotEmpty } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { AuthMessage } from "../../../common/enums/message.enum";
export class ChangePasswordDTO {
@Expose()
@IsNotEmpty({ message: AuthMessage.EmailNotEmpty })
@IsEmail(undefined, { message: AuthMessage.IncorrectEmail })
@ApiProperty({ type: "string", description: "email of created admin", example: "defaultAdmin@email.com" })
email: string;
@Expose()
@IsNotEmpty({ message: AuthMessage.PasswordNotEmpty })
@ApiProperty({ type: "string", description: "password of created admin", example: "123@123!" })
password: string;
@Expose()
@IsNotEmpty({ message: AuthMessage.PasswordNotEmpty })
@ApiProperty({ type: "string", description: "new password", example: "123@123!" })
newPassword: string;
}
+18
View File
@@ -0,0 +1,18 @@
import { Expose } from "class-transformer";
import { IsNotEmpty } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
export class CreateContractDTO {
@Expose()
@IsNotEmpty()
@ApiProperty({ type: "string", description: "content of the contract", example: "content of the contract" })
content: string;
}
export class UpdateContractDTO {
@Expose()
@IsNotEmpty()
@ApiProperty({ type: "string", description: "content of the contract", example: "content of the contract" })
content: string;
}
+37
View File
@@ -0,0 +1,37 @@
import { Expose } from "class-transformer";
import { IsNotEmpty } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { AdminMessage, RoleMessage } from "../../../common/enums/message.enum";
export class CreateAdminDTO {
@Expose()
@IsNotEmpty({ message: AdminMessage.FullNameNotEmpty })
@ApiProperty({ type: "string", description: "full name of new admin", example: "admin 1" })
fullName: string;
@Expose()
@IsNotEmpty({ message: AdminMessage.UserNameNotEmpty })
@ApiProperty({ type: "string", description: "username of new admin", example: "admin1" })
userName: string;
@Expose()
@IsNotEmpty({ message: AdminMessage.EmailNotEmpty })
@ApiProperty({ type: "string", description: "email of new admin", example: "admin1@gmail.com" })
email: string;
@Expose()
@IsNotEmpty({ message: AdminMessage.PhoneNotEmpty })
@ApiProperty({ type: "string", description: "phoneNumber of new admin", example: "09918914108" })
phoneNumber: string;
@Expose()
@IsNotEmpty({ message: RoleMessage.PermissionsNotEmpty })
@ApiProperty({ type: "array", description: "permissions of user", example: ["67723b0c8109df157bd9b2c0", "67723b0c8109df157bd9b2ba"] })
permissions: string[];
@Expose()
@IsNotEmpty({ message: AdminMessage.PasswordNotEmpty })
@ApiProperty({ type: "string", description: "password", example: "123@123!" })
password: string;
}
+17
View File
@@ -0,0 +1,17 @@
import { Expose } from "class-transformer";
import { IsNotEmpty } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { RoleMessage } from "../../../common/enums/message.enum";
export class CreateRoleDTO {
@Expose()
@IsNotEmpty({ message: RoleMessage.RoleNameNotEmpty })
@ApiProperty({ type: "string", description: "name of role", example: "ADMIN" })
name: string;
@Expose()
@IsNotEmpty({ message: RoleMessage.PermissionsNotEmpty })
@ApiProperty({ type: "array", description: "permissions of role", example: ["67723b0c8109df157bd9b2c0", "67723b0c8109df157bd9b2ba"] })
permissions: string[];
}
@@ -0,0 +1,18 @@
import { Expose } from "class-transformer";
import { IsEmail, IsNotEmpty } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { AuthMessage } from "../../../common/enums/message.enum";
export class LoginPassDTO {
@Expose()
@IsNotEmpty({ message: AuthMessage.EmailNotEmpty })
@IsEmail(undefined, { message: AuthMessage.IncorrectEmail })
@ApiProperty({ type: "string", description: "email of created admin", example: "defaultAdmin@email.com" })
email: string;
@Expose()
@IsNotEmpty({ message: AuthMessage.PasswordNotEmpty })
@ApiProperty({ type: "string", description: "password of created admin", example: "123@123!" })
password: string;
}
+205
View File
@@ -0,0 +1,205 @@
import { Expose } from "class-transformer";
import { IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { DisplayLocations } from "../../../common/enums/media.enum";
//*****************************************
//*****************************************
export class CreateSliderDTO {
@Expose()
@IsNotEmpty()
@IsEnum(DisplayLocations)
@ApiProperty({
type: "string",
description: "Display location of the slider",
example: `${DisplayLocations.Desktop} | ${DisplayLocations.Mobile}`,
})
displayLocation: DisplayLocations;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the image url of slider", example: "https://cdn.com" })
imageUrl: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the alt text of image ", example: "slider-image" })
altText: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the title text of image ", example: "slider 1" })
title: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the link of that slider that is clickable", example: "/summer-sale" })
linkUrl: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ type: "boolean", description: "slider active status", example: true, required: false })
isActive: boolean;
}
export class CreateBannerDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the image url of slider", example: "https://cdn.com" })
imageUrl: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the alt text of image ", example: "slider-image" })
altText: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the title text of image ", example: "slider 1" })
title: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the link of that slider that is clickable", example: "/summer-sale" })
linkUrl: string;
// @Expose()
// @IsNotEmpty()
// @IsEnum(DisplayLocations)
// @ApiProperty({
// type: "string",
// description: "Display location of the slider",
// example: "homepage_top | homepage_bottom | category_page | product_page",
// })
// displayLocation: DisplayLocations;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ type: "boolean", description: "slider active status", example: true, required: false })
isActive: boolean;
@Expose()
@IsNotEmpty()
@ApiProperty({ type: "number", description: "the order of image for the banner group", example: 1, required: false })
order: number;
}
//*****************************************
//*****************************************
export class UpdateSliderDTO {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the image url of slider", example: "https://cdn.com" })
imageUrl: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the alt text of image ", example: "slider-image" })
altText: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the title text of image ", example: "slider 1" })
title: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the link of that slider that is clickable", example: "/summer-sale" })
linkUrl: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ type: "boolean", description: "slider active status", example: true, required: false })
isActive: boolean;
}
export class UpdateBannerDTO {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the image url of slider", example: "https://cdn.com" })
imageUrl: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the alt text of image ", example: "slider-image" })
altText: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the title text of image ", example: "slider 1" })
title: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the link of that slider that is clickable", example: "/summer-sale" })
linkUrl: string;
// @Expose()
// @IsOptional()
// @IsNotEmpty()
// @IsEnum(DisplayLocations)
// @ApiProperty({
// type: "string",
// description: "Display location of the slider",
// example: "homepage_top | homepage_bottom | category_page | product_page",
// })
// displayLocation: DisplayLocations;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ type: "boolean", description: "slider active status", example: true, required: false })
isActive: boolean;
// @Expose()
// @IsOptional()
// @IsNotEmpty()
// @ApiProperty({ type: "number", description: "the order of image for the banner group", example: 1, required: false })
// order: number;
}
//*****************************************
//*****************************************
export class ActivateMediaDTO {
@Expose()
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ type: "boolean", description: "Active status of the media", example: true })
isActive: boolean;
}
@@ -0,0 +1,30 @@
import { Expose } from "class-transformer";
import { IsInt, IsMongoId, IsNotEmpty } from "class-validator";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { CommonMessage } from "../../../common/enums/message.enum";
import { IncredibleOffersModel } from "../../product/models/IncredibleOffers.model";
import { ProductModel } from "../../product/models/product.model";
import { ProductVariantModel } from "../../product/models/productVariant.model";
export class AddIncredibleOffersParamDto {
@Expose()
@IsNotEmpty()
@IsInt()
@IsValidId(ProductModel)
id: number;
@Expose()
@IsNotEmpty()
@IsMongoId({ message: CommonMessage.NotValidId })
@IsValidId(ProductVariantModel)
variantId: string;
}
export class DeleteIncredibleOffersParamDto {
@Expose()
@IsNotEmpty()
@IsMongoId({ message: CommonMessage.NotValidId })
@IsValidId(IncredibleOffersModel)
id: string;
}
@@ -0,0 +1,29 @@
import { Expose } from "class-transformer";
import { IsEnum, IsNotEmpty, IsOptional } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { StatusEnum } from "../../../common/enums/status.enum";
import { SellerDocumentModel } from "../../seller/models/sellerDocument.model";
export class UpdateSellerDocumentDto {
@Expose()
@IsNotEmpty()
@IsValidId(SellerDocumentModel)
@ApiProperty({ type: "string", description: "the document id of seller", example: "60f4b3b3b3b3b3b3b3b3b3b3" })
docsId: string;
@Expose()
@IsNotEmpty()
@IsEnum(StatusEnum)
@ApiProperty({ type: "string", description: `${StatusEnum.Approved} | ${StatusEnum.Rejected}`, example: StatusEnum.Approved })
status: StatusEnum;
}
export class RejectReasonSellerDocumentDto {
@Expose()
@IsOptional()
@IsNotEmpty()
@ApiProperty({ type: "string", description: "the reason of rejection", example: "the document is not clear" })
reason: string;
}
+234
View File
@@ -0,0 +1,234 @@
import { compare, hash } from "bcrypt";
import { inject, injectable } from "inversify";
import { LoginPassDTO } from "./DTO/loginPassword.dto";
import { AdminMessage, AuthMessage, CommonMessage, ContractMessage, RoleMessage, ShopMessage } from "../../common/enums/message.enum";
import { TokenService } from "../token/token.service";
import { ChangePasswordDTO } from "./DTO/changePassword.dto";
import { CreateContractDTO, UpdateContractDTO } from "./DTO/contract.dto";
import { CreateAdminDTO } from "./DTO/createAdmin.dto";
import { CreateRoleDTO } from "./DTO/createRole.dto";
import { AdminRepository } from "./repository/admin";
import { ContractRepository } from "./repository/contract";
import { BannerRepository, SliderRepository } from "./repository/media";
import { PermissionRepository } from "./repository/permission";
import { RoleRepository } from "./repository/role";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
import { BrandService } from "../brand/brand.service";
import { OrderService } from "../order/order.service";
import { ProductService } from "../product/providers/product.service";
import { SellerService } from "../seller/seller.service";
import { OwnerRef } from "../shop/models/Abstraction/IShop";
import { ShopRepo } from "../shop/shop.repository";
import { UserService } from "../user/user.service";
import { WalletService } from "../wallet/wallet.service";
import { WarrantyService } from "../warranty/warranty.service";
import { RoleEnum } from "./models/Abstraction/IRole";
@injectable()
class AdminService {
@inject(IOCTYPES.AdminRepository) adminRepo: AdminRepository;
@inject(IOCTYPES.RoleRepository) roleRepo: RoleRepository;
@inject(IOCTYPES.PermissionRepository) permissionRepo: PermissionRepository;
@inject(IOCTYPES.TokenService) tokenService: TokenService;
@inject(IOCTYPES.SliderRepository) sliderRepo: SliderRepository;
@inject(IOCTYPES.BannerRepository) bannerRepo: BannerRepository;
@inject(IOCTYPES.ContractRepository) contractRepo: ContractRepository;
@inject(IOCTYPES.OrderService) private orderService: OrderService;
@inject(IOCTYPES.WalletService) private walletService: WalletService;
@inject(IOCTYPES.ProductService) private productService: ProductService;
@inject(IOCTYPES.SellerService) private sellerService: SellerService;
@inject(IOCTYPES.UserService) private userService: UserService;
@inject(IOCTYPES.BrandService) private brandService: BrandService;
@inject(IOCTYPES.WarrantyService) private warrantyService: WarrantyService;
@inject(IOCTYPES.ShopRepo) private shopRepo: ShopRepo;
async loginPasswordS(loginDto: LoginPassDTO) {
const { password, email } = loginDto;
const admin = await this.adminRepo.findByEmail(email);
if (!admin) throw new BadRequestError(AuthMessage.EmailOrPasswordInc);
console.log(admin);
const passMatch = await this.comparePassword(password, admin.password);
if (!passMatch) throw new BadRequestError(AuthMessage.EmailOrPasswordInc);
const tokens = await this.tokenService.generateAuthTokens({ sub: admin._id.toString() });
return {
message: AuthMessage.SuccessLogin,
...tokens,
};
}
async profileS(adminId: string) {
const admin = await this.adminRepo.model.findOne({ _id: adminId }, { password: 0, role: 0 });
if (!admin) throw new BadRequestError(CommonMessage.NotFound);
return { admin };
}
async changePasswordS(changePasswordDto: ChangePasswordDTO) {
const { email, password, newPassword } = changePasswordDto;
const admin = await this.adminRepo.findByEmail(email);
if (!admin) throw new BadRequestError(AuthMessage.EmailOrPasswordInc);
const passMatch = await this.comparePassword(password, admin.password);
if (!passMatch) throw new BadRequestError(AuthMessage.EmailOrPasswordInc);
const newChangedPassword = await hash(newPassword, 10);
const updatedAdmin = await this.adminRepo.model.findByIdAndUpdate(admin._id.toString(), { password: newChangedPassword });
console.log(updatedAdmin);
return {
message: AuthMessage.PasswordChangedSuccessfully,
};
}
async getContactContent() {
const contract = await this.contractRepo.model.findOne();
return { contract };
}
async createContentS(createDto: CreateContractDTO) {
const contract = await this.contractRepo.model.findOne();
if (contract) {
throw new BadRequestError(ContractMessage.AlreadyExist);
}
const createdContent = await this.contractRepo.model.create({
...createDto,
});
return { createdContent };
}
async updateContentS(updateDto: UpdateContractDTO) {
const contract = await this.contractRepo.model.findOne();
if (!contract) {
throw new BadRequestError(ContractMessage.NotFound);
}
const updatedContent = await this.contractRepo.model.findByIdAndUpdate(
contract._id.toString(),
{
...updateDto,
},
{ new: true },
);
return { updatedContent };
}
async comparePassword(raw: string, hashed: string) {
return await compare(raw, hashed);
}
async createAdminS(adminId: string, createDto: CreateAdminDTO) {
const { password, userName, fullName, email, phoneNumber, permissions } = createDto;
const admin = await this.adminRepo.findAdmin(email, phoneNumber);
if (admin) throw new BadRequestError(AdminMessage.AdminIsExist);
const roleDoc = await this.roleRepo.model.findOne({ name: RoleEnum.SUPERADMIN });
if (!roleDoc) throw new BadRequestError(AdminMessage.RoleNotFound);
const permissionDocs = await this.permissionRepo.model.find({ _id: { $in: permissions } });
const createdAdmin = await this.adminRepo.model.create({
userName,
fullName,
phoneNumber,
email,
password,
permissions: permissionDocs.map((perm) => perm._id),
role: roleDoc._id,
createdBy: adminId,
});
const { password: adminPassword, ...newAdmin } = createdAdmin.toObject();
return { newAdmin };
}
async getAdminsS() {
const admins = await this.adminRepo.getAdmins();
return { admins };
}
async deleteAdminS(adminId: string) {
const admin = await this.adminRepo.model.findByIdAndDelete(adminId);
if (!admin) throw new BadRequestError(AdminMessage.NotFound);
return { message: AdminMessage.DeletedSuccessfully };
}
async createRoleS(createDto: CreateRoleDTO) {
const { name, permissions } = createDto;
const roleExist = await this.roleRepo.model.findOne({ name });
if (roleExist) throw new BadRequestError(RoleMessage.RoleExist);
const permissionDocs = await this.permissionRepo.model.find({ _id: { $in: permissions } });
const role = await this.roleRepo.model.create({ name, permissions: permissionDocs.map((perm) => perm._id) });
return { role };
}
async getRoles() {
const roles = await this.roleRepo.model.find({});
return { roles };
}
async getPermissions() {
const permissions = await this.permissionRepo.model.find();
return { permissions };
}
async reports() {
const dailySales = await this.orderService.getDailySalesReport();
const weeklySales = await this.orderService.getWeeklySalesReport();
const monthlySales = await this.orderService.getMonthlySalesReport();
const yearlySales = await this.orderService.getAnnualSalesReport();
const dailyShipmentSales = await this.orderService.getDailyShipmentSalesReport();
const weeklyShipmentSales = await this.orderService.getWeeklyShipmentSalesReport();
const monthlyShipmentSales = await this.orderService.getMonthlyShipmentSalesReport();
const yearlyShipmentSales = await this.orderService.getAnnualShipmentSalesReport();
const FiveDaysAgoSales = await this.orderService.getFiveDaysAgoSalesReport();
const topProducts = await this.orderService.getTopSellingProductsS();
const proccessingOrders = await this.orderService.getAllProcessingOrders();
const withdrawalRequests = await this.walletService.getWithdrawalRequestsReport();
const pendingComments = await this.productService.getCommentsForReport();
const pendingQuestions = await this.productService.getQuestionsForReport();
const sellersCount = await this.sellerService.getAllSellerForReport();
const usersCount = await this.userService.getUsersCount();
const wholesaleRequestCount = await this.sellerService.getWholesaleRequestCount();
const pendingBrandsCount = await this.brandService.getPendingBrandsCount();
const pendingWarranties = await this.warrantyService.getPendingWarranties();
const productCount = await this.productService.getProductCount();
const pendingProductCount = await this.productService.getPendingProductCount();
return {
dailySales,
weeklySales,
monthlySales,
yearlySales,
dailyShipmentSales,
weeklyShipmentSales,
monthlyShipmentSales,
yearlyShipmentSales,
withdrawalRequests,
FiveDaysAgoSales,
topProducts,
proccessingOrders,
pendingComments,
pendingQuestions,
sellersCount,
usersCount,
wholesaleRequestCount,
pendingBrandsCount,
pendingWarranties,
productCount,
pendingProductCount,
};
}
async changeShopChatStatus(sellerId: string) {
const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.ADMIN });
if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound);
shop.isChatActive = !shop.isChatActive;
await shop.save();
return {
message: CommonMessage.Updated,
};
}
}
export { AdminService };
@@ -0,0 +1,132 @@
import { Request } from "express";
import rateLimit from "express-rate-limit";
import { inject } from "inversify";
import { controller, httpDelete, httpGet, httpPatch, httpPost, request, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { appConfig } from "../../../core/config/app.config";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { ShopUpdateDTO } from "../../shop/DTO/shop-update.dto";
import { OwnerRef } from "../../shop/models/Abstraction/IShop";
import { ShopService } from "../../shop/shop.service";
import { AdminService } from "../admin.service";
import { ChangePasswordDTO } from "../DTO/changePassword.dto";
import { CreateAdminDTO } from "../DTO/createAdmin.dto";
import { LoginPassDTO } from "../DTO/loginPassword.dto";
import { IAdmin } from "../models/Abstraction/IAdmin";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin base")
@controller("/admin")
export class AdminBaseController extends BaseController {
@inject(IOCTYPES.AdminService) private adminService: AdminService;
@inject(IOCTYPES.ShopService) private shopService: ShopService;
@ApiOperation("login admin with its password")
@ApiResponse("login the admin", HttpStatus.Ok)
@ApiModel(LoginPassDTO)
@httpPost("/login/password", rateLimit(appConfig.rate), ValidationMiddleware.validateInput(LoginPassDTO))
public async loginPassword(@requestBody() LoginPasswordDto: LoginPassDTO) {
const data = await this.adminService.loginPasswordS(LoginPasswordDto);
return this.response(data);
}
@ApiOperation("change admin password")
@ApiResponse("change admin password", HttpStatus.Ok)
@ApiModel(ChangePasswordDTO)
@ApiAuth()
@httpPatch("/change/password", rateLimit(appConfig.rate), Guard.authAdmin(), ValidationMiddleware.validateInput(ChangePasswordDTO))
public async changePassword(@requestBody() changePasswordDTO: ChangePasswordDTO) {
const data = await this.adminService.changePasswordS(changePasswordDTO);
return this.response(data);
}
@ApiOperation("get admin profile")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiAuth()
@httpGet("/profile", Guard.authAdmin())
public async profile(@request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.adminService.profileS(admin._id.toString());
return this.response(data);
}
@ApiOperation("get native shop info")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiAuth()
@httpGet("/shop", Guard.authAdmin())
public async getShop(@request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.shopService.getShopInfo(admin._id.toString(), OwnerRef.ADMIN);
return this.response(data);
}
@ApiOperation("update native shop info")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiModel(ShopUpdateDTO)
@ApiAuth()
@httpPatch("/shop", Guard.authAdmin(), ValidationMiddleware.validateInput(ShopUpdateDTO))
public async updateShop(@request() req: Request, @requestBody() updateDto: ShopUpdateDTO) {
const admin = req.user as IAdmin;
const data = await this.shopService.updateSellerShopInfo(admin._id.toString(), updateDto);
return this.response(data);
}
@ApiOperation("get permissions")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiAuth()
@httpGet("/permissions", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async getPermissions() {
const data = await this.adminService.getPermissions();
return this.response(data);
}
@ApiOperation("Create new admin")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(CreateAdminDTO)
@ApiAuth()
@httpPost(
"/create",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(CreateAdminDTO),
)
public async createAdmin(@request() req: Request, @requestBody() createDto: CreateAdminDTO) {
const admin = req.user as IAdmin;
const data = await this.adminService.createAdminS(admin._id.toString(), createDto);
return this.response(data);
}
@ApiOperation("delete admin")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiAuth()
@ApiParam("id", "admin id", true)
@httpDelete("/:id/delete", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async deleteAdmin(@requestParam("id") adminId: string) {
const data = await this.adminService.deleteAdminS(adminId);
return this.response(data);
}
@ApiOperation("get admins")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiAuth()
@httpGet("/admins", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async getAdmins() {
const data = await this.adminService.getAdminsS();
return this.response(data);
}
//order-reports
@ApiOperation("get all sellers products")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/reports", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async getOrdersReports() {
const data = await this.adminService.reports();
return this.response(data);
}
}
@@ -0,0 +1,121 @@
import { Request } from "express";
import { inject } from "inversify";
import { controller, httpDelete, httpPost, request, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiFile, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { UploadService } from "../../../utils/upload.service";
import { BlogService } from "../../blog/blog.service";
import { CreateBlogDTO, UpdateBlogDTO } from "../../blog/DTO/createBlog.dto";
import { CreateBlogCategoryDTO, UpdateBlogCategoryDTO } from "../../blog/DTO/createBlogCategory.dto";
import { IAdmin } from "../models/Abstraction/IAdmin";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin Blog")
@controller("/admin/blog")
export class AdminBlogController extends BaseController {
@inject(IOCTYPES.BlogService) private blogService: BlogService;
@ApiOperation("create a blog category")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateBlogCategoryDTO)
@ApiAuth()
@httpPost(
"/category",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.BLOG]),
ValidationMiddleware.validateInput(CreateBlogCategoryDTO),
)
public async createBlogCategory(@requestBody() createBlogCatDto: CreateBlogCategoryDTO) {
const data = await this.blogService.createBlogCategory(createBlogCatDto);
return this.response(data, HttpStatus.Created);
}
//delete a blog category
@ApiOperation("delete a blog category with its id")
@ApiParam("categoryId", "the blog category id ", true)
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpDelete("/category/:categoryId", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.BLOG]))
public async deleteBlogCategory(@requestParam("categoryId") catId: string) {
const data = await this.blogService.deleteBlogCategory(catId);
return this.response(data);
}
@ApiOperation("delete a blog with its id")
@ApiParam("blogId", "the blog id ", true)
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpDelete("/:blogId", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.BLOG]))
public async deleteBlog(@requestParam("blogId") blogId: string) {
const data = await this.blogService.deleteBlog(blogId);
return this.response(data);
}
@ApiOperation("update a blog category")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "blog category id", true)
@ApiModel(UpdateBlogCategoryDTO)
@ApiAuth()
@httpPost(
"/category/:id",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.BLOG]),
ValidationMiddleware.validateInput(UpdateBlogCategoryDTO),
)
public async updateBlogCategory(@requestParam("id") blogCatId: string, @requestBody() updateBlogCatDto: UpdateBlogCategoryDTO) {
const data = await this.blogService.updateBlogCategory(blogCatId, updateBlogCatDto);
return this.response(data);
}
@ApiOperation("create a blog")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateBlogDTO)
@ApiAuth()
@httpPost("", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.BLOG]), ValidationMiddleware.validateInput(CreateBlogDTO))
public async createBlog(@request() req: Request, @requestBody() createBlogDto: CreateBlogDTO) {
const admin = req.user as IAdmin;
const data = await this.blogService.createBlog(admin._id.toString(), createBlogDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update a blog with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "blog id", true)
@ApiModel(UpdateBlogDTO)
@ApiAuth()
@httpPost(
"/:id",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.BLOG]),
ValidationMiddleware.validateInput(UpdateBlogDTO),
)
public async updateBlog(@requestParam("id") blogId: string, @requestBody() updateBlogDto: UpdateBlogDTO) {
const data = await this.blogService.updateBlog(blogId, updateBlogDto);
return this.response(data);
}
//blog uploader
@ApiOperation("Upload a single blog image ==> need to login as admin")
@ApiResponse("File uploaded successfully", HttpStatus.Accepted)
@ApiFile("image")
@ApiAuth()
@httpPost("/upload/single", Guard.authAdmin(), UploadService.single("image", "blog-image"))
public async uploadBlogImage(@request() req: Request) {
// eslint-disable-next-line no-undef
const file = req.file as Express.MulterS3.File;
const data = {
message: "file uploaded!",
url: {
originalname: file.originalname,
size: file.size,
url: file.location,
type: file.mimetype,
},
};
return this.response(data, HttpStatus.Accepted);
}
}
@@ -0,0 +1,89 @@
import rateLimit from "express-rate-limit";
import { inject } from "inversify";
import { controller, httpDelete, httpPatch, httpPost, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { appConfig } from "../../../core/config/app.config";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { BrandService } from "../../brand/brand.service";
import { CreateBrandDTO, CreateGlobalBrand } from "../../brand/DTO/createBrand.dto";
import { UpdateBrandDTO } from "../../brand/DTO/updateBrand.dto";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin Brand")
@controller("/admin/brand")
export class AdminBrandController extends BaseController {
@inject(IOCTYPES.BrandService) private brandService: BrandService;
@ApiOperation("request to create a new brand ==> login as admin")
@ApiResponse("successful", HttpStatus.Ok)
@ApiModel(CreateBrandDTO)
@ApiAuth()
@httpPost(
"",
rateLimit(appConfig.rate),
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(CreateBrandDTO),
)
public async createBrand(@requestBody() createBrandDto: CreateBrandDTO) {
const data = await this.brandService.createBrand(createBrandDto);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("request to create a new global brand ==> login as admin")
@ApiResponse("successful", HttpStatus.Ok)
@ApiModel(CreateGlobalBrand)
@ApiAuth()
@httpPost(
"/global",
rateLimit(appConfig.rate),
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(CreateGlobalBrand),
)
public async createGlobalBrand(@requestBody() createBrandDto: CreateGlobalBrand) {
const data = await this.brandService.createGlobalBrand(createBrandDto);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("update a brand with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of warranty")
@ApiModel(UpdateBrandDTO)
@ApiAuth()
@httpPatch(
"/:id",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(UpdateBrandDTO),
)
public async UpdateBrand(@requestParam("id") id: string, @requestBody() updateDto: UpdateBrandDTO) {
const data = await this.brandService.updateById(id, updateDto);
return this.response({ data });
}
@ApiOperation("approve a brand status with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of brand")
@ApiAuth()
@httpPost("/:id/approve", Guard.authAdmin())
public async approveBrand(@requestParam("id") id: string) {
const data = await this.brandService.approve(id);
return this.response({ data });
}
@ApiOperation("delete a brand with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of warranty")
@ApiAuth()
@httpDelete("/:id", Guard.authAdmin())
public async deleteBrand(@requestParam("id") id: string) {
const data = await this.brandService.deleteById(id);
return this.response({ data });
}
}
@@ -0,0 +1,125 @@
import { inject } from "inversify";
import { controller, httpDelete, httpPatch, httpPost, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiBody, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { ParamDto } from "../../../common/dto/param.dto";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { CategoryService } from "../../category/category.service";
import { CreateCategoryDTO } from "../../category/DTO/CreateCategory.dto";
import { CreateCategoryAttDTO } from "../../category/DTO/createCategoryAtt.dto";
import { CreateCategoryThemeDTO, UpdateCategoryVariantDTO } from "../../category/DTO/createCategoryTheme.dto";
import { UpdateCategoryDTO } from "../../category/DTO/UpdateCategory.dto";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin Category")
@controller("/admin/category")
export class AdminCategoryController extends BaseController {
@inject(IOCTYPES.CategoryService) private categoryService: CategoryService;
@ApiOperation("create a category ")
@ApiResponse("return created category", HttpStatus.Created)
@ApiModel(CreateCategoryDTO)
@ApiAuth()
@httpPost("", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateCategoryDTO))
public async createCategory(@requestBody() createDto: CreateCategoryDTO) {
const data = await this.categoryService.createCategory(createDto);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("update a category")
@ApiResponse("successful", HttpStatus.Created)
@ApiAuth()
@ApiModel(UpdateCategoryDTO)
@httpPatch(
"",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(UpdateCategoryDTO),
)
public async updateCategory(@requestBody() updateDto: UpdateCategoryDTO) {
const data = await this.categoryService.updateCategory(updateDto);
return this.response(data, HttpStatus.Ok);
}
@ApiOperation("delete a category with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of category")
@ApiAuth()
@httpDelete("/:id/delete", Guard.authAdmin())
public async deleteCategory(@requestParam("id") id: string) {
const data = await this.categoryService.deleteCategory(id);
return this.response(data);
}
@ApiOperation("delete a category attribute with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of attribute")
@ApiAuth()
@httpDelete("/attributes/:id/delete", Guard.authAdmin())
public async deleteCategoryAttribute(@requestParam("id") id: string) {
const data = await this.categoryService.deleteCategoryAttributeS(id);
return this.response(data);
}
@ApiOperation("delete a category variant with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("catId", "id of category")
@ApiParam("varId", "id of variant")
@ApiAuth()
@httpDelete("/:catId/variant/:varId/delete", Guard.authAdmin())
public async deleteCategoryVariant(@requestParam("catId") catId: string, @requestParam("varId") varId: string) {
const data = await this.categoryService.deleteCategoryVariantS(catId, varId);
return this.response(data);
}
@ApiOperation("create a category theme and theme variant")
@ApiResponse("successful", HttpStatus.Created)
@ApiParam("id", "id of category", true)
@ApiModel(CreateCategoryThemeDTO)
@ApiBody(
"the colors and sizes and meterage fields are optional and one of them should send and u can also send noColor_noSize too for no theme",
)
@ApiAuth()
@httpPost(
"/:id/variants",
Guard.authAdmin(),
ValidationMiddleware.validateInput(CreateCategoryThemeDTO),
ValidationMiddleware.validateParameter(ParamDto),
)
public async createCategoryTheme(@requestBody() createDto: CreateCategoryThemeDTO, @requestParam() paramDto: ParamDto) {
const data = await this.categoryService.createCategoryThemeS(createDto, paramDto.id);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("add a category variant")
@ApiResponse("successful", HttpStatus.Created)
@ApiParam("id", "id of category", true)
@ApiModel(UpdateCategoryVariantDTO)
@ApiAuth()
@httpPatch(
"/:id/variants",
Guard.authAdmin(),
ValidationMiddleware.validateInput(UpdateCategoryVariantDTO),
ValidationMiddleware.validateParameter(ParamDto),
)
public async updateCategoryVariant(@requestBody() updateDto: UpdateCategoryVariantDTO, @requestParam() paramDto: ParamDto) {
const data = await this.categoryService.updateCategoryVariantS(updateDto, paramDto.id);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("create a category attributes with its id")
@ApiResponse("successful", HttpStatus.Created)
@ApiParam("id", "id of category", true)
@ApiModel(CreateCategoryAttDTO)
@ApiAuth()
@ApiBody("the attribute type can be text|select|number|checkbox|input ")
@httpPost("/:id/attributes", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateCategoryAttDTO))
public async createCategoryAttribute(@requestBody() createAttributeDto: CreateCategoryAttDTO, @requestParam("id") catId: string) {
const data = await this.categoryService.createCategoryAttributeS(createAttributeDto, catId);
return this.response({ data }, HttpStatus.Created);
}
}
@@ -0,0 +1,100 @@
import { Request } from "express";
import rateLimit from "express-rate-limit";
import { inject } from "inversify";
import { controller, httpDelete, httpGet, httpPatch, httpPost, request, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { ParamDto } from "../../../common/dto/param.dto";
import { appConfig } from "../../../core/config/app.config";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { CouponService } from "../../Coupon/coupon.service";
import { CreateCouponDTO } from "../../Coupon/DTO/createCoupon.dto";
import { UpdateCouponDTO } from "../../Coupon/DTO/updateCoupon.dto";
import { OwnerRef } from "../../shop/models/Abstraction/IShop";
import { IAdmin } from "../models/Abstraction/IAdmin";
@ApiTags("Admin Coupon")
@controller("/admin/coupon")
export class AdminCouponController extends BaseController {
@inject(IOCTYPES.CouponService) couponService: CouponService;
@ApiOperation("get all native shop created coupon ")
@ApiResponse("successful")
@ApiAuth()
@httpGet("/native", Guard.authAdmin())
public async getSellerCoupons(@request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.couponService.getSellerCoupons(admin._id.toString(), OwnerRef.ADMIN);
return this.response(data);
}
@ApiOperation("get all seller shop created coupon ")
@ApiResponse("successful")
@ApiAuth()
@httpGet("/seller", Guard.authAdmin())
public async getSellersCoupons() {
const data = await this.couponService.getSellersCoupons();
return this.response(data);
}
@ApiOperation("get a single coupon")
@ApiParam("id", "coupon id", true)
@ApiAuth()
@httpGet("/:id", Guard.authAdmin())
public async getSingleCoupon(@requestParam() paramDto: ParamDto) {
const data = await this.couponService.getSingleCoupon(paramDto.id);
return this.response(data);
}
@ApiOperation("create a Coupon")
@ApiResponse("successful")
@ApiModel(CreateCouponDTO)
@ApiAuth()
@httpPost("", rateLimit(appConfig.rate), Guard.authAdmin(), ValidationMiddleware.validateInput(CreateCouponDTO))
public async createCoupon(@requestBody() createDto: CreateCouponDTO, @request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.couponService.createCoupon(createDto, admin._id.toString(), OwnerRef.ADMIN);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update a native shop coupon")
@ApiResponse("successful")
@ApiModel(UpdateCouponDTO)
@ApiParam("id", "coupon id", true)
@ApiAuth()
@httpPatch("/:id", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateCouponDTO))
public async updateSellerCoupon(
@requestBody() updateDto: UpdateCouponDTO,
@request() req: Request,
@requestParam("id") couponId: string,
) {
const admin = req.user as IAdmin;
const data = await this.couponService.updateCoupon(updateDto, admin._id.toString(), couponId, OwnerRef.ADMIN);
return this.response(data);
}
@ApiOperation("deactivate a coupon ")
@ApiResponse("successful")
@ApiAuth()
@ApiParam("id", "coupon id", true)
@httpPatch("/:id/status", Guard.authAdmin(), ValidationMiddleware.validateParameter(ParamDto))
public async changeCouponStatus(@request() req: Request, @requestParam() paramDto: ParamDto) {
const admin = req.user as IAdmin;
const data = await this.couponService.changeCouponStatus(admin._id.toString(), paramDto.id, OwnerRef.ADMIN);
return this.response(data);
}
@ApiOperation("delete a coupon")
@ApiResponse("successful")
@ApiAuth()
@ApiParam("id", "coupon id", true)
@httpDelete("/:id/delete", Guard.authAdmin())
public async deleteCoupon(@requestParam("id") couponId: string) {
const data = await this.couponService.removeCouponByAdmin(couponId);
return this.response(data);
}
}
@@ -0,0 +1,149 @@
import { Response } from "express";
import { inject } from "inversify";
import {
controller,
httpDelete,
httpGet,
httpPatch,
httpPost,
queryParam,
requestBody,
requestParam,
response,
} from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { ParamDto } from "../../../common/dto/param.dto";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { PaymentService } from "../../payment/providers/payment.service";
import { CreatePricingDTO } from "../../pricing/DTO/createPricing.dto";
import { UpdatePricingDTO } from "../../pricing/DTO/updatePricing.dto";
import { PricingService } from "../../pricing/pricing.service";
import { WithdrawalStatusDTO } from "../../wallet/DTO/withdraw.dto";
import { WalletService } from "../../wallet/wallet.service";
@ApiTags("Admin Financial")
@controller("/admin/financial")
export class AdminFinancialController extends BaseController {
@inject(IOCTYPES.WalletService) private walletService: WalletService;
@inject(IOCTYPES.PaymentService) private paymentService: PaymentService;
@inject(IOCTYPES.PricingService) private pricingService: PricingService;
//payments
@ApiOperation("get all payments")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("status", "status of payment ==> value = Completed | Cancelled | Pending ")
@ApiQuery(
"since",
"time of order created ==> value should be standard timestamp since that time user want like == > Date.now() - (7 * 24 * 60 * 60 * 1000) ==> this mean from a week ago till now ",
)
@ApiQuery("maxPrice", "search based on payment priceRange")
@ApiQuery("minPrice", "search based on payment priceRange")
@ApiAuth()
@httpGet("/payments", Guard.authAdmin())
public async getPayments(
@queryParam("limit") limit: string,
@queryParam("page") page: string,
@queryParam("status") status: string,
@queryParam("since") since: string,
@queryParam("maxPrice") maxPrice: string,
@queryParam("minPrice") minPrice: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
status,
since: +since,
maxPrice: +maxPrice,
minPrice: +minPrice,
};
const { ...data } = await this.paymentService.getAllPaymentsForAdminPanelS(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, payments: data.payments });
}
//wallet - withdrawal request
@ApiOperation("Get all seller withdrawal requests ==> login as admin")
@ApiResponse("Successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/withdrawal/requests", Guard.authAdmin())
public async getAllWithdrawalRequests() {
const data = await this.walletService.getWithdrawalRequestsForAdminPanel();
return this.response(data);
}
@ApiOperation("Get withdrawal request CSV")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/withdrawal/requests/csv", Guard.authAdmin())
public async getWithdrawalRequestCsv(@response() res: Response): Promise<void> {
// Set the response headers for CSV
res.setHeader("Content-Type", "text/csv");
res.setHeader("Content-Disposition", "attachment; filename=withdrawal_requests.csv");
// Call the service to generate the CSV stream and pipe it to the response
await this.walletService.getWithdrawalRequestsCsv(res);
}
@ApiOperation("approve a withdrawal with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of withdrawal")
@ApiModel(WithdrawalStatusDTO)
@ApiAuth()
@httpPost("/withdrawal/:id/process", Guard.authAdmin())
public async processWithdrawal(@requestParam("id") id: string, @requestBody() withdrawalStatusDto: WithdrawalStatusDTO) {
const data = await this.walletService.processWithdrawal(id, withdrawalStatusDto);
return this.response(data);
}
@ApiOperation("get all pricing")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/pricing", Guard.authAdmin())
public async getPricing() {
const data = await this.pricingService.getAllPricing();
return this.response(data);
}
@ApiOperation("create pricing")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreatePricingDTO)
@ApiAuth()
@httpPost("/pricing", Guard.authAdmin(), ValidationMiddleware.validateInput(CreatePricingDTO))
public async createPricing(@requestBody() createDto: CreatePricingDTO) {
const data = await this.pricingService.createPricing(createDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update pricing")
@ApiResponse("successful", HttpStatus.Ok)
@ApiModel(UpdatePricingDTO)
@ApiParam("id", "id of pricing", true)
@ApiAuth()
@httpPatch(
"/pricing/:id",
Guard.authAdmin(),
ValidationMiddleware.validateParameter(ParamDto),
ValidationMiddleware.validateInput(UpdatePricingDTO),
)
public async updatePricing(@requestBody() updateDto: UpdatePricingDTO, @requestParam() paramDto: ParamDto) {
const data = await this.pricingService.updatePricing(paramDto.id, updateDto);
return this.response(data);
}
@ApiOperation("delete pricing")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of pricing", true)
@ApiAuth()
@httpDelete("/pricing/:id", Guard.authAdmin(), ValidationMiddleware.validateParameter(ParamDto))
public async deletePricing(@requestParam() paramDto: ParamDto) {
const data = await this.pricingService.deletePricing(paramDto.id);
return this.response(data);
}
}
@@ -0,0 +1,74 @@
import { inject } from "inversify";
import { controller, httpDelete, httpGet, httpPatch, httpPost, queryParam, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { FineRuleDTO } from "../../fine/DTO/fineRule.dto";
import { UpdateFineRuleDTO } from "../../fine/DTO/role-update.dto";
import { FineService } from "../../fine/fine.service";
@ApiTags("Admin Fine")
@controller("/admin/fine")
export class AdminFineController extends BaseController {
@inject(IOCTYPES.FineService) private fineService: FineService;
@ApiOperation("create fine rule")
@ApiResponse("successful", HttpStatus.Ok)
@ApiModel(FineRuleDTO)
@ApiAuth()
@httpPost("/rule", Guard.authAdmin())
public async createRule(@requestBody() fineRuleDto: FineRuleDTO) {
const data = await this.fineService.createFineRule(fineRuleDto);
return this.response(data);
}
@ApiOperation("update fine rule")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of rule")
@ApiModel(UpdateFineRuleDTO)
@ApiAuth()
@httpPatch("/rule/:id/update", Guard.authAdmin())
public async updateRule(@requestParam("id") id: string, @requestBody() updateDto: UpdateFineRuleDTO) {
const data = await this.fineService.updateFineRule(id, updateDto);
return this.response(data);
}
@ApiOperation("get one fine rule")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of rule")
@ApiAuth()
@httpGet("/rule/:id", Guard.authAdmin())
public async getRule(@requestParam("id") id: string) {
const data = await this.fineService.getRuleWithId(id);
return this.response(data);
}
@ApiOperation("delete one fine rule")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of rule")
@ApiAuth()
@httpDelete("/rule/:id/delete", Guard.authAdmin())
public async deleteRule(@requestParam("id") id: string) {
const data = await this.fineService.deleteRule(id);
return this.response(data);
}
@ApiOperation("get all fine ==> login as admin")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of fines data")
@ApiQuery("page", "the page want to get")
@ApiAuth()
@httpGet("/fines", Guard.authAdmin())
public async getAllFinesBySeller(@queryParam("limit") limit: string, @queryParam("page") page: string) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
};
const data = await this.fineService.getAllFines(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, fineList: data.fineList });
}
}
@@ -0,0 +1,89 @@
import { inject } from "inversify";
import { controller, httpDelete, httpGet, httpPatch, httpPost, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { CreateLearningDTO } from "../../learning/DTO/createLearning.dto";
import { CreateLearningCategoryDTO } from "../../learning/DTO/createLearningCategory.dto";
import { UpdateLearningDTO } from "../../learning/DTO/updateLearning.dto";
import { UpdateLearningCategoryDTO } from "../../learning/DTO/updateLearningCategory.dto";
import { LearningService } from "../../learning/learning.service";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin Learning")
@controller("/admin/learning")
export class AdminLearningController extends BaseController {
@inject(IOCTYPES.LearningService) private learningService: LearningService;
//learning
@ApiOperation("create a learning category")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(CreateLearningCategoryDTO)
@ApiAuth()
@httpPost("/category", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateLearningCategoryDTO))
public async createLearningCategory(@requestBody() createLearningCategoryDto: CreateLearningCategoryDTO) {
const data = await this.learningService.createLearningCategory(createLearningCategoryDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("create a learning")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(CreateLearningDTO)
@ApiAuth()
@httpPost("", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateLearningDTO))
public async createLearning(@requestBody() createLearningDto: CreateLearningDTO) {
const data = await this.learningService.createLearning(createLearningDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("get all learning progress")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/progress", Guard.authAdmin())
public async getLearningProgressBySeller() {
const data = await this.learningService.getLearningProgressBySellerForAdmin();
return this.response(data);
}
@ApiOperation("update a learning")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(UpdateLearningDTO)
@ApiParam("id", "id of learning", true)
@ApiAuth()
@httpPatch("/:id/update", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateLearningDTO))
public async updateLearning(@requestBody() updateLearningDto: UpdateLearningDTO, @requestParam("id") learningId: string) {
console.log(learningId, "ssss");
const data = await this.learningService.update(learningId, updateLearningDto);
return this.response(data, HttpStatus.Ok);
}
@ApiOperation("update a learning")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(UpdateLearningCategoryDTO)
@ApiParam("id", "id of learning category", true)
@ApiAuth()
@httpPatch("/category/:id/update", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateLearningCategoryDTO))
public async updateLearningCategory(
@requestBody() updateLearningCategoryDto: UpdateLearningCategoryDTO,
@requestParam("id") catId: string,
) {
const data = await this.learningService.updateCategory(catId, updateLearningCategoryDto);
return this.response(data, HttpStatus.Ok);
}
//TODO:add delete route for category of learning
@ApiOperation("delete a learning")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "learning id", true)
@ApiAuth()
@httpDelete("/:id", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async deleteLearning(@requestParam("id") learningId: string) {
const data = await this.learningService.delete(learningId);
return this.response(data);
}
}
@@ -0,0 +1,165 @@
import { Request } from "express";
import { inject } from "inversify";
import { controller, httpDelete, httpGet, httpPatch, httpPost, request, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiFile, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { UploadService } from "../../../utils/upload.service";
import { MediaService } from "../../media/media.service";
import { ActivateMediaDTO, CreateBannerDTO, CreateSliderDTO, UpdateBannerDTO, UpdateSliderDTO } from "../DTO/media.dto";
@ApiTags("Admin Media")
@controller("/admin/medias")
export class AdminMediaController extends BaseController {
@inject(IOCTYPES.MediaService) private mediaService: MediaService;
//==============================================>
// #region setting and medias
@ApiOperation("get all sliders")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/slider", Guard.authAdmin())
public async getAllSliders() {
const data = await this.mediaService.getAllSlidersForAdmin();
return this.response(data, HttpStatus.Ok);
}
@ApiOperation("get all banners")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/banners", Guard.authAdmin())
public async getAllBanners() {
const data = await this.mediaService.getAllBannersForAdmin();
return this.response(data, HttpStatus.Ok);
}
@ApiOperation("Create a new slider")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateSliderDTO)
@ApiAuth()
@httpPost("/slider", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateSliderDTO))
public async createSlider(@requestBody() createSliderDto: CreateSliderDTO) {
const data = await this.mediaService.createSlider(createSliderDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("Create a new banner")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateBannerDTO)
@ApiAuth()
@httpPost("/banner", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateBannerDTO))
public async createBanner(@requestBody() createBannerDTO: CreateBannerDTO) {
const data = await this.mediaService.createBanner(createBannerDTO);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("Update an existing slider")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("sliderId", "id of slider", true)
@ApiModel(UpdateSliderDTO)
@ApiAuth()
@httpPost("/slider/:sliderId", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateSliderDTO))
public async updateSlider(@requestParam("sliderId") sliderId: string, @requestBody() updateSliderDto: UpdateSliderDTO) {
const data = await this.mediaService.updateSlider(sliderId, updateSliderDto);
return this.response(data, HttpStatus.Ok);
}
@ApiOperation("Update an existing banner")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("bannerId", "id of banner", true)
@ApiModel(UpdateBannerDTO)
@ApiAuth()
@httpPost("/banner/:bannerId", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateBannerDTO))
public async updateBanner(@requestParam("bannerId") bannerId: string, @requestBody() updateBannerDTO: UpdateBannerDTO) {
const data = await this.mediaService.updateBanner(bannerId, updateBannerDTO);
return this.response(data, HttpStatus.Ok);
}
@ApiOperation("Delete a slider")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("sliderId", "id of slider", true)
@ApiAuth()
@httpDelete("/slider/:sliderId", Guard.authAdmin())
public async deleteSlider(@requestParam("sliderId") sliderId: string) {
const data = await this.mediaService.deleteMedia(sliderId, "slider");
return this.response(data);
}
@ApiOperation("Delete a banner")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("bannerId", "id of banner", true)
@ApiAuth()
@httpDelete("/banner/:bannerId", Guard.authAdmin())
public async deleteBanner(@requestParam("bannerId") bannerId: string) {
const data = await this.mediaService.deleteMedia(bannerId, "banner");
return this.response(data);
}
@ApiOperation("Activate/Deactivate a slider")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("sliderId", "id of slider", true)
@ApiModel(ActivateMediaDTO)
@ApiAuth()
@httpPatch("/slider/:sliderId/activate", Guard.authAdmin(), ValidationMiddleware.validateInput(ActivateMediaDTO))
public async activateSlider(@requestParam("sliderId") sliderId: string, @requestBody() activateDto: ActivateMediaDTO) {
const data = await this.mediaService.activateMediaS(sliderId, activateDto.isActive, "slider");
return this.response(data);
}
@ApiOperation("Activate/Deactivate a banner")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("bannerId", "id of banner", true)
@ApiModel(ActivateMediaDTO)
@ApiAuth()
@httpPatch("/banner/:bannerId/activate", Guard.authAdmin(), ValidationMiddleware.validateInput(ActivateMediaDTO))
public async activateMedia(@requestParam("bannerId") bannerId: string, @requestBody() activateDto: ActivateMediaDTO) {
const data = await this.mediaService.activateMediaS(bannerId, activateDto.isActive, "banner");
return this.response(data);
}
@ApiOperation("Upload a single file ==> need to login as admin")
@ApiResponse("File uploaded successfully", HttpStatus.Accepted)
@ApiFile("file", true, true)
@ApiAuth()
@httpPost("/upload/single", Guard.authAdmin(), UploadService.single("file", "media-file"))
public async uploadBlogImage(@request() req: Request) {
// eslint-disable-next-line no-undef
const file = req.file as Express.MulterS3.File;
const data = {
message: "file uploaded!",
url: {
originalname: file.originalname,
size: file.size,
url: file.location,
type: file.mimetype,
},
};
return this.response(data, HttpStatus.Accepted);
}
@ApiOperation("Upload a multiple files ==> need to login as admin")
@ApiResponse("File uploaded successfully", HttpStatus.Accepted)
@ApiFile("file", true, true)
@ApiAuth()
@httpPost("/upload/multiple", Guard.authAdmin(), UploadService.multiple("file", 10, "media-file"))
public async uploadImages(@request() req: Request) {
// eslint-disable-next-line no-undef
const files = req.files as Express.MulterS3.File[];
const data = {
message: "files uploaded!",
urls: files.map((file) => ({
originalname: file.originalname,
size: file.size,
url: file.location,
type: file.mimetype,
})),
};
return this.response(data, HttpStatus.Accepted);
}
// #endregion
}
@@ -0,0 +1,111 @@
import { inject } from "inversify";
import { controller, httpGet, httpPost, queryParam, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { CreateNotificationForAllSellerDTO, CreateNotificationForSpecificDTO } from "../../notification/DTO/notification-create.dto";
import { NotificationType } from "../../notification/models/Abstraction/INotification";
import { NotificationService } from "../../notification/notification.service";
@ApiTags("Admin Notification")
@controller("/admin/notification")
export class AdminNotificationController extends BaseController {
@inject(IOCTYPES.NotificationService) private notificationService: NotificationService;
//notification
@ApiOperation("get all admin notification")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("unread", "specify the read status ==> value = true")
@ApiQuery("type", "specify the type of notification ==> value = ticket | order | fine | wallet | announcement | product | shipment")
@ApiQuery(
"since",
"time of notification created ==> value should be standard timestamp since that time user want like == > Date.now() - (7 * 24 * 60 * 60 * 1000) ==> this mean from a week ago till now ",
)
@ApiAuth()
@httpGet("", Guard.authAdmin())
public async getAllNotification(
@queryParam("limit") limit: string,
@queryParam("page") page: string,
@queryParam("unread") unread: string,
@queryParam("type") type: string,
@queryParam("since") since: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
unread: !!unread,
type: type as NotificationType,
since: +since,
};
const { count, notifications } = await this.notificationService.getAdminNotifications(queries);
const { pager } = this.paginate(count);
return this.response({ pager, notifications });
}
@ApiOperation("mark admin notification as read")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "notification id")
@ApiAuth()
@httpPost("/:id/read", Guard.authAdmin())
public async markAsRead(@requestParam("id") notificationId: string) {
const data = await this.notificationService.markAsRead(notificationId);
return this.response(data);
}
@ApiOperation("create notification for all sellers ===> login as admin")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateNotificationForAllSellerDTO)
@ApiAuth()
@httpPost("/allSeller", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateNotificationForAllSellerDTO))
public async createNotificationForAllSellers(@requestBody() createDto: CreateNotificationForAllSellerDTO) {
const data = await this.notificationService.notifyToAllSeller(createDto);
return this.response(data);
}
@ApiOperation("create notification for Specific sellers ===> login as admin")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateNotificationForSpecificDTO)
@ApiAuth()
@httpPost("/specific-sellers", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateNotificationForSpecificDTO))
public async createNotificationForSpecificSellers(@requestBody() createDto: CreateNotificationForSpecificDTO) {
const data = await this.notificationService.notifyToSpecificSellers(createDto);
return this.response(data);
}
@ApiOperation("get all seller notification that admin sends to all seller")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("unread", "specify the read status ==> value = true")
@ApiQuery("type", "specify the type of notification ==> value = ticket | order | fine | wallet | announcement | product | shipment")
@ApiQuery(
"since",
"time of notification created ==> value should be standard timestamp since that time user want like == > Date.now() - (7 * 24 * 60 * 60 * 1000) ==> this mean from a week ago till now ",
)
@ApiAuth()
@httpGet("/allSeller", Guard.authAdmin())
public async getNotificationForAllSellers(
@queryParam("limit") limit: string,
@queryParam("page") page: string,
@queryParam("unread") unread: string,
@queryParam("type") type: string,
@queryParam("since") since: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
unread: !!unread,
type: type as NotificationType,
since: +since,
};
const { count, notifications } = await this.notificationService.getNotifyAllSellerForAdmin(queries);
const { pager } = this.paginate(count);
return this.response({ pager, notifications });
}
}
@@ -0,0 +1,231 @@
import { Request } from "express";
import { inject } from "inversify";
import { controller, httpDelete, httpGet, httpPost, queryParam, request, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { PaginationDTO } from "../../../common/dto/pagination.dto";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { CancelService } from "../../cancel/cancel.service";
import { CreateCancelDTO } from "../../cancel/DTO/cancel.dto";
import { CancelReasonDTO } from "../../cancel/DTO/cancelReason.dto";
import { OrderService } from "../../order/order.service";
import { ReasonDTO, UpdateReasonDTO } from "../../return/DTO/returnReason.dto";
import { ReturnService } from "../../return/return.service";
import { OwnerRef } from "../../shop/models/Abstraction/IShop";
import { IAdmin } from "../models/Abstraction/IAdmin";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin Order")
@controller("/admin/orders")
export class AdminOrderController extends BaseController {
@inject(IOCTYPES.CancelService) private cancelService: CancelService;
@inject(IOCTYPES.OrderService) private orderService: OrderService;
@inject(IOCTYPES.ReturnService) private returnService: ReturnService;
//==============================================>
//==============================================>
// #region return
@ApiOperation("get list of orders that returned ==> login as admin")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiAuth()
@httpGet("/returns", Guard.authAdmin(), ValidationMiddleware.validateQuery(PaginationDTO))
public async getReturnOrders(@queryParam() paginationDto: PaginationDTO) {
const data = await this.returnService.getAllReturnOrders(paginationDto);
const { pager } = this.paginate(data.count);
return this.response({ pager, returnsOrders: data.returnsOrders });
}
@ApiOperation("approve a return order request with return order id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("returnOrderId", "id of return order", true)
@ApiAuth()
@httpPost("/returns/:returnOrderId/approve", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ORDER]))
public async approveReturnRequest(@requestParam("returnOrderId") returnOrderId: string) {
const data = await this.returnService.approveReturnRequest(returnOrderId);
return this.response(data);
}
@ApiOperation("reject a return order request with return order id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("returnOrderId", "id of return order", true)
@ApiAuth()
@httpPost("/returns/:returnOrderId/reject", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ORDER]))
public async rejectReturnRequest(@requestParam("returnOrderId") returnOrderId: string) {
const data = await this.returnService.rejectReturnRequest(returnOrderId);
return this.response(data);
}
@ApiOperation("complete a return order request with return order id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("returnOrderId", "id of return order", true)
@ApiAuth()
@httpPost("/returns/:returnOrderId/complete", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ORDER]))
public async completeReturnRequest(@requestParam("returnOrderId") returnOrderId: string) {
const data = await this.returnService.completeReturnRequest(returnOrderId);
return this.response(data);
}
@ApiOperation("create a return reason")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(ReasonDTO)
@ApiAuth()
@httpPost("/returns/reasons", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async createReturnReason(@requestBody() reasonDto: ReasonDTO) {
const data = await this.returnService.createReturnReason(reasonDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update a return reason")
@ApiResponse("successful", HttpStatus.Created)
@ApiParam("reasonId", "id of return reason", true)
@ApiModel(UpdateReasonDTO)
@ApiAuth()
@httpPost("/returns/reasons/:reasonId", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async updateReturnReason(@requestParam("reasonId") reasonId: string, @requestBody() updateReasonDto: UpdateReasonDTO) {
const data = await this.returnService.updateReturnReason(reasonId, updateReasonDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("delete a return reason")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("reasonId", "id of return reason", true)
@ApiAuth()
@httpDelete("/returns/reasons/:reasonId", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async deleteReturnReason(@requestParam("reasonId") reasonId: string) {
const data = await this.returnService.deleteReturnReason(reasonId);
return this.response(data);
}
//cancel orders
@ApiOperation("get list of orders that canceled ==> login as admin")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of canceled data")
@ApiQuery("page", "the page want to get")
@ApiAuth()
@httpGet("/cancel", Guard.authAdmin())
public async getCancelOrders(@queryParam("limit") limit: string, @queryParam("page") page: string) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
};
const data = await this.cancelService.getAllCancelOrders(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, cancelsOrders: data.cancelsOrders });
}
@ApiOperation("cancel an item of order ==> login as admin")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateCancelDTO)
@ApiAuth()
@httpPost("/cancel", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateCancelDTO))
public async createCancelByAdmin(@request() req: Request, @requestBody() cancelDto: CreateCancelDTO) {
const admin = req.user as IAdmin;
const data = await this.cancelService.createCancelOrderByAdmin(admin._id.toString(), cancelDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("create a cancel reason")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CancelReasonDTO)
@ApiAuth()
@httpPost("/cancel/reasons", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async createCancelReason(@requestBody() cancelReasonDto: CancelReasonDTO) {
const data = await this.cancelService.createCancelReason(cancelReasonDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("delete a cancel reason with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of reason")
@ApiAuth()
@httpDelete("/reasons/:id/delete", Guard.authAdmin())
public async deleteCancelReason(@requestParam("id") reasonId: string) {
const data = await this.cancelService.deleteCancelReason(reasonId);
return this.response(data);
}
//get orders for admin panel
@ApiOperation("get all native shop orders")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("shipperId", "shipper id")
@ApiQuery("status", "status of payment ==> value = Completed | Cancelled | Pending ")
@ApiQuery(
"since",
"time of order created ==> value should be standard timestamp since that time user want like == > Date.now() - (7 * 24 * 60 * 60 * 1000) ==> this mean from a week ago till now ",
)
@ApiQuery("maxPrice", "search based on payment priceRange")
@ApiQuery("minPrice", "search based on payment priceRange")
@ApiAuth()
@httpGet("/native", Guard.authAdmin())
public async getNativeOrders(
@request() req: Request,
@queryParam("limit") limit: string,
@queryParam("page") page: string,
@queryParam("shipperId") shipperId: string,
@queryParam("status") status: string,
@queryParam("since") since: string,
@queryParam("maxPrice") maxPrice: string,
@queryParam("minPrice") minPrice: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
shipperId,
status,
since: +since,
maxPrice: +maxPrice,
minPrice: +minPrice,
};
const admin = req.user as IAdmin;
const { priceRange, ...data } = await this.orderService.getAdminOrders(admin._id.toString(), queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, orders: data.orders, priceRange });
}
@ApiOperation("get all seller orders")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("shipperId", "shipper id")
@ApiQuery("status", "status of payment ==> value = Completed | Cancelled | Pending ")
@ApiQuery(
"since",
"time of order created ==> value should be standard timestamp since that time user want like == > Date.now() - (7 * 24 * 60 * 60 * 1000) ==> this mean from a week ago till now ",
)
@ApiQuery("maxPrice", "search based on payment priceRange")
@ApiQuery("minPrice", "search based on payment priceRange")
@ApiAuth()
@httpGet("/seller", Guard.authAdmin())
public async getSellerOrders(
@queryParam("limit") limit: string,
@queryParam("page") page: string,
@queryParam("shipperId") shipperId: string,
@queryParam("status") status: string,
@queryParam("since") since: string,
@queryParam("maxPrice") maxPrice: string,
@queryParam("minPrice") minPrice: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
shipperId,
status,
since: +since,
maxPrice: +maxPrice,
minPrice: +minPrice,
};
const { priceRange, ...data } = await this.orderService.getSellerOrders(OwnerRef.SELLER, queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, orders: data.orders, priceRange });
}
}
@@ -0,0 +1,510 @@
import { Request } from "express";
import { inject } from "inversify";
import {
controller,
httpDelete,
httpGet,
httpPatch,
httpPost,
queryParam,
request,
requestBody,
requestParam,
} from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { AddVariantDTO } from "../../product/DTO/addVariant.dto";
import { ProductFinalStepDTO, ProductStepOneDTO, ProductStepTwoDTO } from "../../product/DTO/CreateProduct.dto";
import { CreateAnswerQuestionDTO } from "../../product/DTO/createQuestion.dto";
import { CreateReportQuestionDTO } from "../../product/DTO/createReportQuestion.dto";
import { RejectCommentDTO } from "../../product/DTO/product.dto";
import { ProductParamDto } from "../../product/DTO/productParam.dto";
import {
AddVoiceToProductDTO,
UpdateProductDTO,
UpdateProductFinalStepDTO,
UpdateProductStepOneDTO,
UpdateProductStepTwoDTO,
} from "../../product/DTO/updateProduct.dto";
import { ActivateVariantDTO, UpdateVariantDTO } from "../../product/DTO/updateVariant.dto";
import { ProductRequestService } from "../../product/providers/product-request.service";
import { ProductService } from "../../product/providers/product.service";
import { SellerProductQueryDTO } from "../../seller/DTO/sellerProductQuery.dto";
import { OwnerRef } from "../../shop/models/Abstraction/IShop";
import { AddIncredibleOffersParamDto } from "../DTO/product-param.dto";
import { IAdmin } from "../models/Abstraction/IAdmin";
@ApiTags("Admin Product")
@controller("/admin/products")
export class AdminProductController extends BaseController {
@inject(IOCTYPES.ProductService) private productService: ProductService;
@inject(IOCTYPES.ProductRequestService) private productRequestService: ProductRequestService;
@ApiOperation("first step to create a single product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(ProductStepOneDTO)
@ApiAuth()
@httpPost("/creation/detail", Guard.authAdmin(), ValidationMiddleware.validateInput(ProductStepOneDTO))
public async createProduct(@requestBody() createProductStepOneDto: ProductStepOneDTO, @request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.productService.createProductS(createProductStepOneDto, admin._id.toString());
return this.response(data, HttpStatus.Created);
}
@ApiOperation("get single product details")
@ApiResponse("successfully")
@ApiParam("id", "the product id", true)
@ApiAuth()
@httpGet("/:id/details", Guard.authAdmin())
public async getSingleProduct(@requestParam("id") productId: string) {
const data = await this.productService.getProductDetailsForAdminPanel(+productId);
return this.response(data);
}
@ApiOperation("second step to create a single product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(ProductStepTwoDTO)
@ApiAuth()
@httpPost("/creation/attribute", Guard.authAdmin(), ValidationMiddleware.validateInput(ProductStepTwoDTO))
public async createProductStepTwo(@requestBody() createProductStepTwoDto: ProductStepTwoDTO, @request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.productService.createProductStepTwoS(createProductStepTwoDto, admin._id.toString());
return this.response(data, HttpStatus.Created);
}
@ApiOperation("final step to create a single product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(ProductFinalStepDTO)
@ApiAuth()
@httpPost("/creation/save", Guard.authAdmin(), ValidationMiddleware.validateInput(ProductFinalStepDTO))
public async createProductFinalStep(@requestBody() createProductFinalStepDto: ProductFinalStepDTO, @request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.productService.createProductFinalStepS(createProductFinalStepDto, admin._id.toString());
return this.response(data, HttpStatus.Created);
}
@ApiOperation("add voice to product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(AddVoiceToProductDTO)
@ApiParam("id", "id of product", true)
@ApiAuth()
@httpPatch("/:id/add-voice", Guard.authAdmin(), ValidationMiddleware.validateInput(AddVoiceToProductDTO))
public async addVoiceToProduct(@requestBody() addVoiceToProductDto: AddVoiceToProductDTO, @requestParam("id") productId: string) {
const data = await this.productService.addVoiceToProduct(+productId, addVoiceToProductDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("delete voice from product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiParam("id", "id of product", true)
@ApiAuth()
@httpPatch("/:id/delete-voice", Guard.authAdmin())
public async deleteVoiceToProduct(@requestParam("id") productId: string) {
const data = await this.productService.deleteVoiceToProduct(+productId);
return this.response(data, HttpStatus.Created);
}
//update product if it is in draft status
@ApiOperation("first step to update a single product ==> need to login as admin")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(UpdateProductStepOneDTO)
@ApiAuth()
@httpPatch("/update/detail", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateProductStepOneDTO))
public async updateProductStepOne(@requestBody() updateProductStepOneDto: UpdateProductStepOneDTO, @request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.productService.updateProductStepOneS(updateProductStepOneDto, admin._id.toString());
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("second step to update a single product ==> need to login as admin")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(UpdateProductStepTwoDTO)
@ApiAuth()
@httpPatch("/update/attribute", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateProductStepTwoDTO))
public async updateProductStepTwo(@requestBody() updateProductStepTwoDto: UpdateProductStepTwoDTO, @request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.productService.updateProductStepTwoS(updateProductStepTwoDto, admin._id.toString());
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("final step to update a single product ==> need to login as admin")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(UpdateProductFinalStepDTO)
@ApiAuth()
@httpPatch("/update/save", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateProductFinalStepDTO))
public async updateProductFinalStep(@requestBody() updateProductFinalStepDto: UpdateProductFinalStepDTO, @request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.productService.updateProductFinalStepS(updateProductFinalStepDto, admin._id.toString());
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("mark a product as draft")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of product")
@ApiAuth()
@httpPost("/:id/draft", Guard.authAdmin())
public async draftProduct(@requestParam("id") id: string) {
const data = await this.productService.draft(+id);
return this.response(data);
}
@ApiOperation("approve a product status with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of product")
@ApiAuth()
@httpPost("/:id/approve", Guard.authAdmin())
public async approveProduct(@requestParam("id") id: string) {
const data = await this.productService.approve(+id);
return this.response(data);
}
@ApiOperation("pending a product status with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of product")
@ApiAuth()
@httpPost("/:id/pending", Guard.authAdmin())
public async pendingProduct(@requestParam("id") id: string) {
const data = await this.productService.pending(+id);
return this.response(data);
}
@ApiOperation("reject a product status with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of product")
@ApiModel(RejectCommentDTO)
@ApiAuth()
@httpPost("/:id/reject", Guard.authAdmin(), ValidationMiddleware.validateInput(RejectCommentDTO))
public async rejectProduct(@requestParam("id") id: string, @requestBody() rejectCommentDto: RejectCommentDTO) {
const data = await this.productService.reject(+id, rejectCommentDto);
return this.response(data);
}
@ApiOperation("get products questions")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("productId", "product id")
@ApiQuery("status", "status of question ==> value = Approved | Rejected | Pending ")
@ApiAuth()
@httpGet("/questions", Guard.authAdmin())
public async productsQuestions(
@queryParam("page") page: string,
@queryParam("limit") limit: string,
@queryParam("productId") productId: string,
@queryParam("status") status: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
productId: parseInt(productId),
status,
};
const data = await this.productService.getQuestionsForAdminPanel(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, ...data });
}
@ApiOperation("approve a question status with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of question")
@ApiAuth()
@httpPost("/questions/:id/approve", Guard.authAdmin())
public async approveQuestion(@requestParam("id") id: string) {
const data = await this.productService.approveQuestion(id);
return this.response(data);
}
@ApiOperation("answer to a question with its id ====> need to login as admin")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of question")
@ApiModel(CreateAnswerQuestionDTO)
@ApiAuth()
@httpPost("/questions/:id/answer", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateAnswerQuestionDTO))
public async answerQuestion(@requestParam("id") id: string, @requestBody() createDto: CreateAnswerQuestionDTO, @request() req: Request) {
const user = req.user as IAdmin;
const data = await this.productService.answerQuestion(id, createDto, user._id.toString(), OwnerRef.ADMIN);
return this.response(data);
}
@ApiOperation("get products comments")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("productId", "product id")
@ApiQuery("status", "status of product ==> value = Approved | Rejected | Pending ")
@ApiAuth()
@httpGet("/comments", Guard.authAdmin())
public async productsComments(
@queryParam("page") page: string,
@queryParam("limit") limit: string,
@queryParam("productId") productId: string,
@queryParam("status") status: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
productId: parseInt(productId),
status,
};
const { comments, count } = await this.productService.getCommentsForAdminPanel(queries);
const { pager } = this.paginate(count);
return this.response({ pager, comments });
}
@ApiOperation("approve a comments status with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of question")
@ApiAuth()
@httpPost("/comments/:id/approve", Guard.authAdmin())
public async approveComment(@requestParam("id") id: string) {
const data = await this.productService.approveComment(id);
return this.response(data);
}
//product variant
@ApiOperation("Activate/Deactivate a product variant status")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiParam("id", "id of product", true)
@ApiModel(ActivateVariantDTO)
@ApiAuth()
@httpPost("/:id/variants/activate", Guard.authAdmin(), ValidationMiddleware.validateInput(ActivateVariantDTO))
public async activateVariant(
@requestBody() activateDto: ActivateVariantDTO,
@request() req: Request,
@requestParam("id") productId: string,
) {
const admin = req.user as IAdmin;
const data = await this.productService.activateVariantS(admin._id.toString(), activateDto, +productId);
return this.response(data);
}
@ApiOperation("add a variant to product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(AddVariantDTO)
@ApiParam("id", "id of product", true)
@ApiAuth()
@httpPost("/:id/variants", Guard.authAdmin(), ValidationMiddleware.validateInput(AddVariantDTO))
public async addVariant(@requestBody() addVariantDto: AddVariantDTO, @request() req: Request, @requestParam("id") productId: string) {
const admin = req.user as IAdmin;
const data = await this.productService.addVariantS(addVariantDto, admin._id.toString(), +productId);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("add a popular to product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiParam("id", "id of product", true)
@ApiAuth()
@httpPost("/:id/popular", Guard.authAdmin())
public async addPopularProduct(@requestParam("id") productId: string) {
const data = await this.productService.addPopularProduct(+productId);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("delete a popular product with its productId")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiParam("productId", "id of a product", true)
@ApiAuth()
@httpDelete("/:productId/popular/delete", Guard.authAdmin())
public async deletePopularProduct(@requestParam("productId") productId: string) {
const data = await this.productService.deletePopularProduct(+productId);
return this.response(data);
}
@ApiOperation("add a product to incredible product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiParam("id", "id of product", true)
@ApiParam("variantId", "id of product variant", true)
@ApiAuth()
@httpPost("/:id/incredible/:variantId", Guard.authAdmin(), ValidationMiddleware.validateParameter(AddIncredibleOffersParamDto))
public async addIncredibleProduct(@requestParam() paramDto: AddIncredibleOffersParamDto) {
const data = await this.productService.addIncredibleOffers(paramDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update a variant of product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(UpdateVariantDTO)
@ApiParam("id", "id of product", true)
@ApiAuth()
@httpPatch("/:id/variants", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateVariantDTO))
public async updateVariant(
@requestBody() updateVariantDto: UpdateVariantDTO,
@request() req: Request,
@requestParam("id") productId: string,
) {
const admin = req.user as IAdmin;
const data = await this.productService.updateVariantS(updateVariantDto, admin._id.toString(), +productId);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update a product")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(UpdateProductDTO)
@ApiParam("id", "id of product", true)
@ApiAuth()
@httpPatch("/:id/update", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateProductDTO))
public async updateProduct(@requestBody() updateDto: UpdateProductDTO, @requestParam("id") productId: string) {
const data = await this.productService.updateProduct(+productId, updateDto);
return this.response(data, HttpStatus.Created);
}
/** */
@ApiOperation("get a single product variant ==> need to login as admin")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiParam("id", "id of product", true)
@ApiParam("variantId", "id of product variant", true)
@ApiAuth()
@httpGet("/:id/variants/:variantId", Guard.authAdmin())
public async getSingleVariant(
@request() req: Request,
@requestParam("id") productId: string,
@requestParam("variantId") variantId: string,
) {
const admin = req.user as IAdmin;
const data = await this.productService.getSingleVariant(admin._id.toString(), +productId, variantId);
return this.response(data);
}
@ApiOperation("get all variants of a product ==> need to login as admin")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiParam("id", "id of product", true)
@ApiQuery("page", "the page want to get")
@ApiQuery("limit", "the limit of return data")
@ApiQuery("variant_status", "status of variant ==> value = 1 if want to include those with this status")
@ApiQuery("shipment_method", "shipment method id of product variant")
@ApiQuery("stock", "stock status of variant ==> value 1 if want to include those with this status")
@ApiQuery("include_ad", "include_ad status ==> value = 1 if want to include those with this status ")
@ApiQuery("special_sale", "special_sale status ==> value = 1 if want to include those with this status")
@ApiQuery("sort", "sort variant ==> value = createdAt", false, "array")
@ApiQuery("q", "q is the product variant id or special code of variant that seller specify")
@ApiAuth()
@httpGet("/:id/variants", Guard.authAdmin())
public async getVariant(
@request() req: Request,
@requestParam("id") productId: string,
@queryParam("page") page: string,
@queryParam("limit") limit: string,
@queryParam("variant_status") variantStatus: string,
@queryParam("shipment_method") shipmentMethod: string,
@queryParam("stock") stock: string,
@queryParam("include_ad") includeAd: string,
@queryParam("special_sale") specialSale: string,
@queryParam("sort") sort: string[],
@queryParam("q") q: string,
) {
const queries = {
page: parseInt(page),
limit: parseInt(limit),
variantStatus: !!variantStatus,
shipmentMethod: +shipmentMethod,
stock: !!stock,
includeAd: !!includeAd,
specialSale: !!specialSale,
sort,
q,
};
const admin = req.user as IAdmin;
const data = await this.productService.getAllVariantS(admin._id.toString(), +productId, queries);
return this.response(data);
}
@ApiOperation("delete a product with its id ==> login as admin")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiParam("id", "id of a product", true)
@ApiAuth()
@httpDelete("/:id/delete", Guard.authAdmin(), ValidationMiddleware.validateParameter(ProductParamDto))
public async deleteProduct(@request() req: Request, @requestParam() paramDto: ProductParamDto) {
const admin = req.user as IAdmin;
const data = await this.productService.softDelete(admin._id.toString(), paramDto.id);
return this.response(data);
}
@ApiOperation("create report question for product report")
@ApiAuth()
@ApiModel(CreateReportQuestionDTO)
@httpPost("/report-question", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateReportQuestionDTO))
public async createReportQuestion(@requestBody() createDto: CreateReportQuestionDTO) {
const data = await this.productService.createReportQuestion(createDto);
return this.response(data);
}
//get product
@ApiOperation("get all products reports")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiAuth()
@httpGet("/user-reports", Guard.authAdmin())
public async getProductRequests() {
const data = await this.productService.getProductReportForAdmin();
return this.response(data);
}
@ApiOperation("get all sellers products")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("categoryId", "category id")
@ApiQuery("status", "status of product ==> value = Approved | Rejected | Pending ")
@ApiQuery("q", "q is the product variant id or special code of variant that seller specify")
@ApiQuery("since", "time of product created ==> value should be standard shamsi date since that time user want like == > 1400-01-01")
@ApiAuth()
@httpGet("/seller", Guard.authAdmin(), ValidationMiddleware.validateQuery(SellerProductQueryDTO))
public async getSellerProducts(@queryParam() queryDto: SellerProductQueryDTO) {
const data = await this.productService.getAdminPanelProducts(OwnerRef.SELLER, queryDto);
const { pager } = this.paginate(data.count);
return this.response({ pager, products: data.products, brands: data.brands, categories: data.categories });
}
@ApiOperation("get all native shop products")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("categoryId", "category id")
@ApiQuery("status", "status of product ==> value = Approved | Rejected | Pending ")
@ApiQuery("q", "q is the product variant id or special code of variant that seller specify")
@ApiQuery("since", "time of product created ==> value should be standard shamsi date since that time user want like == > 1400-01-01")
@ApiAuth()
@httpGet("/native", Guard.authAdmin(), ValidationMiddleware.validateQuery(SellerProductQueryDTO))
public async getNativeProducts(@request() req: Request, @queryParam() queryDto: SellerProductQueryDTO) {
const admin = req.user as IAdmin;
const data = await this.productService.findSellerProduct(admin._id.toString(), queryDto);
const { pager } = this.paginate(data.count);
return this.response({ pager, products: data.products, brands: data.brands, categories: data.categories });
}
@ApiOperation("get all product request from seller")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/requests/add-by-admin", Guard.authAdmin())
public async productRequests() {
const data = await this.productRequestService.getProductRequestForAdmin();
return this.response(data);
}
@ApiOperation("get all products filtered by status")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("status", "status of product ==> value = Approved | Rejected | Pending ")
@ApiAuth()
@httpGet("/filter-by-status", Guard.authAdmin())
public async getProductsByStatus(
@queryParam("status") status: string,
@queryParam("limit") limit: string,
@queryParam("page") page: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
status,
};
const data = await this.productService.filterProductsByStatus(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, products: data.products, brands: data.brands, categories: data.categories });
}
}
@@ -0,0 +1,47 @@
import { inject } from "inversify";
import { controller, httpGet, httpPost, requestBody } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { AdminService } from "../admin.service";
import { CreateRoleDTO } from "../DTO/createRole.dto";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin base Role")
@controller("/admin/roles")
export class AdminRoleController extends BaseController {
@inject(IOCTYPES.AdminService) private adminService: AdminService;
//==============================================>
@ApiOperation("create a role")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(CreateRoleDTO)
@ApiAuth()
@httpPost("/", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]), ValidationMiddleware.validateInput(CreateRoleDTO))
public async createRole(@requestBody() createRoleDTO: CreateRoleDTO) {
const data = await this.adminService.createRoleS(createRoleDTO);
return this.response(data);
}
@ApiOperation("get roles")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiAuth()
@httpGet("/roles", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async getRoles() {
const data = await this.adminService.getRoles();
return this.response(data);
}
@ApiOperation("get permissions")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiAuth()
@httpGet("/permissions", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async getPermissions() {
const data = await this.adminService.getPermissions();
return this.response(data);
}
}
@@ -0,0 +1,237 @@
import { inject } from "inversify";
import { controller, httpDelete, httpGet, httpPatch, httpPost, queryParam, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { AboutUsService } from "../../about-us/aboutUs.service";
import { CreateAboutUsDTO } from "../../about-us/DTO/createAboutUsRequest.dto";
import { ContactUsService } from "../../contact-us/contactUs.service";
import { CreateFaqDTO } from "../../faq/DTO/createFaq.dto";
import { UpdateFaqDTO } from "../../faq/DTO/updateFaq.dto";
import { FaqService } from "../../faq/faq.service";
import { CreateJobDTO } from "../../job/DTO/createJob.dto";
import { JobService } from "../../job/job.service";
import { CreateDocumentTypeDTO } from "../../seller/DTO/createDocumentType";
import { SellerService } from "../../seller/seller.service";
import { AdminService } from "../admin.service";
import { CreateContractDTO, UpdateContractDTO } from "../DTO/contract.dto";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin base Setting")
@controller("/admin/settings")
export class AdminSettingController extends BaseController {
@inject(IOCTYPES.AdminService) private adminService: AdminService;
@inject(IOCTYPES.AboutUsService) private aboutUsService: AboutUsService;
@inject(IOCTYPES.ContactUsService) private contactUsService: ContactUsService;
@inject(IOCTYPES.SellerService) private sellerService: SellerService;
@inject(IOCTYPES.FaqService) private faqService: FaqService;
@inject(IOCTYPES.JobService) private jobService: JobService;
//=================================================>
// documentType
@ApiOperation("create document type")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateDocumentTypeDTO)
@ApiAuth()
@httpPost("/document-types", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateDocumentTypeDTO))
public async createDocumentType(@requestBody() createDocumentTypeDto: CreateDocumentTypeDTO) {
const data = await this.sellerService.createDocumentType(createDocumentTypeDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("get content of content")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("/contract", Guard.authAdmin())
public async getContract() {
const data = await this.adminService.getContactContent();
return this.response(data);
}
//contract
@ApiOperation("create a contract")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(CreateContractDTO)
@ApiAuth()
@httpPost("/contract", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateContractDTO))
public async createContract(@requestBody() createContractDto: CreateContractDTO) {
const data = await this.adminService.createContentS(createContractDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update a contract")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(UpdateContractDTO)
@ApiParam("id", "id of contract", true)
@ApiAuth()
@httpPatch("/contract/:id/update", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateContractDTO))
public async updateContract(@requestBody() updateContractDto: UpdateContractDTO) {
const data = await this.adminService.updateContentS(updateContractDto);
return this.response(data, HttpStatus.Ok);
}
//about us
@ApiOperation("create a about us")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(CreateAboutUsDTO)
@ApiAuth()
@httpPost("/about-us", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateAboutUsDTO))
public async createAboutUs(@requestBody() createAboutUs: CreateAboutUsDTO) {
const data = await this.aboutUsService.createAboutUs(createAboutUs);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("delete a about us")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "about id", true)
@ApiAuth()
@httpDelete("/about-us/:id", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async deleteAboutUs(@requestParam("id") aboutId: string) {
const data = await this.aboutUsService.delete(aboutId);
return this.response(data);
}
//contact-us
@ApiOperation("get all contact-us data")
@ApiResponse("successfully", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("email_phone", "email of phone")
@ApiAuth()
@httpGet("/contact-us")
public async getContactUs(
@queryParam("limit") limit: string,
@queryParam("page") page: string,
@queryParam("email_phone") email_phone: string,
) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
email_phone,
};
const { ...data } = await this.contactUsService.getAllContactUs(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, contactUs: data.contactUs });
}
//=================================================>
//faq
@ApiOperation("get all faqs")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@ApiQuery("page", "the page of faq ==> value Faq | Seller")
@httpGet("/faqs", Guard.authAdmin())
public async getAllFaqs(@queryParam("page") page: string) {
const data = await this.faqService.getAllFaqs(page);
return this.response(data);
}
@ApiOperation("create faq")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateFaqDTO)
@ApiAuth()
@httpPost(
"/faqs",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(CreateFaqDTO),
)
public async createFaq(@requestBody() createDto: CreateFaqDTO) {
const data = await this.faqService.createFaq(createDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("update a faq")
@ApiResponse("successful", HttpStatus.Created)
@ApiAuth()
@ApiModel(UpdateFaqDTO)
@httpPatch(
"/faqs",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(UpdateFaqDTO),
)
public async updateFaq(@requestBody() updateDto: UpdateFaqDTO) {
const data = await this.faqService.updateFaq(updateDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("delete a faq")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "faq id", true)
@ApiAuth()
@httpDelete("/faqs/:id", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async deleteFaq(@requestParam("id") faqId: string) {
const data = await this.faqService.deleteFaq(faqId);
return this.response(data);
}
// @ApiOperation("delete a faq")
// @ApiResponse("successful", HttpStatus.Ok)
// @ApiParam("id", "faq id", true)
// @ApiAuth()
// @httpDelete("/faqs/:id", Guard.authAdmin(), Guard.checkAdminRole([RoleEnum.SUPERADMIN, RoleEnum.MODERATOR, RoleEnum.EDITOR]))
// public async deleteFaq(@requestParam("id") faqId: string) {
// const data = await this.faqService.deleteFaq(faqId);
// return this.response(data);
// }
//=================================================>
//job and resume
@ApiOperation("create a job")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateJobDTO)
@ApiAuth()
@httpPost(
"/jobs",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
Guard.checkAdminPermissions([PermissionEnum.JOB]),
ValidationMiddleware.validateInput(CreateJobDTO),
)
public async createJob(@requestBody() createJobDto: CreateJobDTO) {
const data = await this.jobService.createJob(createJobDto);
return this.response(data, HttpStatus.Created);
}
@ApiOperation("delete a job")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "job id", true)
@ApiAuth()
@httpDelete("/jobs/:id/delete", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async deleteJob(@requestParam("id") jobId: string) {
const data = await this.jobService.deleteJob(jobId);
return this.response(data);
}
@ApiOperation("fetch all resume")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiAuth()
@httpGet("/jobs/resume", Guard.authAdmin())
public async getAllResumes(@queryParam("limit") limit: string, @queryParam("page") page: string) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
};
const data = await this.jobService.getAllResumes(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, resumes: data.resumes });
}
@ApiOperation("fetch all resume for a job")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "job id", true)
@ApiAuth()
@httpGet("/jobs/:id/resume", Guard.authAdmin())
public async getJobResumes(@requestParam("id") jobId: string) {
const data = await this.jobService.getResumesForJob(jobId);
return this.response(data);
}
}
@@ -0,0 +1,51 @@
import { inject } from "inversify";
import { controller, httpDelete, httpPatch, httpPost, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { CreateShipProvidersDTO } from "../../shipment/DTO/createShipmentProviders.dto";
import { UpdateShipmentProviderDTO } from "../../shipment/DTO/updateShipment.dto";
import { ShipmentService } from "../../shipment/shipment.service";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin Shipment")
@controller("/admin/shipment")
export class AdminShipmentController extends BaseController {
@inject(IOCTYPES.ShipmentService) private shipmentService: ShipmentService;
//=================================================>
//shipment
@ApiOperation("create a shipment provider")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateShipProvidersDTO)
@ApiAuth()
@httpPost("", Guard.authAdmin(), ValidationMiddleware.validateInput(CreateShipProvidersDTO))
public async createShipmentProvider(@requestBody() createShipmentDto: CreateShipProvidersDTO) {
const data = await this.shipmentService.createShipmentProviderS(createShipmentDto);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("update a shipment provider")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "shipment provider id", true)
@ApiModel(UpdateShipmentProviderDTO)
@ApiAuth()
@httpPatch("/:id", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateShipmentProviderDTO))
public async UpdateShipmentProvider(@requestParam("id") id: string, @requestBody() updateDto: UpdateShipmentProviderDTO) {
const data = await this.shipmentService.updateShipmentProvider(+id, updateDto);
return this.response(data);
}
@ApiOperation("delete a shipment provider")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "shipment provider id", true)
@ApiAuth()
@httpDelete("/:id", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async getShipmentProviders(@requestParam("id") shipperId: string) {
const data = await this.shipmentService.deleteShipper(+shipperId);
return this.response(data);
}
}
@@ -0,0 +1,73 @@
import { Request } from "express";
import { inject } from "inversify";
import { controller, httpPatch, httpPost, request, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { ProductFinalStepDTO, ProductStepOneDTO, ProductStepTwoDTO } from "../../product/DTO/CreateProduct.dto";
import { ProductService } from "../../product/providers/product.service";
import { AdminService } from "../admin.service";
import { IAdmin } from "../models/Abstraction/IAdmin";
@ApiTags("Admin Shop")
@controller("/admin/shop")
export class AdminShopController extends BaseController {
@inject(IOCTYPES.ProductService) private productService: ProductService;
@inject(IOCTYPES.AdminService) private adminService: AdminService;
//###############################################################
//create product for shop
@ApiOperation("first step to create a single product for shop ==> need to login as admin")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(ProductStepOneDTO)
@ApiParam("shopId", "id of shop")
@ApiAuth()
@httpPost("/:shopId/creation/detail", Guard.authAdmin(), ValidationMiddleware.validateInput(ProductStepOneDTO))
public async createProductForShop(@requestBody() createProductStepOneDto: ProductStepOneDTO, @requestParam("shopId") shopId: string) {
const data = await this.productService.createProductForShopS(createProductStepOneDto, shopId);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("second step to create a single product for shop ==> need to login as admin")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(ProductStepTwoDTO)
@ApiParam("shopId", "id of shop")
@ApiAuth()
@httpPost("/:shopId/creation/attribute", Guard.authAdmin(), ValidationMiddleware.validateInput(ProductStepTwoDTO))
public async createProductStepTwoForShop(
@requestBody() createProductStepTwoDto: ProductStepTwoDTO,
@requestParam("shopId") shopId: string,
) {
const data = await this.productService.createProductStepTwoForShopS(createProductStepTwoDto, shopId);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("final step to create a single product for shop ==> need to login as admin")
@ApiResponse("successfully", HttpStatus.Created)
@ApiModel(ProductFinalStepDTO)
@ApiParam("shopId", "id of shop")
@ApiAuth()
@httpPost("/:shopId/creation/save", Guard.authAdmin(), ValidationMiddleware.validateInput(ProductFinalStepDTO))
public async createProductFinalStepForShop(
@requestBody() createProductFinalStepDto: ProductFinalStepDTO,
@requestParam("shopId") shopId: string,
) {
const data = await this.productService.createProductFinalStepForShopS(createProductFinalStepDto, shopId);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("change status of shop chat")
@ApiAuth()
@httpPatch("/chat/status", Guard.authAdmin())
public async changeShopChatStatus(@request() req: Request) {
const admin = req.user as IAdmin;
const data = await this.adminService.changeShopChatStatus(admin._id.toString());
return this.response(data);
}
//###############################################################
}
@@ -0,0 +1,50 @@
import { inject } from "inversify";
import { controller, httpPost, requestBody } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { CreateSiteSettingDTO, UpdateSiteSettingDTO } from "../../site-setting/DTO/siteSetting.dto";
import { SiteSettingService } from "../../site-setting/siteSetting.service";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin Site Setting")
@controller("/admin/site-setting")
export class AdminSiteSettingController extends BaseController {
@inject(IOCTYPES.SiteSettingService) private siteSettingService: SiteSettingService;
@ApiOperation("create a setting for site")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateSiteSettingDTO)
@ApiAuth()
@httpPost(
"",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(CreateSiteSettingDTO),
)
public async createSiteSetting(@requestBody() createDto: CreateSiteSettingDTO) {
const data = await this.siteSettingService.createSiteSetting(createDto);
return this.response(data, HttpStatus.Created);
}
//==>
@ApiOperation("update a setting for site")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(UpdateSiteSettingDTO)
@ApiAuth()
@httpPost(
"/update",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(UpdateSiteSettingDTO),
)
public async updateSiteSetting(@requestBody() updateDto: UpdateSiteSettingDTO) {
const data = await this.siteSettingService.updateSiteSetting(updateDto);
return this.response(data, HttpStatus.Created);
}
}
@@ -0,0 +1,112 @@
import { Request } from "express";
import { inject } from "inversify";
import { controller, httpGet, httpPost, request, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiFile, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { UploadService } from "../../../utils/upload.service";
import { CreateTicketCategoryDTO } from "../../ticket/DTO/createTicket.dto";
import { TicketMessageDTO } from "../../ticket/DTO/ticketMessage.dto";
import { TicketStatus } from "../../ticket/models/Abstraction/ITicket";
import { SenderRef } from "../../ticket/models/Abstraction/ITicketMessage";
import { TicketService } from "../../ticket/ticket.service";
import { IAdmin } from "../models/Abstraction/IAdmin";
import { PermissionEnum } from "../models/Abstraction/IPermission";
@ApiTags("Admin Ticket")
@controller("/admin/tickets")
export class AdminTicketController extends BaseController {
@inject(IOCTYPES.TicketService) private ticketService: TicketService;
@ApiOperation("create a category for ticket")
@ApiResponse("successful", HttpStatus.Created)
@ApiParam("id", "id of ticket", true)
@ApiModel(CreateTicketCategoryDTO)
@ApiAuth()
@httpPost(
"/category",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(CreateTicketCategoryDTO),
)
public async createTicketCategory(@requestBody() createDto: CreateTicketCategoryDTO) {
const data = await this.ticketService.createTicketCategory(createDto);
return this.response(data, HttpStatus.Created);
}
//==>
@ApiOperation("get all tickets")
@ApiResponse("successful", HttpStatus.Ok)
@ApiAuth()
@httpGet("", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async getAllTickets() {
const data = await this.ticketService.getAllTickets();
return this.response(data);
}
@ApiOperation("get ticket with messages")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of ticket", true)
@ApiAuth()
@httpGet("/:id", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async getTicketWithMessages(@requestParam("id") ticketId: string) {
const data = await this.ticketService.getTicketWithMessageForAdmin(ticketId);
return this.response(data);
}
//==>
@ApiOperation("add a message to ticket of seller ")
@ApiResponse("successful", HttpStatus.Created)
@ApiParam("id", "id of ticket", true)
@ApiModel(TicketMessageDTO)
@ApiAuth()
@httpPost(
"/:id/add-message",
Guard.authAdmin(),
Guard.checkAdminPermissions([PermissionEnum.ADMIN]),
ValidationMiddleware.validateInput(TicketMessageDTO),
)
public async addMessageToTicket(
@requestParam("id") ticketId: string,
@request() req: Request,
@requestBody() messageDto: TicketMessageDTO,
) {
const admin = req.user as IAdmin;
const data = await this.ticketService.addMessage(ticketId, admin._id.toString(), SenderRef.ADMIN, messageDto);
return this.response(data, HttpStatus.Created);
}
//==>
@ApiOperation("close status of seller ticket with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of ticket", true)
@ApiAuth()
@httpPost("/:id/close", Guard.authAdmin(), Guard.checkAdminPermissions([PermissionEnum.ADMIN]))
public async closeTicket(@requestParam("id") ticketId: string) {
const data = await this.ticketService.updateTicketStatus(ticketId, TicketStatus.CLOSED);
return this.response(data);
}
@ApiOperation("upload a ticket attachment")
@ApiResponse("successful", HttpStatus.Created)
@ApiFile("attachments", true, true)
@ApiAuth()
@httpPost("/attachment", Guard.authAdmin(), UploadService.multiple("attachments", 5, "ticket-attachments"))
public async ticketAttachment(@request() req: Request) {
// eslint-disable-next-line no-undef
const files = req.files as Express.MulterS3.File[];
const data = {
message: "files uploaded!",
urls: files.map((file) => ({
originalname: file.originalname,
size: file.size,
url: file.location,
type: file.mimetype,
})),
};
return this.response(data, HttpStatus.Accepted);
}
}
@@ -0,0 +1,133 @@
import { inject } from "inversify";
import { controller, httpDelete, httpGet, httpPatch, queryParam, requestBody, requestParam } from "inversify-express-utils";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiTags } from "../../../common/decorator/swggerDocs";
import { PaginationDTO } from "../../../common/dto/pagination.dto";
import { ParamDto } from "../../../common/dto/param.dto";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { SellerService } from "../../seller/seller.service";
import { UserService } from "../../user/user.service";
import { RejectReasonSellerDocumentDto, UpdateSellerDocumentDto } from "../DTO/updateSellerDocument.dto";
@ApiTags("Admin Users managements")
@controller("/admin")
export class AdminUsersController extends BaseController {
@inject(IOCTYPES.SellerService) private sellerService: SellerService;
@inject(IOCTYPES.UserService) private userService: UserService;
@ApiOperation("get all sellers")
@ApiQuery("page", "page number", false)
@ApiQuery("limit", "limit per page", false)
@ApiAuth()
@httpGet("/sellers", Guard.authAdmin(), ValidationMiddleware.validateQuery(PaginationDTO))
public async getSellers(@queryParam() queryDto: PaginationDTO) {
const { count, sellers } = await this.sellerService.getSellersWithDocumentsAndContracts(queryDto);
const { pager } = this.paginate(count);
return this.response({ sellers, pager });
}
@ApiOperation("get all wholesale requests")
@ApiQuery("page", "page number", false)
@ApiQuery("limit", "limit per page", false)
@ApiAuth()
@httpGet("/sellers/wholesale-requests", Guard.authAdmin(), ValidationMiddleware.validateQuery(PaginationDTO))
public async getWholesaleRequests(@queryParam() queryDto: PaginationDTO) {
const { count, requests } = await this.sellerService.getWholesaleRequests(queryDto);
const { pager } = this.paginate(count);
return this.response({ requests, pager });
}
@ApiOperation("get single seller")
@ApiParam("id", "the seller id", true)
@ApiAuth()
@httpGet("/sellers/:id", Guard.authAdmin(), ValidationMiddleware.validateParameter(ParamDto))
public async getSingleSeller(@requestParam() paramDto: ParamDto) {
const data = await this.sellerService.getSellerById(paramDto.id);
return this.response(data);
}
@ApiOperation("approve seller wholesale")
@ApiParam("id", "the seller id", true)
@ApiAuth()
@httpPatch("/sellers/wholesale-requests/:id", Guard.authAdmin(), ValidationMiddleware.validateParameter(ParamDto))
public async approveSellerWholesale(@requestParam() paramDto: ParamDto) {
const data = await this.sellerService.approveSellerWholesale(paramDto.id);
return this.response(data);
}
@ApiOperation("approve seller registration")
@ApiParam("id", "the seller id", true)
@ApiAuth()
@httpPatch("/sellers/approve-register/:id", Guard.authAdmin(), ValidationMiddleware.validateParameter(ParamDto))
public async approveSellerRegistration(@requestParam() paramDto: ParamDto) {
const data = await this.sellerService.approveSellerRegistration(paramDto.id);
return this.response(data);
}
@ApiOperation("approve seller contract")
@ApiParam("id", "the seller id", true)
@ApiAuth()
@httpPatch("/sellers/approve-contract/:id", Guard.authAdmin(), ValidationMiddleware.validateParameter(ParamDto))
public async approveSellerContract(@requestParam() paramDto: ParamDto) {
const data = await this.sellerService.approveSellerContract(paramDto.id);
return this.response(data);
}
@ApiOperation("approve seller document")
@ApiParam("id", "the seller id", true)
@ApiModel(UpdateSellerDocumentDto)
@ApiAuth()
@httpPatch(
"/sellers/document/:id",
Guard.authAdmin(),
ValidationMiddleware.validateParameter(ParamDto),
ValidationMiddleware.validateInput(UpdateSellerDocumentDto),
)
public async approveSellerDocs(@requestParam() paramDto: ParamDto, @requestBody() updateDto: UpdateSellerDocumentDto) {
const data = await this.sellerService.updateSellerDocumentStatus(paramDto.id, updateDto);
return this.response(data);
}
@ApiOperation("delete seller document")
@ApiParam("id", "the document id", true)
@ApiModel(RejectReasonSellerDocumentDto)
@ApiAuth()
@httpDelete(
"/sellers/document/:id",
Guard.authAdmin(),
ValidationMiddleware.validateParameter(ParamDto),
ValidationMiddleware.validateInput(RejectReasonSellerDocumentDto),
)
public async deleteSellerDocs(@requestParam() paramDto: ParamDto, @requestBody() rejectDto: RejectReasonSellerDocumentDto) {
const data = await this.sellerService.deleteSellerDocument(paramDto.id, rejectDto.reason);
return this.response(data);
}
// @ApiOperation("get all seller documents")
// @ApiAuth()
// @httpGet("/sellers/documents", Guard.authAdmin())
// public async getSellerDocuments() {}
@ApiOperation("get all users")
@ApiQuery("page", "page number", false)
@ApiQuery("limit", "limit per page", false)
@ApiAuth()
@httpGet("/users", Guard.authAdmin(), ValidationMiddleware.validateQuery(PaginationDTO))
public async getUsers(@queryParam() queryDto: PaginationDTO) {
const { count, users } = await this.userService.getAllUsers(queryDto);
const { pager } = this.paginate(count);
return this.response({ users, pager });
}
@ApiOperation("get single user")
@ApiParam("id", "the user id", true)
@ApiAuth()
@httpGet("/users/:id", Guard.authAdmin(), ValidationMiddleware.validateParameter(ParamDto))
public async getSingleUser(@requestParam() paramDto: ParamDto) {
const data = await this.userService.getUserById(paramDto.id);
return this.response(data);
}
}
@@ -0,0 +1,62 @@
import rateLimit from "express-rate-limit";
import { inject } from "inversify";
import { controller, httpDelete, httpPatch, httpPost, requestBody, requestParam } from "inversify-express-utils";
import { HttpStatus } from "../../../common";
import { BaseController } from "../../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiResponse, ApiTags } from "../../../common/decorator/swggerDocs";
import { appConfig } from "../../../core/config/app.config";
import { Guard } from "../../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../../IOC/ioc.types";
import { CreateWarrantyDTO } from "../../warranty/DTO/createWarranty.dto";
import { UpdateWarrantyDTO } from "../../warranty/DTO/UpdateWarranty.dto";
import { WarrantyService } from "../../warranty/warranty.service";
@ApiTags("Admin Warranty")
@controller("/admin/warranty")
export class AdminWarrantyController extends BaseController {
@inject(IOCTYPES.WarrantyService) private warrantyService: WarrantyService;
//=================================================>
//warranty
@ApiOperation("request to create a new warranty ==> login as admin")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(CreateWarrantyDTO)
@ApiAuth()
@httpPost("", rateLimit(appConfig.rate), Guard.authAdmin(), ValidationMiddleware.validateInput(CreateWarrantyDTO))
public async createWarranty(@requestBody() createWarrantyDto: CreateWarrantyDTO) {
const data = await this.warrantyService.createWarrantyS(createWarrantyDto);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("update a warranty with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of warranty")
@ApiModel(UpdateWarrantyDTO)
@ApiAuth()
@httpPatch("/:id", Guard.authAdmin(), ValidationMiddleware.validateInput(UpdateWarrantyDTO))
public async UpdateWarranty(@requestParam("id") id: string, @requestBody() updateDto: UpdateWarrantyDTO) {
const data = await this.warrantyService.updateById(+id, updateDto);
return this.response({ data });
}
@ApiOperation("approve a warranty status with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of warranty")
@ApiAuth()
@httpPost("/:id/approve", Guard.authAdmin())
public async approveWarranty(@requestParam("id") id: string) {
const data = await this.warrantyService.approve(+id);
return this.response(data);
}
@ApiOperation("delete a warranty with its id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("id", "id of warranty")
@ApiAuth()
@httpDelete("/:id", Guard.authAdmin())
public async deleteWarranty(@requestParam("id") id: string) {
const data = await this.warrantyService.deleteById(+id);
return this.response(data);
}
}
+20
View File
@@ -0,0 +1,20 @@
import "./controllers/admin.controller";
import "./controllers/coupon.controller";
import "./controllers/blog.controller";
import "./controllers/brand.controller";
import "./controllers/category.controller";
import "./controllers/financial.controller";
import "./controllers/learning.controller";
import "./controllers/media.controller";
import "./controllers/notification.controller";
import "./controllers/order.controller";
import "./controllers/product.controller";
import "./controllers/setting.controller";
import "./controllers/shipment.controller";
import "./controllers/ticket.controller";
import "./controllers/warranty.controller";
import "./controllers/fine.controller";
import "./controllers/siteSetting.controller";
import "./controllers/users.controller";
import "./controllers/role.controller";
import "./controllers/shop.controller";
@@ -0,0 +1,13 @@
import { Types } from "mongoose";
export interface IAdmin {
_id: Types.ObjectId;
fullName: string;
userName: string;
email: string;
password: string;
phoneNumber: string;
role: Types.ObjectId;
permissions: Types.ObjectId[];
createdBy: Types.ObjectId;
}
@@ -0,0 +1,3 @@
export interface IContract {
content: string;
}
@@ -0,0 +1,41 @@
import { Types } from "mongoose";
import { DisplayLocations } from "../../../../common/enums/media.enum";
// export interface IMedia {
// _id: Types.ObjectId;
// type: MediaType;
// name: string;
// images: IImage[];
// displayLocation: DisplayLocations;
// isActive: boolean;
// }
// export interface IImage {
// imageUrl: string;
// altText: string;
// title: string;
// linkUrl: string;
// }
export interface IBanner {
_id: Types.ObjectId;
// displayLocation: DisplayLocations;
imageUrl: string;
altText: string;
title: string;
linkUrl: string;
isActive: boolean;
order: number;
}
export interface ISlider {
_id: Types.ObjectId;
displayLocation: DisplayLocations;
imageUrl: string;
altText: string;
title: string;
linkUrl: string;
isActive: boolean;
// order: number;
}
@@ -0,0 +1,18 @@
export interface IPermission {
name: PermissionEnum;
}
export enum PermissionEnum {
BRAND = "BRAND",
CATEGORY = "CATEGORY",
ITEM = "ITEM",
PRODUCT = "PRODUCT",
ORDER = "ORDER",
USER = "USER",
BLOG = "BLOG",
DISCOUNT = "DISCOUNT",
SITE = "SITE",
ADMIN = "ADMIN",
SHOP = "SHOP",
JOB = "JOB",
}
@@ -0,0 +1,10 @@
export interface IRole {
name: RoleEnum;
}
export enum RoleEnum {
SUPERADMIN = "SUPERADMIN",
MODERATOR = "MODERATOR",
EDITOR = "EDITOR",
ADMIN = "ADMIN",
}
+32
View File
@@ -0,0 +1,32 @@
import { hash } from "bcrypt";
import { CallbackError, Schema, model } from "mongoose";
import { IAdmin } from "./Abstraction/IAdmin";
const adminSchema = new Schema<IAdmin>(
{
fullName: { type: String, required: true },
userName: { type: String, required: true },
email: { type: String, unique: true },
password: { type: String, minlength: 6 },
phoneNumber: { type: String, unique: true },
role: { type: Schema.Types.ObjectId, ref: "Role", required: true },
permissions: { type: [Schema.Types.ObjectId], ref: "Permission" },
createdBy: { type: Schema.Types.ObjectId, ref: "Admin" },
},
{ timestamps: true, toJSON: { versionKey: false }, id: false },
);
adminSchema.pre("save", async function (next) {
if (!this.isModified("password")) return next();
try {
this.password = await hash(this.password, 10);
next();
} catch (error) {
next(error as CallbackError);
}
});
const adminModel = model<IAdmin>("Admin", adminSchema);
export { adminModel as AdminModel };
@@ -0,0 +1,13 @@
import { Schema, model } from "mongoose";
import { IContract } from "./Abstraction/IContract";
const ContractSchema = new Schema<IContract>(
{
content: { type: String, required: true },
},
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
);
const ContractModel = model<IContract>("Contract", ContractSchema);
export { ContractModel };
+45
View File
@@ -0,0 +1,45 @@
import mongoose, { Schema, model } from "mongoose";
import { IBanner, ISlider } from "./Abstraction/IMedia";
import { DisplayLocations } from "../../../common/enums/media.enum";
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
const AutoIncrement = require("mongoose-sequence")(mongoose);
const SliderSchema = new Schema<ISlider>(
{
displayLocation: { type: String, enum: DisplayLocations, required: true },
imageUrl: { type: String, required: true },
altText: { type: String, default: "" },
title: { type: String, default: "" },
linkUrl: { type: String, required: true },
isActive: { type: Boolean, default: true },
// order: Number,
},
{
timestamps: true,
toJSON: { versionKey: false },
id: false,
},
);
SliderSchema.plugin(AutoIncrement, { id: "slider_id", inc_field: "order" });
const BannerSchema = new Schema<IBanner>(
{
// displayLocation: { type: String, enum: DisplayLocations, required: true },
imageUrl: { type: String, required: true },
altText: { type: String, default: "" },
title: { type: String, default: "" },
linkUrl: { type: String, required: true },
isActive: { type: Boolean, default: true },
order: { type: Number, required: true },
},
{ timestamps: true, toJSON: { versionKey: false }, id: false },
);
const SliderModel = model<ISlider>("Slider", SliderSchema);
const BannerModel = model<IBanner>("Banner", BannerSchema);
export { SliderModel, BannerModel };
@@ -0,0 +1,14 @@
import { Schema, model } from "mongoose";
import { IPermission, PermissionEnum } from "./Abstraction/IPermission";
const permissionSchema = new Schema<IPermission>(
{
name: { type: String, enum: PermissionEnum, unique: true, required: true },
},
{ timestamps: true, toJSON: { versionKey: false, virtuals: true }, id: false },
);
const PermissionModel = model<IPermission>("Permission", permissionSchema);
export { PermissionModel };
+14
View File
@@ -0,0 +1,14 @@
import { Schema, model } from "mongoose";
import { IRole, RoleEnum } from "./Abstraction/IRole";
const roleSchema = new Schema<IRole>(
{
name: { type: String, enum: RoleEnum, required: true },
},
{ timestamps: true, toJSON: { versionKey: false, virtuals: true }, id: false },
);
const RoleModel = model<IRole>("Role", roleSchema);
export { RoleModel };
+90
View File
@@ -0,0 +1,90 @@
import { BaseRepository } from "../../../common/base/repository";
import { IAdmin } from "../models/Abstraction/IAdmin";
import { AdminModel } from "../models/admin.model";
class AdminRepository extends BaseRepository<IAdmin> {
constructor() {
super(AdminModel);
}
async findByPhone(phoneNumber: string) {
return this.model.findOne({ phoneNumber });
}
async findByEmail(email: string) {
return this.model.findOne({ email });
}
async findAdmin(email: string, phoneNumber: string) {
return this.model.findOne({
$or: [{ email }, { phoneNumber }],
});
}
async getAdmins() {
const data = await this.model.aggregate([
{
$lookup: {
from: "roles",
localField: "role",
foreignField: "_id",
as: "role",
},
},
{
$unwind: "$role",
},
{
$lookup: {
from: "permissions",
localField: "permissions",
foreignField: "_id",
as: "permissions",
},
},
{
$lookup: {
from: "admins",
localField: "createdBy",
foreignField: "_id",
as: "createdBy",
},
},
{
$unwind: "$createdBy",
},
{
$project: {
fullName: 1,
userName: 1,
email: 1,
phoneNumber: 1,
role: {
_id: "$role._id",
name: "$role.name",
createdAt: "$role.createdAt",
updatedAt: "$role.updatedAt",
},
permissions: "$permissions.name",
createdBy: {
_id: 1,
fullName: 1,
userName: 1,
email: 1,
phoneNumber: 1,
},
createdAt: 1,
updatedAt: 1,
},
},
{
$sort: { createdAt: -1 },
},
]);
return data;
}
}
function createAdminRepository(): AdminRepository {
return new AdminRepository();
}
export { AdminRepository, createAdminRepository };
+15
View File
@@ -0,0 +1,15 @@
import { BaseRepository } from "../../../common/base/repository";
import { IContract } from "../models/Abstraction/IContract";
import { ContractModel } from "../models/contract.model";
class ContractRepository extends BaseRepository<IContract> {
constructor() {
super(ContractModel);
}
}
function createContractRepository(): ContractRepository {
return new ContractRepository();
}
export { ContractRepository, createContractRepository };
+24
View File
@@ -0,0 +1,24 @@
import { BaseRepository } from "../../../common/base/repository";
import { IBanner, ISlider } from "../models/Abstraction/IMedia";
import { BannerModel, SliderModel } from "../models/media.model";
class SliderRepository extends BaseRepository<ISlider> {
constructor() {
super(SliderModel);
}
}
function createSliderRepo(): SliderRepository {
return new SliderRepository();
}
class BannerRepository extends BaseRepository<IBanner> {
constructor() {
super(BannerModel);
}
}
function createBannerRepo(): BannerRepository {
return new BannerRepository();
}
export { SliderRepository, BannerRepository, createSliderRepo, createBannerRepo };
@@ -0,0 +1,15 @@
import { BaseRepository } from "../../../common/base/repository";
import { IPermission } from "../models/Abstraction/IPermission";
import { PermissionModel } from "../models/permission.model";
class PermissionRepository extends BaseRepository<IPermission> {
constructor() {
super(PermissionModel);
}
}
function createPermissionRepo(): PermissionRepository {
return new PermissionRepository();
}
export { PermissionRepository, createPermissionRepo };
+15
View File
@@ -0,0 +1,15 @@
import { BaseRepository } from "../../../common/base/repository";
import { IRole } from "../models/Abstraction/IRole";
import { RoleModel } from "../models/role.model";
class RoleRepository extends BaseRepository<IRole> {
constructor() {
super(RoleModel);
}
}
function createRoleRepo(): RoleRepository {
return new RoleRepository();
}
export { RoleRepository, createRoleRepo };
+21
View File
@@ -0,0 +1,21 @@
import { Expose } from "class-transformer";
import { IsEmail, IsMobilePhone, IsNotEmpty, IsOptional } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { AuthMessage } from "../../../common/enums/message.enum";
export class AuthDTO {
@Expose()
@IsOptional()
@IsNotEmpty({ message: AuthMessage.NumberNotEmpty })
@IsMobilePhone("fa-IR", undefined, { message: AuthMessage.IncorrectNumber })
@ApiProperty({ type: "string", description: "phone number", example: "09182532674" })
phone?: string;
@Expose()
@IsOptional()
@IsNotEmpty({ message: AuthMessage.EmailNotEmpty })
@IsEmail(undefined, { message: AuthMessage.IncorrectEmail })
@ApiProperty({ type: "string", description: "email", example: "me@gmail.com" })
email?: string;
}
+26
View File
@@ -0,0 +1,26 @@
import { Expose } from "class-transformer";
import { IsEmail, IsMobilePhone, IsNotEmpty, IsOptional, Length } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { AuthMessage } from "../../../common/enums/message.enum";
export class AuthCheckOtpDTO {
@Expose()
@IsOptional()
@IsNotEmpty({ message: AuthMessage.NumberNotEmpty })
@IsMobilePhone("fa-IR", undefined, { message: AuthMessage.IncorrectNumber })
@ApiProperty({ type: "string", description: "The phone number of the user", example: "09182532674" })
phone?: string;
@Expose()
@IsOptional()
@IsNotEmpty({ message: AuthMessage.EmailNotEmpty })
@IsEmail(undefined, { message: AuthMessage.IncorrectEmail })
@ApiProperty({ type: "string", description: "email", example: "me@gmail.com" })
email?: string;
@Expose()
@IsNotEmpty({ message: AuthMessage.OtpNotEmpty })
@Length(5, 5, { message: AuthMessage.OtpCodeLength })
@ApiProperty({ type: "string", description: "The otpcode", example: "52648" })
otpCode: string;
}
+12
View File
@@ -0,0 +1,12 @@
import { Expose } from "class-transformer";
import { IsNotEmpty } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { AuthMessage } from "../../../common/enums/message.enum";
export class TokenDto {
@Expose()
@IsNotEmpty({ message: AuthMessage.TokenNotEmpty })
@ApiProperty({ type: "string", description: "refresh token", example: "asd3r23wf43t54terrg45y" })
token: string;
}
+260
View File
@@ -0,0 +1,260 @@
import { randomInt } from "crypto";
import Dayjs from "dayjs";
import { inject, injectable } from "inversify";
import { startSession } from "mongoose";
import { IOCTYPES } from "../../IOC/ioc.types";
import { UserRepository } from "../user/user.repository";
import { AuthDTO } from "./DTO/Auth.dto";
import { AuthCheckOtpDTO } from "./DTO/AuthCheckOtp.dto";
import { TokenDto } from "./DTO/Token.dto";
import { AuthMessage, CommonMessage } from "../../common/enums/message.enum";
import { BadRequestError, UnauthorizedError } from "../../core/app/app.errors";
import { CacheService } from "../../utils/cache.service";
import { EmailService } from "../../utils/email.service";
import { SMS } from "../../utils/sms.service";
import { SellerRepository } from "../seller/seller.repository";
import { OwnerRef } from "../shop/models/Abstraction/IShop";
import { ShopService } from "../shop/shop.service";
import { TokenRepository } from "../token/token.repository";
import { TokenService } from "../token/token.service";
import { ChangeEmailDTO, VerifyEmailDTO } from "../user/DTO/userUpdate.dto";
import { WalletService } from "../wallet/wallet.service";
@injectable()
class AuthService {
// private logger = new Logger(AuthService.name)
@inject(IOCTYPES.TokenService) private tokenService: TokenService;
@inject(IOCTYPES.TokenRepository) private tokenRepository: TokenRepository;
@inject(IOCTYPES.UserRepository) userRepository: UserRepository;
@inject(IOCTYPES.SellerRepository) sellerRepository: SellerRepository;
@inject(IOCTYPES.CacheService) private cache: CacheService;
@inject(IOCTYPES.ShopService) private shopService: ShopService;
@inject(IOCTYPES.WalletService) walletService: WalletService;
//########################################################
//########################################################
async authenticate(authDto: AuthDTO, type: string) {
const { phone, email } = authDto;
if (!phone && !email) throw new BadRequestError(AuthMessage.EmailOrPhone);
const identifier = phone || email!,
isPhone = !!phone,
field = isPhone ? "phoneNumber" : "email";
const existUser = await this.getExistUser(type, field, identifier);
// Check if OTP already exists in cache
const existOtp = await this.cache.getAsync(`${identifier}:Login:Otp`);
if (existOtp) {
const ttl = await this.cache.getTtlAsync(`${identifier}:Login:Otp`);
return {
message: isPhone ? AuthMessage.OtpAlreadySentToNo : AuthMessage.OtpAlreadySentToEmail,
hasAccount: !!existUser,
ttl: ttl ? ttl / 1000 : 0, // Convert milliseconds to seconds
identifier,
};
}
const otpCode = this.generateOtpCode();
//=================>
// Send OTP code
if (isPhone) {
await SMS.sendSmsVerifyCode(phone, otpCode);
} else {
await EmailService.sendVerifyEmail(email!, otpCode);
}
//<==================
await this.saveOtpCodeInCache(otpCode, identifier);
const ttl = await this.cache.getTtlAsync(`${identifier}:Login:Otp`);
return {
message: isPhone ? AuthMessage.OtpSentToNo : AuthMessage.OtpSentToEmail,
hasAccount: !!existUser,
ttl: ttl ? ttl / 1000 : 120, // Convert milliseconds to seconds, default to 120
identifier,
};
}
//########################################################
//########################################################
async loginOtpS(loginDto: AuthCheckOtpDTO, type: string) {
const { phone, otpCode, email } = loginDto;
if (!phone && !email) throw new BadRequestError(AuthMessage.EmailOrPhone);
const identifier = phone || email!;
// Verify OTP
await this.verifyOtp(identifier, otpCode);
const Info = type === "user" ? await this.processUserLogin(identifier) : await this.processSellerLogin(identifier);
// Generate tokens
const tokens = await this.tokenService.generateAuthTokens({ sub: Info._id.toString() });
return {
tokenType: "Bearer",
...tokens,
message: AuthMessage.SuccessLogin,
Info,
};
}
//########################################################
//########################################################
async logoutS(id: string) {
const token = await this.tokenService.revokeTokens(id);
if (!token) throw new BadRequestError(AuthMessage.NeedLogin);
return {
message: AuthMessage.LoggedOut,
};
}
//########################################################
//########################################################
public async refreshTokensS(data: TokenDto) {
const refTokens = await this.tokenRepository.findByToken(data.token);
if (!refTokens) throw new BadRequestError(AuthMessage.TokenNotFound);
// let userInfo;
// if (data.type === process.env.USER_TYPE) {
// userInfo = await this.userRepository.findById(refTokens?.userId.toString());
// } else if (data.type === process.env.SELLER_TYPE) {
// userInfo = await this.sellerRepository.findById(refTokens?.userId.toString());
// }
// if (!userInfo) {
// //delete token because there is no user for that like casacade
// await this.tokenRepository.delete(refTokens._id.toString());
// throw new NotFoundError(AuthMessage.TokenNotAssigned);
// }
if (Dayjs(refTokens.refreshTokenExpire).isBefore(Dayjs())) {
await this.tokenRepository.delete(refTokens._id.toString());
throw new UnauthorizedError(AuthMessage.TokenExpired);
}
const { accessToken, refreshToken } = await this.tokenService.generateAuthTokens({ sub: refTokens.userId.toString() });
return {
tokenType: "Bearer",
accessToken,
refreshToken,
};
}
//########################################################
//########################################################
async verifyUserEmail(verifyEmailDto: VerifyEmailDTO, userId: string) {
const { email, otpCode } = verifyEmailDto;
await this.verifyOtp(email, otpCode, "verify");
const updatedUser = await this.userRepository.model.findByIdAndUpdate(userId, { email }, { new: true });
if (!updatedUser) throw new BadRequestError(CommonMessage.NotValid);
return {
message: CommonMessage.Updated,
updatedUser,
};
}
//########################################################
//########################################################
async changeUserEmail(changeEmailDto: ChangeEmailDTO, userId: string) {
const existOtp = await this.cache.getAsync(`${changeEmailDto.email}:verify:Otp`);
if (existOtp) return { message: AuthMessage.OtpAlreadySentToEmail };
const existUser = await this.userRepository.model.exists({ email: changeEmailDto.email, _id: { $ne: userId } });
if (existUser) throw new BadRequestError(AuthMessage.EmailExists);
const otpCode = this.generateOtpCode();
await EmailService.verifyConnection();
await EmailService.sendVerifyEmail(changeEmailDto.email, otpCode);
await this.saveOtpCodeInCache(otpCode, changeEmailDto.email, "verify");
return {
message: AuthMessage.EmailSent,
email: changeEmailDto.email,
};
}
//helpers
//############################
private async saveOtpCodeInCache(otpCode: string, identifier: string, cacheKey: string = "Login") {
const key = `${identifier}:${cacheKey}:Otp`;
this.cache.set(key, otpCode, 120); // 120 seconds TTL
}
//############################
private async verifyOtp(identifier: string, otpCode: string, cacheKey: string = "Login") {
const key = `${identifier}:${cacheKey}:Otp`;
const codeInCache = await this.cache.getAsync(key);
if (!codeInCache || otpCode !== codeInCache) {
throw new BadRequestError(AuthMessage.OtpIncorrect);
}
// Remove OTP from cache
await this.cache.delAsync(key);
}
//############################
private generateOtpCode(): string {
return randomInt(10000, 99999).toString();
}
//############################
private async processUserLogin(identifier: string) {
let user = (await this.userRepository.findUserByPhone(identifier)) || (await this.userRepository.findUserByEmail(identifier));
const userName = `U-${Date.now().toString().slice(2)}`;
if (!user) {
const userData = identifier.includes("@")
? { email: identifier, fullName: userName }
: { phoneNumber: identifier, fullName: userName };
user = await this.userRepository.model.create(userData);
}
return user;
}
//############################
private async processSellerLogin(identifier: string) {
const session = await startSession();
session.startTransaction();
try {
let seller = (await this.sellerRepository.findByPhone(identifier)) || (await this.sellerRepository.findByEmail(identifier));
const userName = `U-${Date.now().toString().slice(2)}`;
if (!seller) {
const sellerData = identifier.includes("@")
? { email: identifier, fullName: userName }
: { phoneNumber: identifier, fullName: userName };
seller = (await this.sellerRepository.model.create([{ ...sellerData }], { session }))[0];
await this.shopService.createDefaultShop(seller._id.toString(), OwnerRef.SELLER, session);
await this.walletService.createSellerWallet(seller._id.toString(), session);
}
await session.commitTransaction();
return seller;
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
await session.endSession();
}
}
//############################
private async getExistUser(type: string, field: string, identifier: string) {
return type === "user"
? await this.userRepository.model.findOne({ [field]: identifier })
: await this.sellerRepository.model.findOne({ [field]: identifier });
}
}
export { AuthService };
+69
View File
@@ -0,0 +1,69 @@
import { Expose, Transform, Type, plainToClass } from "class-transformer";
import { TimeService } from "../../../utils/time.service";
import { IBlog } from "../models/Abstraction/IBlog";
export class BlogAuthorDTO {
@Expose()
fullName: string;
}
export class BlogCategoryDTO {
@Expose()
_id: string;
@Expose()
title_fa: string;
@Expose()
slug: string;
@Expose()
description: string;
}
export class BlogDTO {
@Expose()
_id: string;
@Expose()
title_fa: string;
@Expose()
slug: string;
@Expose()
@Type(() => BlogCategoryDTO)
category: BlogCategoryDTO;
@Expose()
description: string;
@Expose()
content: string;
@Expose()
@Type(() => BlogAuthorDTO)
author: BlogAuthorDTO;
@Expose()
view: number;
@Expose()
tags: string;
@Expose()
imageUrl: string;
@Expose()
@Transform(({ value }) => TimeService.convertGregorianToPersian(new Date(value), true))
createdAt: string;
public static transformBlog(data: IBlog): BlogDTO {
const blogDTO = plainToClass(BlogDTO, data, {
excludeExtraneousValues: true,
enableImplicitConversion: true,
});
return blogDTO;
}
}
+112
View File
@@ -0,0 +1,112 @@
import { Expose } from "class-transformer";
import { IsArray, IsNotEmpty, IsOptional, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { BlogCategoryModel } from "../models/blogCategory.model";
export class CreateBlogDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "farsi title of blog", example: "دیابت | علائم، علل ابتلا، پیشگیری و درمان" })
title_fa: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "english slug of blog", example: "diabetes" })
slug: string;
@Expose()
@IsNotEmpty()
@IsValidId(BlogCategoryModel)
@ApiProperty({ type: "string", description: "id of blog category", example: "66eda6397ac110c16462737a" })
category: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "description of blog ", example: "دیابت | علائم، علل ابتلا، پیشگیری و درمان" })
description: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({
type: "string",
description: "description of blog ",
example:
"دیابت بیماری است که در آن بدن نمی تواند گلوکز لازم برای تامین انرژی سلول های بدن بیمار را تامین کند. در واقع شخصی که به این بیماری مبتلا باشد سطح گلوکز در بدن او بالا میماند و هورمونی به اسم انسولین که قند موجود در بدن را به انرژی تبدیل کند نمیتواند این انتقال را انجام دهد .",
})
content: string;
@Expose()
@IsNotEmpty()
@IsArray()
@IsString({ each: true })
@ApiProperty({ type: "Array", description: "tags of blog ", example: ["diabetes", "test", "sugar"] })
tags: string[];
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "image url of blog ", example: "https://cdn.com" })
imageUrl: string;
}
export class UpdateBlogDTO {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "farsi title of blog", example: "دیابت | علائم، علل ابتلا، پیشگیری و درمان" })
title_fa?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "english slug of blog", example: "diabetes" })
slug?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsValidId(BlogCategoryModel)
@ApiProperty({ type: "string", description: "id of blog category", example: "66ed83563858f185c68449ff" })
category?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "description of blog ", example: "دیابت | علائم، علل ابتلا، پیشگیری و درمان" })
description?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({
type: "string",
description: "description of blog ",
example:
"دیابت بیماری است که در آن بدن نمی تواند گلوکز لازم برای تامین انرژی سلول های بدن بیمار را تامین کند. در واقع شخصی که به این بیماری مبتلا باشد سطح گلوکز در بدن او بالا میماند و هورمونی به اسم انسولین که قند موجود در بدن را به انرژی تبدیل کند نمیتواند این انتقال را انجام دهد .",
})
content?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsArray()
@IsString({ each: true })
@ApiProperty({ type: "Array", description: "tags of blog ", example: ["diabetes", "test", "sugar"] })
tags?: string[];
@Expose()
@IsOptional()
@IsNotEmpty()
@ApiProperty({ type: "string", description: "image url of blog ", example: "https://cdn.com" })
imageUrl?: string;
}
@@ -0,0 +1,63 @@
import { Expose } from "class-transformer";
import { IsNotEmpty, IsOptional, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { BlogCategoryModel } from "../models/blogCategory.model";
export class CreateBlogCategoryDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the farsi title of blog category", example: "سلامت" })
title_fa: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the slug of blog category", example: "healthy" })
slug: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the description of blog category", example: "سلامت" })
description: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsValidId(BlogCategoryModel)
@ApiProperty({ type: "string", description: "the id of parent blog category", example: "6103e3a9f9b1b5b27cd7c8e9" })
parent?: string;
}
export class UpdateBlogCategoryDTO {
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the farsi title of blog category", example: "سلامت" })
title_fa?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the slug of blog category", example: "healthy" })
slug?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the description of blog category", example: "سلامت" })
description?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsValidId(BlogCategoryModel)
@ApiProperty({ type: "string", description: "the id of parent blog category", example: "6103e3a9f9b1b5b27cd7c8e9" })
parent?: string;
}
+68
View File
@@ -0,0 +1,68 @@
import { inject } from "inversify";
import { controller, httpGet, queryParam, requestParam } from "inversify-express-utils";
import { BlogService } from "./blog.service";
import { HttpStatus } from "../../common";
import { BaseController } from "../../common/base/controller";
import { ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
import { IOCTYPES } from "../../IOC/ioc.types";
@controller("/blogs")
@ApiTags("Blog")
class BlogController extends BaseController {
@inject(IOCTYPES.BlogService) blogService: BlogService;
@ApiOperation("get all blogs")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("page", "the page number")
@ApiQuery("limit", "the limit of returned data")
@httpGet("")
public async getAllBlogs(@queryParam("limit") limit: string, @queryParam("page") page: string) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
};
const data = await this.blogService.getAllBlogs(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, blogs: data.blogs });
}
@ApiOperation("get blog categories")
@ApiResponse("successful", HttpStatus.Ok)
@httpGet("/categories")
public async getBlogCategories() {
const data = await this.blogService.getBlogCategories();
return this.response(data);
}
@ApiOperation("get blogs with category slug")
@ApiParam("slug", "the slug of category", true)
@ApiResponse("successful", HttpStatus.Ok)
@httpGet("/category/:slug")
public async getBlogsWithCategory(@requestParam("slug") slug: string) {
const data = await this.blogService.getBlogsByCategorySlug(slug);
return this.response(data);
}
@ApiOperation("get blog by id")
@ApiResponse("successful", HttpStatus.Ok)
@ApiResponse("not found by id", HttpStatus.BadRequest)
@ApiParam("id", "the id of blog", true)
@httpGet("/:id([0-9a-fA-F]{24})")
public async getBlogById(@requestParam("id") blogId: string) {
const data = await this.blogService.getBlogById(blogId);
return this.response(data);
}
@ApiOperation("get blog by slug")
@ApiResponse("successful", HttpStatus.Ok)
@ApiResponse("not found by slug", HttpStatus.BadRequest)
@ApiParam("slug", "the slug of blog", true)
@httpGet("/:slug")
public async getBlogBySlug(@requestParam("slug") slug: string) {
const data = await this.blogService.getBlogBySlug(slug);
return this.response(data);
}
}
export { BlogController };
+176
View File
@@ -0,0 +1,176 @@
import { inject, injectable } from "inversify";
import { isValidObjectId } from "mongoose";
import slugify from "slugify";
import { BlogDTO } from "./DTO/blog.dto";
import { CreateBlogDTO, UpdateBlogDTO } from "./DTO/createBlog.dto";
import { CreateBlogCategoryDTO, UpdateBlogCategoryDTO } from "./DTO/createBlogCategory.dto";
import { BlogRepository } from "./repository/blog";
import { BlogCategoryRepo } from "./repository/blogCategory";
import { CategoryMessage, CommonMessage } from "../../common/enums/message.enum";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
@injectable()
class BlogService {
@inject(IOCTYPES.BlogRepository) blogRepo: BlogRepository;
@inject(IOCTYPES.BlogCategoryRepo) blogCategoryRepo: BlogCategoryRepo;
//################################################
//################################################
async createBlogCategory(createDto: CreateBlogCategoryDTO) {
createDto.slug = slugify(createDto.slug);
const existSlug = await this.blogCategoryRepo.findBySlug(createDto.slug);
if (existSlug) throw new BadRequestError(CategoryMessage.DuplicateSlug);
//if parent exist first set parent leaf false
if (createDto.parent) {
const parentCategory = await this.blogCategoryRepo.findById(createDto.parent);
if (parentCategory && parentCategory.children && parentCategory.children.length >= 1) {
throw new BadRequestError(CategoryMessage.ParentHasChild);
}
await this.blogCategoryRepo.model.findByIdAndUpdate(createDto.parent, { leaf: false });
}
const newCategory = await this.blogCategoryRepo.model.create({
...createDto,
});
return {
message: CommonMessage.Created,
newCategory,
};
}
async deleteBlogCategory(catId: string) {
if (!isValidObjectId(catId)) throw new BadRequestError(CommonMessage.NotValidId);
const blogCategory = await this.blogCategoryRepo.findById(catId);
if (!blogCategory) throw new BadRequestError(CommonMessage.NotValidId);
await this.blogCategoryRepo.model.findOneAndDelete({ _id: catId });
return {
message: CommonMessage.Deleted,
};
}
//################################################
//################################################
async updateBlogCategory(catId: string, updateDto: UpdateBlogCategoryDTO) {
if (!isValidObjectId(catId)) throw new BadRequestError(CommonMessage.NotValidId);
const blogCategory = await this.blogCategoryRepo.findById(catId);
if (!blogCategory) throw new BadRequestError(CommonMessage.NotValidId);
if (updateDto.slug) {
updateDto.slug = slugify(updateDto.slug);
const existTitle = await this.blogCategoryRepo.findBySlug(updateDto.slug);
if (existTitle) throw new BadRequestError(CategoryMessage.DuplicateSlug);
}
const UpdatedCategory = await this.blogCategoryRepo.model.findByIdAndUpdate(catId, updateDto, { new: true });
return {
message: CommonMessage.Updated,
UpdatedCategory,
};
}
//################################################
//################################################
async createBlog(adminId: string, createDto: CreateBlogDTO) {
createDto.slug = slugify(createDto.slug);
const existSlug = await this.blogRepo.findBySlug(createDto.slug);
if (existSlug) throw new BadRequestError(CategoryMessage.DuplicateSlug);
const newBlog = await this.blogRepo.model.create({
author: adminId,
...createDto,
});
return {
message: CommonMessage.Created,
newBlog,
};
}
//################################################
//################################################
async updateBlog(blogId: string, updateDto: UpdateBlogDTO) {
await this.validateBlog(blogId);
if (updateDto.slug) {
updateDto.slug = slugify(updateDto.slug);
const existTitle = await this.blogRepo.findBySlug(updateDto.slug);
if (existTitle) throw new BadRequestError(CategoryMessage.DuplicateSlug);
}
const updatedBlog = await this.blogRepo.model.findByIdAndUpdate(blogId, updateDto, { new: true });
return {
message: CommonMessage.Updated,
updatedBlog,
};
}
//################################################
//################################################
async deleteBlog(blogId: string) {
await this.validateBlog(blogId);
const deletedBlog = await this.blogRepo.model.findByIdAndDelete(blogId);
return {
message: CommonMessage.Deleted,
deletedBlog,
};
}
//################################################
//################################################
async getBlogById(blogId: string) {
const blog = await this.validateBlog(blogId);
blog.view += 1;
await blog.save();
return {
blog: BlogDTO.transformBlog(blog),
};
}
//################################################
//################################################
async getBlogBySlug(slug: string) {
const blog = await this.blogRepo.findBySlug(slug);
if (!blog) throw new BadRequestError(CommonMessage.NotFoundBySlug);
blog.view += 1;
await blog.save();
return {
blog: BlogDTO.transformBlog(blog),
};
}
//################################################
//################################################
async getAllBlogs(queries: { limit: number; page: number }) {
const { docs, count } = await this.blogRepo.getAllBlog(queries);
const blogs = docs.map((doc) => BlogDTO.transformBlog(doc));
return {
blogs,
count,
};
}
async getBlogCategories() {
const categories = await this.blogCategoryRepo.model.find({ parent: null });
return { categories };
}
async getBlogsByCategorySlug(slug: string) {
const category = await this.blogCategoryRepo.model.findOne({ slug });
if (!category) throw new BadRequestError(CommonMessage.NotFoundBySlug);
const blogs = await this.blogRepo.getAllWithCategoryId(category._id.toString());
return { blogs };
}
//################################################
//helper methods
private async validateBlog(blogId: string) {
if (!isValidObjectId(blogId)) throw new BadRequestError(CommonMessage.NotValidId);
const blog = await this.blogRepo.findById(blogId);
if (!blog) throw new BadRequestError(CommonMessage.NotValidId);
return blog;
}
}
export { BlogService };
@@ -0,0 +1,14 @@
import { Types } from "mongoose";
export interface IBlog {
title_fa: string;
slug: string;
category: Types.ObjectId;
// name: string;
description: string;
content: string;
author: Types.ObjectId;
view: number;
tags: string[];
imageUrl: string;
}
@@ -0,0 +1,11 @@
import { Types } from "mongoose";
export interface IBlogCategory {
_id: Types.ObjectId;
title_fa: string;
slug: string;
description: string;
parent: Types.ObjectId;
children: IBlogCategory[];
leaf: boolean;
}
+28
View File
@@ -0,0 +1,28 @@
import { Schema, model } from "mongoose";
import { IBlog } from "./Abstraction/IBlog";
const BlogSchema = new Schema<IBlog>(
{
title_fa: { type: String, required: true },
slug: { type: String, unique: true, required: true },
category: { type: Schema.Types.ObjectId, ref: "BlogCategory", required: true },
// name: { type: String, required: true },
description: { type: String, required: true },
content: { type: String, required: true },
author: { type: Schema.Types.ObjectId, ref: "Admin", required: true },
view: { type: Number, default: 0 },
tags: { type: [String] },
imageUrl: { type: String },
},
{ timestamps: true, toJSON: { virtuals: true }, id: false },
);
BlogSchema.pre(["find", "findOne"], function (next) {
this.populate([{ path: "category" }, { path: "author", select: { _id: 1, fullName: 1 } }]);
next();
});
const BlogModel = model<IBlog>("Blog", BlogSchema);
export { BlogModel };
@@ -0,0 +1,52 @@
import { Schema, model } from "mongoose";
import { IBlogCategory } from "./Abstraction/IBlogCategory";
import { BlogModel } from "./blog.model";
const BlogCategorySchema = new Schema<IBlogCategory>(
{
title_fa: { type: String, required: true },
slug: { type: String, unique: true, required: true },
description: String,
parent: { type: Schema.Types.ObjectId, ref: "BlogCategory", default: null },
leaf: { type: Boolean, default: true },
},
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
);
BlogCategorySchema.post(["deleteOne", "findOneAndDelete"], async function (doc, next) {
await BlogModel.deleteMany({ category: doc._id });
next();
});
BlogCategorySchema.virtual("children", {
ref: "BlogCategory",
localField: "_id",
foreignField: "parent",
justOne: false,
});
BlogCategorySchema.pre("find", function (next) {
this.populate({ path: "children" });
next();
});
BlogCategorySchema.pre("findOne", function (next) {
this.populate({ path: "children" });
next();
});
BlogCategorySchema.pre("findOneAndDelete", async function (next) {
const parent = await this.model.findOne(this.getFilter());
if (parent) {
const childCategories = await this.model.find({ parent: parent._id });
const categoryIds = [parent._id, ...childCategories.map((c) => c._id)];
console.log(categoryIds);
await BlogModel.deleteMany({ category: { $in: categoryIds } });
const deletedBlogs = await BlogCategoryModel.deleteMany({ parent: parent._id });
console.log({ deletedBlogs });
}
next();
});
const BlogCategoryModel = model<IBlogCategory>("BlogCategory", BlogCategorySchema);
export { BlogCategoryModel };
+40
View File
@@ -0,0 +1,40 @@
import { BaseRepository } from "../../../common/base/repository";
import { IBlog } from "../models/Abstraction/IBlog";
import { BlogModel } from "../models/blog.model";
class BlogRepository extends BaseRepository<IBlog> {
constructor() {
super(BlogModel);
}
async getAllBlog(queries: { page: number; limit: number }) {
const page = queries.page || 1;
const limit = queries.limit || 10;
const skip = (page - 1) * limit;
const count = await this.model.countDocuments({});
const docs = await this.model.find().skip(skip).limit(limit).sort({ createdAt: -1 });
return { docs, count };
}
async getAllWithCategoryId(categoryId: string) {
return this.model.find({ category: categoryId });
}
async findById(id: string) {
return this.model.findById(id);
}
async findBySlug(slug: string) {
return this.model.findOne({ slug });
}
async findAll() {
return this.model.find();
}
}
function createBlogRepo(): BlogRepository {
return new BlogRepository();
}
export { BlogRepository, createBlogRepo };
@@ -0,0 +1,18 @@
import { BaseRepository } from "../../../common/base/repository";
import { IBlogCategory } from "../models/Abstraction/IBlogCategory";
import { BlogCategoryModel } from "../models/blogCategory.model";
class BlogCategoryRepo extends BaseRepository<IBlogCategory> {
constructor() {
super(BlogCategoryModel);
}
async findBySlug(slug: string) {
return this.model.findOne({ slug });
}
}
function createBlogCategoryRepo(): BlogCategoryRepo {
return new BlogCategoryRepo();
}
export { BlogCategoryRepo, createBlogCategoryRepo };
+43
View File
@@ -0,0 +1,43 @@
import { Expose } from "class-transformer";
import { IsBoolean, IsInt, IsOptional, Max, Min } from "class-validator";
export class BrandSearchQueryDTO {
@Expose()
@IsOptional()
@IsInt()
@Max(50)
limit?: number;
@Expose()
@IsOptional()
@IsInt()
@Min(1)
page?: number;
@Expose()
@IsOptional()
@IsBoolean()
stock?: boolean;
@Expose()
@IsOptional()
@IsInt()
maxPrice?: number;
@Expose()
@IsOptional()
@IsInt()
minPrice?: number;
@Expose()
@IsOptional()
@IsBoolean()
wholeSale?: boolean;
@Expose()
@IsOptional()
@IsInt()
@Min(1)
@Max(7)
sort?: number;
}
+35
View File
@@ -0,0 +1,35 @@
import { Expose, plainToInstance } from "class-transformer";
import { IBrand } from "../models/Abstraction/IBrand";
export class BrandDTO {
@Expose()
_id: string;
@Expose()
status: string;
@Expose()
title_en: string;
@Expose()
title_fa: string;
@Expose()
images: string[];
@Expose()
logoUrl: string;
@Expose()
website_link: string;
public static transformBrand(data: IBrand): BrandDTO {
const transformed = plainToInstance(BrandDTO, data, {
excludeExtraneousValues: true,
enableImplicitConversion: true,
});
return transformed;
}
}
+53
View File
@@ -0,0 +1,53 @@
import { OmitType } from "@nestjs/mapped-types";
import { Expose } from "class-transformer";
import { IsArray, IsNotEmpty, IsOptional, IsString, Length } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { CategoryModel } from "../../category/models/category.model";
export class CreateBrandDTO {
@Expose()
@IsNotEmpty()
@Length(2, 40)
@ApiProperty({ type: "string", description: "title_fa of the brand", example: "آدیتا" })
title_fa: string;
@Expose()
@IsNotEmpty()
@Length(2, 40)
@ApiProperty({ type: "string", description: "title_en of the brand", example: "ADATA" })
title_en: string;
@Expose()
@IsNotEmpty()
@IsString()
@IsValidId(CategoryModel)
@ApiProperty({ type: "string", description: "the id of category", example: "aas55a4ee6fvw343g4fdg" })
category: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
website_link: string;
@Expose()
@IsNotEmpty()
@ApiProperty({ type: "string", description: "description of the brand", example: "برند آدیتا" })
description: string;
@Expose()
@IsNotEmpty()
@ApiProperty({ type: "string", description: "logoUrl of the brand", example: "https://cdnUrl.com" })
logoUrl: string;
@Expose()
@IsOptional()
@IsArray()
@IsString({ each: true })
@ApiProperty({ type: "Array", description: "images url of the brand", example: ["https://cdnUrl.com", "https://cdnUrl.com"] })
images?: string[];
}
export class CreateGlobalBrand extends OmitType(CreateBrandDTO, ["category"] as const) {}
@@ -0,0 +1,20 @@
import { Expose } from "class-transformer";
import { IsOptional, IsString } from "class-validator";
import { PaginationDTO } from "../../../common/dto/pagination.dto";
import { StatusEnum } from "../../../common/enums/status.enum";
export class FindBrandQueryDTO extends PaginationDTO {
@Expose()
@IsOptional()
categoryId: string;
@Expose()
@IsOptional()
status: StatusEnum;
@Expose()
@IsOptional()
@IsString()
q: string;
}
+42
View File
@@ -0,0 +1,42 @@
import { Expose } from "class-transformer";
import { IsNotEmpty, IsOptional, IsString, Length } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId } from "../../../common/decorator/validation.decorator";
import { CategoryModel } from "../../category/models/category.model";
export class UpdateBrandDTO {
@Expose()
@IsOptional()
@IsNotEmpty()
@Length(2, 40)
@ApiProperty({ type: "string", description: "title_fa of the brand", example: "آدیتا" })
title_fa?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@Length(2, 40)
@ApiProperty({ type: "string", description: "title_en of the brand", example: "ADATA" })
title_en?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@IsString()
@IsValidId(CategoryModel)
@ApiProperty({ type: "string", description: "the id of category", example: "aas55a4ee6fvw343g4fdg" })
category?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@ApiProperty({ type: "string", description: "description of the brand", example: "برند آدیتا" })
description?: string;
@Expose()
@IsOptional()
@IsNotEmpty()
@ApiProperty({ type: "string", description: "logoUrl of the brand", example: "https://cdnUrl.com" })
logoUrl?: string;
}
+93
View File
@@ -0,0 +1,93 @@
import { Request } from "express";
import rateLimit from "express-rate-limit";
import { inject } from "inversify";
import { controller, httpGet, httpPost, queryParam, request, requestBody, requestParam } from "inversify-express-utils";
import { BrandService } from "./brand.service";
import { CreateBrandDTO } from "./DTO/createBrand.dto";
import { HttpStatus } from "../../common";
import { BrandSearchQueryDTO } from "./DTO/brand-search.dto";
import { FindBrandQueryDTO } from "./DTO/findBrandQuery.dto";
import { BaseController } from "../../common/base/controller";
import { ApiAuth, ApiFile, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
import { appConfig } from "../../core/config/app.config";
import { Guard } from "../../core/middlewares/guard.middleware";
import { ValidationMiddleware } from "../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../IOC/ioc.types";
import { UploadService } from "../../utils/upload.service";
import { IUser } from "../user/models/Abstraction/IUser";
@controller("/brand")
@ApiTags("Brand")
class BrandController extends BaseController {
@inject(IOCTYPES.BrandService) brandService: BrandService;
@ApiOperation("request to create a new brand ==> login as seller")
@ApiResponse("successful", HttpStatus.Ok)
@ApiModel(CreateBrandDTO)
@ApiAuth()
@httpPost("", rateLimit(appConfig.rate), Guard.authSeller(), ValidationMiddleware.validateInput(CreateBrandDTO))
public async createBrand(@requestBody() createBrandDto: CreateBrandDTO) {
const data = await this.brandService.createBrand(createBrandDto);
return this.response({ data }, HttpStatus.Created);
}
@ApiOperation("get products of a brand")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("name", "name of brand", true)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("stock", "search based on product stock")
@ApiQuery("maxPrice", "search based on product priceRange")
@ApiQuery("minPrice", "search based on product priceRange")
@ApiQuery("wholeSale", "search based on product wholeSale if its enable")
@ApiQuery("sort", "sort option should be int from 1 to 7")
@httpGet("/:name/search", Guard.authOptional(), ValidationMiddleware.validateQuery(BrandSearchQueryDTO))
@ApiAuth()
public async getProductsOfBrand(
@requestParam("name") name: string,
@queryParam() searchQueryDto: BrandSearchQueryDTO,
@request() req: Request,
) {
const user = req.user as IUser;
const { count, ...data } = await this.brandService.getProductsOfBrand(name, searchQueryDto, user?._id?.toString());
const { pager } = this.paginate(count);
return this.response({ ...data, pager });
}
@ApiOperation("get a list of brands")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("categoryId", "category id of brand")
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@ApiQuery("status", "the status of brand ==> value Approved | Pending")
@ApiQuery("q", "search by brand title_en")
@httpGet("", ValidationMiddleware.validateQuery(FindBrandQueryDTO))
public async getAll(@queryParam() queryDto: FindBrandQueryDTO) {
const data = await this.brandService.getAllS(queryDto);
const { pager } = this.paginate(data.count);
return this.response({ brands: data.docs, pager });
}
//uploader
@ApiOperation("Upload a brand info image ==> need to login as seller")
@ApiResponse("File uploaded successfully", HttpStatus.Accepted)
@ApiFile("image")
@ApiAuth()
@httpPost("/image/upload", Guard.authSeller(), UploadService.single("image", "brand-images"))
public async uploadImage(@request() req: Request) {
// eslint-disable-next-line no-undef
const file = req.file as Express.MulterS3.File;
const data = {
message: "file uploaded!",
url: {
size: file?.size,
url: file?.location,
type: file?.mimetype,
},
};
return this.response({ data }, HttpStatus.Accepted);
}
}
export { BrandController };
+90
View File
@@ -0,0 +1,90 @@
import { Types } from "mongoose";
import { FindBrandQueryDTO } from "./DTO/findBrandQuery.dto";
import { IBrand } from "./models/Abstraction/IBrand";
import { BrandModel } from "./models/brand.model";
import { BaseRepository } from "../../common/base/repository";
import { StatusEnum } from "../../common/enums/status.enum";
import { paginationUtils } from "../../utils/pagination.utils";
class BrandRepository extends BaseRepository<IBrand> {
constructor() {
super(BrandModel);
}
async getAllBrand(queryDto: FindBrandQueryDTO, hierarchy?: Types.ObjectId[]) {
const { skip, limit } = paginationUtils(queryDto);
const matchStage: { [x: string]: unknown } = {
deleted: false,
};
if (hierarchy && hierarchy.length) {
matchStage["$or"] = [{ category: { $in: hierarchy } }, { category: null }];
}
if (queryDto.status) {
matchStage.status = queryDto.status;
}
if (queryDto.q) {
matchStage.$or = [{ title_fa: { $regex: queryDto.q, $options: "i" } }, { title_en: { $regex: queryDto.q, $options: "i" } }];
}
const docs = await this.model.aggregate([
{
$match: matchStage,
},
{
$lookup: {
from: "categories",
let: { categoryId: "$category" },
pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$categoryId"] } } }, { $project: { title_fa: 1, name: 1 } }],
as: "categoryDetails",
},
},
{
$addFields: {
category: {
$cond: {
if: { $gt: [{ $size: "$categoryDetails" }, 0] },
then: { name: { $arrayElemAt: ["$categoryDetails.title_fa", 0] }, _id: "$category" },
else: null,
},
},
},
},
{
$skip: skip,
},
{
$limit: limit,
},
{
$project: {
__v: 0,
categoryDetails: 0,
createdAt: 0,
updatedAt: 0,
},
},
]);
const count = await this.model.countDocuments(matchStage);
return {
docs,
count,
};
}
async findWithName(name: string) {
return this.model.findOne({ title_en: name }, { _id: 1, title_en: 1, title_fa: 1, status: 1, image: 1, category: 1 });
}
async getPendingBrandsCount() {
return this.model.countDocuments({ status: StatusEnum.Pending });
}
}
function createBrandRepository(): BrandRepository {
return new BrandRepository();
}
export { BrandRepository, createBrandRepository };
+122
View File
@@ -0,0 +1,122 @@
import { inject, injectable } from "inversify";
import slugify from "slugify";
import { BrandRepository } from "./brand.repository";
import { BrandSearchQueryDTO } from "./DTO/brand-search.dto";
import { CreateBrandDTO, CreateGlobalBrand } from "./DTO/createBrand.dto";
import { FindBrandQueryDTO } from "./DTO/findBrandQuery.dto";
import { UpdateBrandDTO } from "./DTO/updateBrand.dto";
import { BrandMessage, CategoryMessage, CommonMessage } from "../../common/enums/message.enum";
import { StatusEnum } from "../../common/enums/status.enum";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
import { CategoryRepository } from "../category/category.repository";
import { ICategory } from "../category/models/Abstraction/ICategory";
import { ProductDTO } from "../product/DTO/product.dto";
import { IProduct } from "../product/models/Abstraction/IProduct";
import { ProductRepository } from "../product/Repository/product";
@injectable()
class BrandService {
@inject(IOCTYPES.BrandRepository) brandRepo: BrandRepository;
@inject(IOCTYPES.ProductRepository) productRepo: ProductRepository;
@inject(IOCTYPES.CategoryRepository) categoryRepo: CategoryRepository;
async createBrand(createDto: CreateBrandDTO) {
createDto.title_en = slugify(createDto.title_en);
const existBrand = await this.brandRepo.model.findOne({ title_en: createDto.title_en, category: createDto.category });
if (existBrand) throw new BadRequestError(BrandMessage.Exist);
const newBrand = await this.brandRepo.model.create(createDto);
return {
message: BrandMessage.Created,
newBrand,
};
}
//########################
async createGlobalBrand(createDto: CreateGlobalBrand) {
createDto.title_en = slugify(createDto.title_en);
const existBrand = await this.brandRepo.model.findOne({ title_en: createDto.title_en });
if (existBrand) throw new BadRequestError(BrandMessage.Exist);
const newBrand = await this.brandRepo.model.create(createDto);
return {
message: BrandMessage.Created,
newBrand,
};
}
//########################
async getAllS(queryDto: FindBrandQueryDTO) {
let category: ICategory | null = null;
if (queryDto.categoryId) {
category = await this.categoryRepo.findById(queryDto.categoryId);
if (!category) throw new BadRequestError(CategoryMessage.NotFound);
}
const { count, docs } = await this.brandRepo.getAllBrand(queryDto, category?.hierarchy);
return { count, docs };
}
async deleteById(id: string) {
const deletedBrand = await this.brandRepo.model.findByIdAndUpdate(id, { deleted: true }, { new: true });
if (!deletedBrand) throw new BadRequestError(CommonMessage.NotValidId);
return {
message: CommonMessage.Deleted,
deletedBrand,
};
}
async updateById(id: string, updateDto: UpdateBrandDTO) {
if (updateDto.title_en) {
updateDto.title_en = slugify(updateDto.title_en);
const existBrand = await this.brandRepo.model.findOne({
title_en: updateDto.title_en,
category: updateDto.category,
_id: { $ne: id },
});
if (existBrand) throw new BadRequestError(BrandMessage.Exist);
}
const updatedBrand = await this.brandRepo.model.findByIdAndUpdate(id, updateDto, { new: true });
if (!updatedBrand) throw new BadRequestError(CommonMessage.NotValidId);
return {
message: CommonMessage.Updated,
updatedBrand,
};
}
async getProductsOfBrand(name: string, queries: BrandSearchQueryDTO, userId?: string) {
const brand = await this.brandRepo.findWithName(name);
if (!brand) throw new BadRequestError(BrandMessage.NotFound);
const category = await this.categoryRepo.findById(brand.category.toString());
const priceRange = await this.productRepo.getPriceRangeByBrand(brand._id.toString());
const { docs, count } = await this.productRepo.getProductsWithBrand(brand._id.toString(), queries, userId);
const products = docs.map((doc: IProduct) => ProductDTO.transformProduct(doc));
return { brand, products, count, filters: { priceRange }, category };
}
async approve(id: string) {
const updatedBrand = await this.brandRepo.model.findByIdAndUpdate(id, { status: StatusEnum.Approved }, { new: true });
if (!updatedBrand) throw new BadRequestError(CommonMessage.NotValidId);
return {
message: CommonMessage.Updated,
updatedBrand,
};
}
async getPendingBrandsCount() {
return await this.brandRepo.getPendingBrandsCount();
}
}
export { BrandService };
@@ -0,0 +1,15 @@
import { Types } from "mongoose";
import { StatusEnum } from "../../../../common/enums/status.enum";
export interface IBrand {
title_fa: string;
title_en: string;
category: Types.ObjectId;
website_link: string;
status: StatusEnum;
description: string;
logoUrl: string;
images: string[];
deleted: boolean;
}
+36
View File
@@ -0,0 +1,36 @@
import { Schema, model } from "mongoose";
import { IBrand } from "./Abstraction/IBrand";
import { StatusEnum } from "../../../common/enums/status.enum";
const brandSchema = new Schema<IBrand>(
{
title_fa: { type: String, required: true },
title_en: { type: String, required: true },
website_link: { type: String },
// category: { type: Schema.Types.ObjectId, ref: "Category", required: true },
category: { type: Schema.Types.ObjectId, ref: "Category", default: null },
status: { type: String, enum: StatusEnum, default: StatusEnum.Pending },
description: String,
logoUrl: String,
images: { type: [String], default: [] },
deleted: { type: Boolean, default: false },
},
{ timestamps: true, toJSON: { virtuals: true, versionKey: false }, id: false },
);
brandSchema.index(
{ title_en: 1, category: 1 },
{
unique: true,
partialFilterExpression: { deleted: { $eq: false } },
},
);
brandSchema.virtual("url").get(function () {
return `/brand/${this.title_en}`;
});
const BrandModel = model<IBrand>("Brand", brandSchema);
export { BrandModel };

Some files were not shown because too many files have changed in this diff Show More