Order api: create order schema

This commit is contained in:
Md Sharafat Hassain
2026-04-12 22:47:56 +06:00
parent 308445f346
commit ee5eb0f0f5
20 changed files with 443 additions and 33 deletions
@@ -0,0 +1,27 @@
-- CreateEnum
CREATE TYPE "STATUS" AS ENUM ('INITIATED', 'CONFIRMED', 'ONGOING', 'DELIVERED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "PAYMENT_TYPE" AS ENUM ('COD');
-- CreateTable
CREATE TABLE "Order" (
"id" TEXT NOT NULL,
"shopAccountId" TEXT NOT NULL,
"productPrice" INTEGER NOT NULL,
"productQuantity" INTEGER NOT NULL,
"productName" TEXT NOT NULL,
"status" "STATUS" NOT NULL,
"customerName" TEXT NOT NULL,
"customerPhone" TEXT NOT NULL,
"customerAddress" TEXT NOT NULL,
"customerNote" TEXT NOT NULL,
"paymentType" "PAYMENT_TYPE" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Order_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_shopAccountId_fkey" FOREIGN KEY ("shopAccountId") REFERENCES "Profile"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "Order" DROP CONSTRAINT "Order_shopAccountId_fkey";
-- AddForeignKey
ALTER TABLE "Order" ADD CONSTRAINT "Order_shopAccountId_fkey" FOREIGN KEY ("shopAccountId") REFERENCES "Account"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+3 -1
View File
@@ -18,5 +18,7 @@ model Account {
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
profile Profile?
profile Profile? //one-to-one
orders Order[] //one-to-many
}
+29
View File
@@ -0,0 +1,29 @@
enum STATUS {
INITIATED
CONFIRMED
ONGOING
DELIVERED
CANCELLED
}
enum PAYMENT_TYPE {
COD
}
model Order {
id String @id @default(uuid())
shopAccountId String
account Account @relation(fields: [shopAccountId], references: [id], onDelete: Cascade)
productPrice Int
productQuantity Int
productName String
status STATUS
customerName String
customerPhone String
customerAddress String
customerNote String
paymentType PAYMENT_TYPE
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
+1
View File
@@ -9,4 +9,5 @@ model Profile {
shopAddress String?
shopMapLocation String?
shopCategory String?
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
import { configs } from "../configs";
import { configs } from "../errors/configs";
import { AppError } from "../utils/app_error";
import { jwtHelpers, JwtPayloadType } from "../utils/JWT";
+1 -1
View File
@@ -1,6 +1,6 @@
import { ErrorRequestHandler } from "express";
import { ZodError } from "zod";
import { configs } from "../configs";
import { configs } from "../errors/configs";
import handleZodError from "../errors/zodError";
import { TErrorSources } from "../types/error";
import { AppError } from "../utils/app_error";
@@ -1,4 +1,4 @@
import { configs } from "../../configs";
import { configs } from "../../errors/configs";
import catchAsync from "../../utils/catch_async";
import manageResponse from "../../utils/manage_response";
import { account_services } from "./account.service";
+1 -1
View File
@@ -1,6 +1,6 @@
import bcrypt from "bcrypt";
import { Request } from "express";
import { configs } from "../../configs";
import { configs } from "../../errors/configs";
import { prisma } from "../../lib/prisma";
import { emailQueue } from "../../queues/email/email.queue";
import { AppError } from "../../utils/app_error";
+68
View File
@@ -0,0 +1,68 @@
import catchAsync from "../../utils/catch_async";
import manageResponse from "../../utils/manage_response";
import { order_service } from "./order.service";
const get_all_order = catchAsync(async (req, res) => {
const result = await order_service.get_all_order_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "All order fetched successfully.",
data: result,
meta: {},
});
});
const get_single_order = catchAsync(async (req, res) => {
const result = await order_service.get_single_order_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "Single order fetched successfully.",
data: result,
meta: {},
});
});
const create_order = catchAsync(async (req, res) => {
const result = await order_service.create_order_into_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "order created successfully.",
data: result,
meta: {},
});
});
const update_order = catchAsync(async (req, res) => {
const result = await order_service.update_order_into_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "order updated successfully.",
data: result,
meta: {},
});
});
const delete_order = catchAsync(async (req, res) => {
const result = await order_service.delete_order_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "order deleted successfully.",
data: result,
meta: {},
});
});
export const order_controller = {
get_all_order,
get_single_order,
create_order,
update_order,
delete_order,
};
+24
View File
@@ -0,0 +1,24 @@
import { Router } from "express";
import RequestValidator from "../../middlewares/request_validator";
import { order_controller } from "./order.controller";
import { order_validations } from "./order.validation";
const router = Router();
router.get("/", order_controller.get_all_order);
router.post(
"/",
RequestValidator(order_validations.create_order),
order_controller.create_order,
);
router.get("/:id", order_controller.get_single_order);
router.patch(
"/:id",
RequestValidator(order_validations.update_order),
order_controller.update_order,
);
router.delete("/:id", order_controller.delete_order);
export default router;
+86
View File
@@ -0,0 +1,86 @@
import { Request } from "express";
import { prisma } from "../../lib/prisma";
import { AppError } from "../../utils/app_error";
const get_all_order_from_db = async (req: Request) => {
// define your own login here
const result = await prisma.order.findMany();
return result;
};
const get_single_order_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params;
const result = await prisma.order.findUnique({ where: { id } });
return result;
};
const create_order_into_db = async (req: Request) => {
// define your own login here
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);
}
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 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 result = await prisma.order.delete({ where: { id } });
return result;
};
export const order_service = {
get_all_order_from_db,
get_single_order_from_db,
create_order_into_db,
update_order_into_db,
delete_order_from_db,
};
+132
View File
@@ -0,0 +1,132 @@
export const orderSwaggerDocs = {
"/api/order": {
post: {
tags: ["order"],
summary: "Create new order",
description: "",
requestBody: {
required: true,
content: {
"application/json": {
example: JSON.stringify({
"productPrice": 1500,
"productQuantity": 2,
"productName": "Wireless Mouse",
"customerName": "Rahim Uddin",
"customerPhone": "+8801712345678",
"customerAddress": "Rangpur, Bangladesh",
"customerNote": "Please deliver between 3-5 PM",
"paymentType": "Cash on Delivery",
"status": "Pending"
}), // put your request body
},
},
},
responses: {
201: { description: "order created successfully" },
500: { description: "Validation error or internal server error" },
},
},
get: {
tags: ["order"],
summary: "Get all order",
description: "",
parameters: [
{
name: "page",
in: "query",
required: false,
schema: { type: "number" },
},
{
name: "limit",
in: "query",
required: false,
schema: { type: "number" },
},
],
responses: {
200: { description: "order fetched successfully" },
401: { description: "unauthorized" },
},
},
},
"/api/order/{id}": {
get: {
tags: ["order"],
summary: "Get single order",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "order fetched successfully" },
401: { description: "unauthorized" },
},
},
patch: {
tags: ["order"],
summary: "Update order",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
requestBody: {
required: true,
content: {
"application/json": {
example: JSON.stringify({
"shopAccountId": "shop_12345",
"productPrice": 1500,
"productQuantity": 2,
"productName": "Wireless Mouse",
"customerName": "Rahim Uddin",
"customerPhone": "+8801712345678",
"customerAddress": "Rangpur, Bangladesh",
"customerNote": "Please deliver between 3-5 PM",
"paymentType": "Cash on Delivery",
"status": "Pending"
}), // put your request body
},
},
},
responses: {
200: { description: "order updated successfully" },
500: { description: "Validation error or internal server error" },
},
},
delete: {
tags: ["order"],
summary: "Delete order",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "order delete successfully" },
401: { description: "unauthorized" },
},
},
},
};
+32
View File
@@ -0,0 +1,32 @@
import { uuid, 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()
});
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()
});
export const order_validations = {
create_order,
update_order,
};
+1 -1
View File
@@ -1,6 +1,6 @@
import { v2 as cloudinary } from 'cloudinary';
import fs from 'fs';
import { configs } from '../configs';
import { configs } from '../errors/configs';
type ICloudinaryResponse = {
asset_id: string;
+21 -21
View File
@@ -1,31 +1,31 @@
import nodemailer from 'nodemailer';
import { configs } from '../configs';
import { configs } from '../errors/configs';
type TMailContent = {
to: string,
subject: string,
textBody: string,
htmlBody: string,
name?: string
to: string,
subject: string,
textBody: string,
htmlBody: string,
name?: string
}
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true, // true for 465, false for other ports
auth: {
user: configs.email.app_email!,
pass: configs.email.app_password!,
},
host: "smtp.gmail.com",
port: 465,
secure: true, // true for 465, false for other ports
auth: {
user: configs.email.app_email!,
pass: configs.email.app_password!,
},
});
// ✅ Email Sender Function
const sendMail = async (payload: TMailContent) => {
const info = await transporter.sendMail({
from: 'info@digitalcreditai.com',
to: payload.to,
subject: payload.subject,
text: payload.textBody,
html: `
const info = await transporter.sendMail({
from: 'info@digitalcreditai.com',
to: payload.to,
subject: payload.subject,
text: payload.textBody,
html: `
<!doctype html>
<html lang="en">
<head>
@@ -116,8 +116,8 @@ const sendMail = async (payload: TMailContent) => {
`,
});
return info
});
return info
};
export default sendMail;
+2
View File
@@ -2,10 +2,12 @@ import { Router } from "express";
import accountRouter from "./app/modules/account/account.route";
import profileRoute from "./app/modules/profile/profile.route";
import planRoute from "./app/modules/plan/plan.route";
import orderRoute from "./app/modules/order/order.route";
const appRouter = Router();
const moduleRoutes = [
{ path: "/order", route: orderRoute },
{ path: "/plan", route: planRoute },
{ path: "/profile", route: profileRoute },{ path: "/auth", route: accountRouter }];
+1 -1
View File
@@ -1,5 +1,5 @@
import app from "./app";
import { configs } from "./app/configs/index";
import { configs } from "./app/errors/configs/index";
import { prisma } from "./app/lib/prisma";
// import "./app/queues/worker";
+6 -4
View File
@@ -1,9 +1,10 @@
import { fileURLToPath } from "node:url";
import path from "path";
import { configs } from "./app/configs";
import { configs } from "./app/errors/configs";
import { accountSwaggerDocs } from "./app/modules/account/account.swagger";
import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger";
import { orderSwaggerDocs } from "./app/modules/order/order.swagger";
import { planSwaggerDocs } from "./app/modules/plan/plan.swagger";
import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -18,8 +19,9 @@ export const swaggerOptions = {
},
paths: {
...accountSwaggerDocs,
...profileSwaggerDocs,
...planSwaggerDocs,
...profileSwaggerDocs,
...planSwaggerDocs,
...orderSwaggerDocs,
},
servers:
configs.env === "production"