From 78a9b99aae4a977c86c8769382a6ec44fa19e9a6 Mon Sep 17 00:00:00 2001 From: dev-abumahid Date: Mon, 13 Apr 2026 00:13:52 +0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(order):=20add=20customer=20ema?= =?UTF-8?q?il=20and=20optional=20fields,=20update=20order=20creation=20flo?= =?UTF-8?q?w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added `customerEmail` field to Order model. - Made `customerNote` optional in the Order model. - Updated order creation logic to send a tracking email. - Updated order validation to handle optional fields. - Refactored configurations import path from `errors/configs` to `configs`. - Minor modifications across account and order services for consistency. --- .../migration.sql | 3 + prisma/schema/order.prisma | 3 +- src/app/{errors => }/configs/index.ts | 0 src/app/middlewares/auth.ts | 2 +- src/app/middlewares/global_error_handler.ts | 2 +- src/app/modules/account/account.controller.ts | 2 +- src/app/modules/account/account.service.ts | 4 +- src/app/modules/order/order.service.ts | 65 ++++++------------- src/app/modules/order/order.swagger.ts | 10 ++- src/app/modules/order/order.validation.ts | 41 ++++++------ .../email/order/order.email.processor.ts | 14 ++++ .../queues/email/order/order.email.queue.ts | 15 +++++ .../queues/email/order/order.email.worker.ts | 14 ++++ src/app/queues/worker.ts | 1 + src/app/utils/cloudinary.ts | 2 +- src/app/utils/mail_sender.ts | 2 +- src/server.ts | 4 +- src/swaggerOptions.ts | 2 +- 18 files changed, 103 insertions(+), 83 deletions(-) create mode 100644 prisma/migrations/20260412181256_order_schema_change/migration.sql rename src/app/{errors => }/configs/index.ts (100%) create mode 100644 src/app/queues/email/order/order.email.processor.ts create mode 100644 src/app/queues/email/order/order.email.queue.ts create mode 100644 src/app/queues/email/order/order.email.worker.ts diff --git a/prisma/migrations/20260412181256_order_schema_change/migration.sql b/prisma/migrations/20260412181256_order_schema_change/migration.sql new file mode 100644 index 0000000..01ce630 --- /dev/null +++ b/prisma/migrations/20260412181256_order_schema_change/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Order" ADD COLUMN "customerEmail" TEXT, +ALTER COLUMN "customerNote" DROP NOT NULL; diff --git a/prisma/schema/order.prisma b/prisma/schema/order.prisma index db25e7a..b6c39c9 100644 --- a/prisma/schema/order.prisma +++ b/prisma/schema/order.prisma @@ -20,8 +20,9 @@ model Order { status STATUS customerName String customerPhone String + customerEmail String? customerAddress String - customerNote String + customerNote String? paymentType PAYMENT_TYPE createdAt DateTime @default(now()) diff --git a/src/app/errors/configs/index.ts b/src/app/configs/index.ts similarity index 100% rename from src/app/errors/configs/index.ts rename to src/app/configs/index.ts diff --git a/src/app/middlewares/auth.ts b/src/app/middlewares/auth.ts index 408421e..5723cf3 100644 --- a/src/app/middlewares/auth.ts +++ b/src/app/middlewares/auth.ts @@ -1,5 +1,5 @@ import { NextFunction, Request, Response } from "express"; -import { configs } from "../errors/configs"; +import { configs } from "../configs"; import { AppError } from "../utils/app_error"; import { jwtHelpers, JwtPayloadType } from "../utils/JWT"; diff --git a/src/app/middlewares/global_error_handler.ts b/src/app/middlewares/global_error_handler.ts index 7c1d72f..c361f7f 100644 --- a/src/app/middlewares/global_error_handler.ts +++ b/src/app/middlewares/global_error_handler.ts @@ -1,6 +1,6 @@ import { ErrorRequestHandler } from "express"; import { ZodError } from "zod"; -import { configs } from "../errors/configs"; +import { configs } from "../configs"; import handleZodError from "../errors/zodError"; import { TErrorSources } from "../types/error"; import { AppError } from "../utils/app_error"; diff --git a/src/app/modules/account/account.controller.ts b/src/app/modules/account/account.controller.ts index 4904117..f0a467d 100644 --- a/src/app/modules/account/account.controller.ts +++ b/src/app/modules/account/account.controller.ts @@ -1,4 +1,4 @@ -import { configs } from "../../errors/configs"; +import { configs } from "../../configs"; import catchAsync from "../../utils/catch_async"; import manageResponse from "../../utils/manage_response"; import { account_services } from "./account.service"; diff --git a/src/app/modules/account/account.service.ts b/src/app/modules/account/account.service.ts index ae01b8d..33868fa 100644 --- a/src/app/modules/account/account.service.ts +++ b/src/app/modules/account/account.service.ts @@ -1,6 +1,6 @@ import bcrypt from "bcrypt"; import { Request } from "express"; -import { configs } from "../../errors/configs"; +import { configs } from "../../configs"; import { prisma } from "../../lib/prisma"; import { emailQueue } from "../../queues/email/email.queue"; import { AppError } from "../../utils/app_error"; @@ -72,7 +72,7 @@ const create_account_into_db = async (req: Request) => { email: payload.email, textBody: "You can use otp or verification link for verifying your account" }) - return result; + return null; }; const verify_account_using_otp_into_db = async (req: Request) => { diff --git a/src/app/modules/order/order.service.ts b/src/app/modules/order/order.service.ts index 7b54a28..371301b 100644 --- a/src/app/modules/order/order.service.ts +++ b/src/app/modules/order/order.service.ts @@ -1,7 +1,8 @@ import { Request } from "express"; +import { configs } from "../../configs"; import { prisma } from "../../lib/prisma"; -import { AppError } from "../../utils/app_error"; +import { orderEmailQueue } from "../../queues/email/order/order.email.queue"; const get_all_order_from_db = async (req: Request) => { // define your own login here @@ -11,68 +12,42 @@ const get_all_order_from_db = async (req: Request) => { const get_single_order_from_db = async (req: Request) => { // define your own login here - const { id } = req.params; + const { id } = req.params as { id: string }; const result = await prisma.order.findUnique({ where: { id } }); return result; }; const create_order_into_db = async (req: Request) => { - // define your own login here + const payload = req?.body; + payload.status = "INITIATED"; + payload.paymentType = "COD" - const user = req.user - console.log(user) - const { - shopAccountId, - productPrice, - productQuantity, - productName, - customerName, - customerPhone, - customerAddress, - customerNote, - paymentType, - status, - } = req.body - const isUserExists = await prisma.account.findFirst({ - where: { - id: user?.accountId - } - }) - if (!isUserExists) { - throw new AppError("Account not found", 404); + // nwo init order + const result = await prisma.order.create({ data: payload }); + + // if email exist sent tracking link + if (payload.customerEmail) { + const trackingLink = `${configs.jwt.front_end_url}/track-order/${result.id}`; + await orderEmailQueue.add("order-email-queue", { + email: payload.customerEmail, + subject: "Order Tracking", + textBody: `Your order has been created. Track your order here: ${trackingLink}`, + htmlBody: `

