diff --git a/src/controllers/getOrderController.ts b/src/controllers/getOrderController.ts
new file mode 100644
index 0000000..66f8541
--- /dev/null
+++ b/src/controllers/getOrderController.ts
@@ -0,0 +1,48 @@
+import { Request, Response } from "express"
+import { getOrderBuyer, updateOrderStatusService } from "../services/OrderService"
+import * as mailService from "../services/mail.service";
+import { SUBJECTS, UserId } from "../types";
+import { orderStatusTemplate } from "../email-templates/orderStatus";
+import Order from "../sequelize/models/orders";
+import User from "../sequelize/models/users";
+
+
+export const getOrderController = async (req: Request, res: Response) => {
+ try {
+ const response = await getOrderBuyer(req, res)
+ return res.status(200).json({
+ status: 200,
+ length: response?.length,
+ orders: response?.length === 0? "You have not order yet" : response
+ })
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({
+ status: 500,
+ message: `error accured during fetching order ${error}`
+ })
+ }
+}
+
+export const updateOrderStatus = async (req: Request, res: Response) => {
+
+ try {
+ const { id: userId} = req.user as UserId;
+ const { id: orderId } = req.params;
+ const { status } = req.body;
+ const buyerId = await Order.findByPk(orderId)
+ const dataValues = await User.findByPk(buyerId?.buyerId)
+
+ const updatedItems = await updateOrderStatusService(userId, orderId, status);
+ if (updatedItems) {
+ // @ts-ignore
+ await mailService.sendNotification(dataValues?.dataValues.email, SUBJECTS.ORDER_STATUS_UPDATED, orderStatusTemplate(dataValues?.dataValues.username, buyerId?.id, buyerId?.createdAt, status))
+
+ return res.json({ message: 'Order items status updated', updatedItems });
+ }
+
+ } catch (error: any) {
+ res.status(500).json({ message: error.message });
+ }
+ };
+
\ No newline at end of file
diff --git a/src/docs/orders.ts b/src/docs/orders.ts
new file mode 100644
index 0000000..db5eabb
--- /dev/null
+++ b/src/docs/orders.ts
@@ -0,0 +1,74 @@
+import { required } from "joi";
+
+export const StatusSchema = {
+ type: "object",
+ properties: {
+ status: {
+ type: "string",
+ },
+ },
+};
+
+export const buyerAndSellerOrder = {
+ tags: ["orders"],
+ security: [{ bearerAuth: [] }],
+ summary: "Orders for seller and buyers",
+ responses: {
+ 200: {
+ description: "Success",
+ },
+ 400: {
+ description: "Bad request",
+ },
+ 401: {
+ description: "Unauthorized",
+ },
+ 500: {
+ description: "Internal server error",
+ },
+ },
+};
+
+export const statusUpdate = {
+ tags: ["orders"],
+ security: [{ bearerAuth: [] }],
+ summary: "Status update for seller",
+ parameters: [
+ {
+ name: "id",
+ in: "path",
+ required: true,
+ description: "order Id",
+ schema: {
+ type: "number",
+ },
+ },
+ ],
+ requestBody: {
+ required: true,
+ content: {
+ "application/json": {
+ schema: {
+ type: "object",
+ properties: {
+ status: {
+ type: "string",
+ },
+ },
+ required: ["status"],
+ },
+ },
+ },
+ },
+ responses: {
+ 201: {
+ description: "Upgrade successfully",
+ },
+ 404: {
+ description: "Review not found",
+ },
+ 500: {
+ description: "Internal server error.",
+ },
+ },
+};
diff --git a/src/docs/swagger.ts b/src/docs/swagger.ts
index db1d631..d944cb1 100644
--- a/src/docs/swagger.ts
+++ b/src/docs/swagger.ts
@@ -41,7 +41,8 @@ import { homepage } from "./home";
import { payment } from "./payments";
import { createReviewProduct, deleteReview, getReviewProduct, reviewSchema, updateReviewProduct } from "./reviews";
import { getAdProducts } from "./products";
-import { PrivateChatSchema, getAllUserPrivateChats, getUserToUserPrivateMessages, createPrivateChat } from "./privateChatDoc";
+import {PrivateChatSchema, getAllUserPrivateChats, getUserToUserPrivateMessages, createPrivateChat } from "./privateChatDoc"
+import { StatusSchema, buyerAndSellerOrder, statusUpdate } from "./orders";
const docRouter = express.Router();
@@ -79,6 +80,7 @@ const options = {
{ name: "Carts", description: "Endpoints related to Cart" },
{ name: "Payments", description: "Endpoints related to payments" },
{ name: "PrivateChat", description: "Endpoints related to Private Chat" },
+ {name: "orders", description: "Endpoints related to orders"}
],
paths: {
@@ -198,6 +200,12 @@ const options = {
post: createPrivateChat,
get: getUserToUserPrivateMessages,
},
+ "/api/v1/orders": {
+ get: buyerAndSellerOrder
+ },
+ "/api/v1/orders/{id}/status": {
+ patch: statusUpdate
+ }
},
components: {
schemas: {
@@ -211,6 +219,7 @@ const options = {
Wish: wishSchema,
Review: reviewSchema,
PrivateChat: PrivateChatSchema,
+ status: StatusSchema
},
securitySchemes: {
bearerAuth: {
diff --git a/src/email-templates/orderStatus.ts b/src/email-templates/orderStatus.ts
new file mode 100644
index 0000000..32e80e7
--- /dev/null
+++ b/src/email-templates/orderStatus.ts
@@ -0,0 +1,63 @@
+export const orderStatusTemplate = (username: string, orderId: number, createdAt: Date, status: string) => {
+ return `
+
+
+
+
+
+ Order Status
+
+
+
+
+
+
Order Status Updated 😍
+
Dear ${username}, Your product status with this order id #${orderId} has been successfully to ${status} .
+
Status updated at ${createdAt}
+
+
+
+
+ `;
+};
diff --git a/src/routes/index.ts b/src/routes/index.ts
index 138224d..2be3cae 100644
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -8,6 +8,7 @@ import cartRoutes from "./cartRoutes";
import notificationRoutes from "./notificationRoutes";
import paymentRouter from "./paymentRoutes";
import PrivateChatRoutes from "./privateChatRoutes";
+import orderRouter from "./orderRoute";
const appROutes = Router();
appROutes.use("/chats", PrivateChatRoutes);
@@ -19,4 +20,5 @@ appROutes.use("/messages", joinChatRoomRoutes);
appROutes.use("/carts", cartRoutes);
appROutes.use("/notifications", notificationRoutes);
appROutes.use("/payment", paymentRouter);
+appROutes.use("/orders", orderRouter)
export default appROutes;
diff --git a/src/routes/orderRoute.ts b/src/routes/orderRoute.ts
new file mode 100644
index 0000000..13eede7
--- /dev/null
+++ b/src/routes/orderRoute.ts
@@ -0,0 +1,12 @@
+import express from "express"
+import { getOrderController, updateOrderStatus, } from "../controllers/getOrderController";
+import { isLoggedIn } from "../middlewares/isLoggedIn";
+import { isAseller } from "../middlewares/sellerAuth";
+
+
+const orderRouter = express.Router()
+
+orderRouter.get('/', isLoggedIn, getOrderController)
+orderRouter.patch("/:id/status", isLoggedIn, updateOrderStatus)
+
+export default orderRouter;
\ No newline at end of file
diff --git a/src/schemas/signUpSchema.ts b/src/schemas/signUpSchema.ts
index 46a55e7..9827713 100644
--- a/src/schemas/signUpSchema.ts
+++ b/src/schemas/signUpSchema.ts
@@ -36,3 +36,9 @@ export const profileSchemas = Joi.object({
country: Joi.string()
.optional()
})
+
+export const OrderStatus = Joi.object({
+ status: Joi.string()
+ .valid("Pending", "Delivered", "Cancelled")
+ .required()
+})
diff --git a/src/sequelize/migrations/x20240711191430-modify-order-items.js b/src/sequelize/migrations/x20240711191430-modify-order-items.js
new file mode 100644
index 0000000..eff03e2
--- /dev/null
+++ b/src/sequelize/migrations/x20240711191430-modify-order-items.js
@@ -0,0 +1,31 @@
+module.exports = {
+ async up(queryInterface, Sequelize) {
+ return Promise.all([
+ queryInterface.addColumn(
+ 'orderItems',
+ 'userId',
+ {
+ type: Sequelize.INTEGER,
+ allowNull: true,
+ },
+ ),
+ queryInterface.addColumn(
+ 'orderItems',
+ 'status',
+ {
+ type: Sequelize.ENUM,
+ allowNull: false,
+ values: ["Pending", "Delivered", "Cancelled"],
+ defaultValue: 'Pending'
+ },
+ ),
+ ]);
+ },
+
+ down(queryInterface, Sequelize) {
+ return Promise.all([
+ queryInterface.removeColumn('orderItems', 'status'),
+ queryInterface.removeColumn('orderItems', 'userId'),
+ ]);
+ },
+};
\ No newline at end of file
diff --git a/src/sequelize/models/orderItems.ts b/src/sequelize/models/orderItems.ts
index 26f9e13..d88d986 100644
--- a/src/sequelize/models/orderItems.ts
+++ b/src/sequelize/models/orderItems.ts
@@ -1,6 +1,9 @@
import { DataTypes, Model } from "sequelize";
import sequelize from "../../config/dbConnection";
import Product from "./products";
+import Order from "./orders";
+import User from "./users"
+import Profile from "./profiles";
export interface orderItemAttributes {
@@ -56,5 +59,10 @@ OrderItem.belongsTo(Product, {
foreignKey: "productId",
as: "product"
});
+OrderItem.belongsTo(User,{
+ foreignKey: "userId",
+ as: "user"
+})
+
export default OrderItem;
\ No newline at end of file
diff --git a/src/sequelize/models/orders.ts b/src/sequelize/models/orders.ts
index b09b543..316024a 100644
--- a/src/sequelize/models/orders.ts
+++ b/src/sequelize/models/orders.ts
@@ -2,6 +2,7 @@ import { Model, DataTypes } from "sequelize";
import sequelize from "../../config/dbConnection";
import User from "./users";
import OrderItem from "./orderItems";
+import Product from "./products";
export interface OrderAttributes {
@@ -38,7 +39,7 @@ Order.init({
},
status: {
type: DataTypes.ENUM,
- values: ["pending", "delivered", "canceled"],
+ values: ["Pending", "Delivered", "Cancelled"],
defaultValue: "pending",
allowNull: false,
},
@@ -62,5 +63,8 @@ Order.init({
Order.belongsTo(User, { foreignKey: "buyerId", as: "buyer" });
User.hasMany(Order, { foreignKey: "buyerId", as: "orders" });
Order.hasMany(OrderItem, { foreignKey: "orderId", as: "items" });
+Order.hasMany(OrderItem, { foreignKey: "status", as: "statuses"})
+Order.hasMany(Product, { foreignKey: "id", as: "products"})
+OrderItem.belongsTo(Order, { foreignKey: 'orderId', as: 'order' });
export default Order
\ No newline at end of file
diff --git a/src/services/OrderService.ts b/src/services/OrderService.ts
new file mode 100644
index 0000000..60743d9
--- /dev/null
+++ b/src/services/OrderService.ts
@@ -0,0 +1,104 @@
+// @ts-nocheck
+import { Request, Response } from "express";
+import OrderItem from "../sequelize/models/orderItems";
+import Order from "../sequelize/models/orders";
+import User from "../sequelize/models/users";
+import Product from "../sequelize/models/products";
+import { authStatus } from "../utils/isSellerOrNormalUser";
+import { Role } from "../sequelize/models/roles";
+import Profile from "../sequelize/models/profiles";
+
+export const getOrderBuyer = async (req: Request, res: Response) => {
+ try {
+ await authStatus(req, res);
+ // @ts-ignore
+ const { roleId, id } = req.user;
+ const role = await Role.findByPk(roleId);
+
+ if (role?.name === "seller") {
+ const products = await Product.findAll({
+ where: { userId: id },
+ });
+ const productIds = products.map((product) => product.id);
+
+ const sellerOrders = await OrderItem.findAll({
+ where: { productId: productIds },
+ include: [
+ {
+ model: Order,
+ as: "order",
+ include: [{ model: User, as: "buyer", include: [{ model: Profile, as: "profile" }] }],
+ },
+ { model: User, as: "user" },
+ { model: Product, as: "product" },
+ ],
+ });
+
+ const groupedOrders = sellerOrders.reduce((acc, item) => {
+ const { orderId } = item;
+ if (!acc[orderId]) {
+ acc[orderId] = {
+ order: item.order,
+ products: [],
+ };
+ }
+ acc[orderId].products.push(item);
+ return acc;
+ }, {});
+
+ return Object.values(groupedOrders);
+ } else {
+ const buyerOrders = await Order.findAll({
+ where: { buyerId: id },
+ include: [
+ {
+ model: OrderItem,
+ as: "items",
+ include: [{ model: Product, as: "product" }],
+ },
+ { model: User, as: "buyer", include: [{ model: Profile, as: "profile" }] },
+ ],
+ });
+
+ const groupedOrders = buyerOrders.map((order) => ({
+ order,
+ products: order.items,
+ }));
+
+ return groupedOrders;
+ }
+ } catch (error: any) {
+ throw new Error(error.message);
+ }
+};
+
+export const updateOrderStatusService = async (userId: string, orderId: string, status: string) => {
+ const user = await User.findByPk(userId);
+ if (user?.roleId !== 2) {
+ throw new Error("Unauthorized to update order status");
+ }
+
+ const orderItems = await OrderItem.findAll({
+ where: { orderId },
+ include: [
+ {
+ model: Product,
+ as: "product",
+ where: { userId },
+ },
+ {
+ model: Order,
+ as: "order",
+ },
+ ],
+ });
+
+ if (!orderItems.length) {
+ throw new Error("Order items not found or not associated with this seller");
+ }
+ for (const item of orderItems) {
+ item.status = status;
+ await item.save();
+ }
+ return orderItems;
+};
diff --git a/src/services/payment.service.ts b/src/services/payment.service.ts
index 31edbcf..a7259ff 100644
--- a/src/services/payment.service.ts
+++ b/src/services/payment.service.ts
@@ -57,8 +57,10 @@ export const placeOrder = async (cart: CartAttributes) => {
const cartItem = await OrderItem.create(
{
orderId: order.id as number,
+ // @ts-ignore
+ userId: item.product.userId ,
productId: item.productId,
- quantity: item.quantity
+ quantity: item.quantity,
}
)
};
diff --git a/src/types.ts b/src/types.ts
index 5701d6e..9d26638 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -17,7 +17,8 @@ export enum SUBJECTS {
REQUEST_2FA = "Request for 2FA",
VERIFY_LOGIN = "Verify that It's you",
DISABLE_2FA = "Disable 2-Factor Authentication",
- PAYMENT_CONFIRMATION = "Payment Confirmation"
+ PAYMENT_CONFIRMATION = "Payment Confirmation",
+ ORDER_STATUS_UPDATED = "Status order updated."
}
export enum STATUS {
@@ -26,6 +27,10 @@ export enum STATUS {
FAILED = "Failed",
}
+export interface UserId {
+ id: string
+}
+
export interface ProductType{
id?:number,
name:string,