Your order has been created. Track your order here: Track Order

` + }) } - - const result = await prisma.order.create({ - data: { - productPrice, - productQuantity, - productName, - customerName, - customerPhone, - customerAddress, - customerNote, - paymentType, - status, - account: { - connect: { - id: user?.accountId, - }, - }, - } - }); return result; }; const update_order_into_db = async (req: Request) => { // define your own login here - const { id } = req.params; + const { id } = req.params as { id: string }; const result = await prisma.order.update({ where: { id }, data: req.body }); return result; }; const delete_order_from_db = async (req: Request) => { // define your own login here - const { id } = req.params; + const { id } = req.params as { id: string }; const result = await prisma.order.delete({ where: { id } }); return result; }; diff --git a/src/app/modules/order/order.swagger.ts b/src/app/modules/order/order.swagger.ts index 04368b7..3d37735 100644 --- a/src/app/modules/order/order.swagger.ts +++ b/src/app/modules/order/order.swagger.ts @@ -10,18 +10,16 @@ export const orderSwaggerDocs = { content: { "application/json": { example: JSON.stringify({ - + "shopAccountId": "", "productPrice": 1500, "productQuantity": 2, "productName": "Wireless Mouse", "customerName": "Rahim Uddin", "customerPhone": "+8801712345678", + "customerEmail": "softvence.abumahid@gmail.com", "customerAddress": "Rangpur, Bangladesh", - "customerNote": "Please deliver between 3-5 PM", - "paymentType": "Cash on Delivery", - "status": "Pending" - - }), // put your request body + "customerNote": "Please deliver between 3-5 PM" + }) }, }, }, diff --git a/src/app/modules/order/order.validation.ts b/src/app/modules/order/order.validation.ts index 52eaab8..114c4ff 100644 --- a/src/app/modules/order/order.validation.ts +++ b/src/app/modules/order/order.validation.ts @@ -1,29 +1,28 @@ -import { uuid, z } from "zod"; +import { z } from "zod"; const create_order = z.object({ - shopAccountId:z.string(), - productPrice:z.number(), - productQuantity:z.number(), - productName:z.string(), - customerName:z.string(), - customerPhone:z.string(), - customerAddress:z.string(), - customerNote:z.string(), - paymentType:z.string(), - status:z.string() + shopAccountId: z.string(), + productPrice: z.number(), + productQuantity: z.number(), + productName: z.string(), + customerName: z.string(), + customerPhone: z.string(), + customerEmail: z.string().optional(), + customerAddress: z.string(), + customerNote: z.string().optional() }); const update_order = z.object({ - shopAccountId:z.string().optional(), - productPrice:z.number().optional(), - productQuantity:z.number().optional(), - productName:z.string().optional(), - customerName:z.string().optional(), - customerPhone:z.string().optional(), - customerAddress:z.string().optional(), - customerNote:z.string().optional(), - paymentType:z.string().optional(), - status:z.string().optional() + shopAccountId: z.string().optional(), + productPrice: z.number().optional(), + productQuantity: z.number().optional(), + productName: z.string().optional(), + customerName: z.string().optional(), + customerPhone: z.string().optional(), + customerEmail: z.string().optional(), + customerAddress: z.string().optional(), + customerNote: z.string().optional(), + status: z.string().optional() }); export const order_validations = { diff --git a/src/app/queues/email/order/order.email.processor.ts b/src/app/queues/email/order/order.email.processor.ts new file mode 100644 index 0000000..1a81c35 --- /dev/null +++ b/src/app/queues/email/order/order.email.processor.ts @@ -0,0 +1,14 @@ +import sendMail from "../../../utils/mail_sender"; +import { TOrderEmailQueue } from "./order.email.queue"; + +// email.processor.ts +export const orderEmailProcessor = async (job: any) => { + const payload: TOrderEmailQueue = job.data; + await sendMail({ + to: payload.email as string, + subject: payload.subject, + htmlBody: payload.htmlBody as string, + textBody: payload.textBody as string, + }); + console.log("Sending email job complete:", job.id); +}; \ No newline at end of file diff --git a/src/app/queues/email/order/order.email.queue.ts b/src/app/queues/email/order/order.email.queue.ts new file mode 100644 index 0000000..db7d087 --- /dev/null +++ b/src/app/queues/email/order/order.email.queue.ts @@ -0,0 +1,15 @@ +import { Queue } from "bullmq"; +import { redisConnection } from "../../connection"; + +export type TOrderEmailQueue = { + email: string; + subject: string; + textBody?: string; + htmlBody?: string; + +} + + +export const orderEmailQueue = new Queue("order-email-queue", { + connection: redisConnection, +}); \ No newline at end of file diff --git a/src/app/queues/email/order/order.email.worker.ts b/src/app/queues/email/order/order.email.worker.ts new file mode 100644 index 0000000..eb3e400 --- /dev/null +++ b/src/app/queues/email/order/order.email.worker.ts @@ -0,0 +1,14 @@ +// email.worker.ts +import { Worker } from "bullmq"; +import { redisConnection } from "../../connection"; +import { orderEmailProcessor } from "./order.email.processor"; +import { TOrderEmailQueue } from "./order.email.queue"; + + +export const emailWorker = new Worker( + "order-email-queue", + async (job) => orderEmailProcessor(job), + { + connection: redisConnection, + } +); \ No newline at end of file diff --git a/src/app/queues/worker.ts b/src/app/queues/worker.ts index cc318ff..71a333d 100644 --- a/src/app/queues/worker.ts +++ b/src/app/queues/worker.ts @@ -1,3 +1,4 @@ import "./email/email.worker"; +import "./email/order/order.email.worker"; console.log("Workers running..."); \ No newline at end of file diff --git a/src/app/utils/cloudinary.ts b/src/app/utils/cloudinary.ts index 0f8bed8..ae77b6b 100644 --- a/src/app/utils/cloudinary.ts +++ b/src/app/utils/cloudinary.ts @@ -1,6 +1,6 @@ import { v2 as cloudinary } from 'cloudinary'; import fs from 'fs'; -import { configs } from '../errors/configs'; +import { configs } from '../configs'; type ICloudinaryResponse = { asset_id: string; diff --git a/src/app/utils/mail_sender.ts b/src/app/utils/mail_sender.ts index bbc4591..9a45052 100644 --- a/src/app/utils/mail_sender.ts +++ b/src/app/utils/mail_sender.ts @@ -1,5 +1,5 @@ import nodemailer from 'nodemailer'; -import { configs } from '../errors/configs'; +import { configs } from '../configs'; type TMailContent = { to: string, subject: string, diff --git a/src/server.ts b/src/server.ts index b730ede..b265e0d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,7 @@ import app from "./app"; -import { configs } from "./app/errors/configs/index"; +import { configs } from "./app/configs/index"; import { prisma } from "./app/lib/prisma"; -// import "./app/queues/worker"; +import "./app/queues/worker"; async function main() { try { diff --git a/src/swaggerOptions.ts b/src/swaggerOptions.ts index 18c1a22..9f0cc09 100644 --- a/src/swaggerOptions.ts +++ b/src/swaggerOptions.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from "node:url"; import path from "path"; -import { configs } from "./app/errors/configs"; +import { configs } from "./app/configs"; import { accountSwaggerDocs } from "./app/modules/account/account.swagger"; import { orderSwaggerDocs } from "./app/modules/order/order.swagger"; import { planSwaggerDocs } from "./app/modules/plan/plan.swagger";