From ba1593c84cef03dc7407cb98c7dc0acdb2bdc247 Mon Sep 17 00:00:00 2001 From: Rwirangira Date: Wed, 3 Jul 2024 17:30:21 +0200 Subject: [PATCH] Fixing order status --- public/order.html | 353 ++++++++++++++++++ public/seller.html | 294 +++++++++++++++ src/chatSetup.ts | 12 +- src/controllers/orderStatusController.ts | 95 +++++ .../migrations/20240514160800-orders.js | 4 + src/database/models/order.ts | 6 + src/docs/orders.yaml | 284 ++++++++++++++ src/routes/orderRoute.ts | 5 + src/server.ts | 1 + 9 files changed, 1053 insertions(+), 1 deletion(-) create mode 100644 public/order.html create mode 100644 public/seller.html create mode 100644 src/controllers/orderStatusController.ts diff --git a/public/order.html b/public/order.html new file mode 100644 index 00000000..3f5a822f --- /dev/null +++ b/public/order.html @@ -0,0 +1,353 @@ + + + + + + Order Status + + + +
+

Login

+ + + +
+ +
+

Order Status

+ + + + + + + + + + +
SelectOrder IDStatusActions
+
+ +
+

+ + +
+ +
+

+ +
+ + + + \ No newline at end of file diff --git a/public/seller.html b/public/seller.html new file mode 100644 index 00000000..a839d2d4 --- /dev/null +++ b/public/seller.html @@ -0,0 +1,294 @@ + + + + + + Seller Order Management + + + +
+

Login

+ + + +
+ +
+

Seller Order Management

+ + + + + + + + + + +
SelectProductStatusActions
+
+ +
+

+ + + + + + +
+ + + + \ No newline at end of file diff --git a/src/chatSetup.ts b/src/chatSetup.ts index 9fb666d4..d1ac515d 100644 --- a/src/chatSetup.ts +++ b/src/chatSetup.ts @@ -66,7 +66,13 @@ const disconnected = () => { }; export const socketSetUp = (server: HttpServer) => { - const io = new Server(server); + const io = new Server(server, { + cors: { + origin: '*', + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'], + }, + }); // eslint-disable-next-line @typescript-eslint/no-misused-promises io.use(async (socket: CustomSocket, next) => { const id = findId(socket); @@ -79,5 +85,9 @@ export const socketSetUp = (server: HttpServer) => { socket.on('sentMessage', data => sentMessage(socket, data, io)); socket.on('typing', isTyping => handleTyping(socket, isTyping)); socket.on('disconnect', disconnected); + socket.on('changeOrderStatus', async statusData => { + io.emit('orderStatusChanged', { order: statusData }); + }); }); + return io; }; diff --git a/src/controllers/orderStatusController.ts b/src/controllers/orderStatusController.ts new file mode 100644 index 00000000..1e2d6f37 --- /dev/null +++ b/src/controllers/orderStatusController.ts @@ -0,0 +1,95 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +import { Request, Response } from 'express'; +import { sendErrorResponse } from '../helpers/helper'; +import Order from '../database/models/order'; +import User from '../database/models/user'; +import logger from '../logs/config'; +import { io } from '../server'; +import { sendInternalErrorResponse } from '../validations'; + +export const processOrder = async (req: Request, res: Response) => { + try { + const { orderId } = req.params; + const { id } = req.user as User; + const order = await Order.findOne({ where: { id: orderId, userId: id } }); + + if (!order) { + return sendErrorResponse(res, 'Order not found'); + } + + io.emit('orderProcessed', { order }); + + return res.status(200).json({ + ok: true, + message: `The order is ${order.status}`, + }); + } catch (err) { + logger.error('Error processing order:', err); + return sendInternalErrorResponse(res, err); + } +}; + +export const cancelOrder = async (req: Request, res: Response) => { + try { + const { orderId } = req.params; + const { id } = req.user as User; + const order = await Order.findOne({ where: { id: orderId, userId: id } }); + + if (!order) { + return sendErrorResponse(res, 'Order not found'); + } + console.log(order); + + if (order.status !== 'cancelled' && order.status !== 'pending') { + return sendErrorResponse( + res, + 'Only orders that are only paid only or pending are the only ones that can be cancelled' + ); + } + order.status = 'cancelled'; + await order.save(); + + io.emit('orderCancelled', { order }); + + return res.status(200).json({ + ok: true, + message: 'Order is cancelled successfully !', + data: order, + }); + } catch (err) { + logger.error('Error cancelling order:', err); + return sendInternalErrorResponse(res, err); + } +}; + +export const sellerChangeOrderStatus = async (req: Request, res: Response) => { + try { + const { orderId } = req.params; + const { status, expectedDeliveryDate } = req.body; + const order = await Order.findOne({ + where: { + id: orderId, + }, + }); + + if (!order) { + return sendErrorResponse(res, 'Order not found or you do not have permission to modify this order'); + } + + order.status = status; + if (expectedDeliveryDate) { + order.expectedDeliveryDate = new Date(expectedDeliveryDate); + } + await order.save(); + + io.emit('orderStatusChanged', { order }); + + return res.status(200).json({ + ok: true, + message: 'Order status updated successfully', + }); + } catch (err) { + logger.error('Error changing order status:', err); + return sendInternalErrorResponse(res, err); + } +}; diff --git a/src/database/migrations/20240514160800-orders.js b/src/database/migrations/20240514160800-orders.js index 637522e7..bf93bf22 100644 --- a/src/database/migrations/20240514160800-orders.js +++ b/src/database/migrations/20240514160800-orders.js @@ -49,6 +49,10 @@ module.exports = { type: Sequelize.FLOAT, allowNull: false, }, + expectedDeliveryDate: { + type: Sequelize.DATE, + allowNull: true, + }, createdAt: { type: Sequelize.DATE, allowNull: false, diff --git a/src/database/models/order.ts b/src/database/models/order.ts index ece38146..4f0d45b3 100644 --- a/src/database/models/order.ts +++ b/src/database/models/order.ts @@ -16,6 +16,7 @@ interface OrderAttributes { userId: string; zipCode?: string; totalPrice: number; + expectedDeliveryDate?: Date; } interface OrderCreationAttributes extends Optional {} @@ -33,6 +34,7 @@ class Order extends Model implements O public userId!: string; public totalPrice!: number; public zipCode?: string; + public expectedDeliveryDate?: Date; } Order.init( @@ -82,6 +84,10 @@ Order.init( type: DataTypes.FLOAT, allowNull: false, }, + expectedDeliveryDate: { + type: DataTypes.DATE, + allowNull: true, + }, createdAt: { type: DataTypes.DATE, allowNull: false, diff --git a/src/docs/orders.yaml b/src/docs/orders.yaml index 6cc7ad07..03e05071 100644 --- a/src/docs/orders.yaml +++ b/src/docs/orders.yaml @@ -298,3 +298,287 @@ components: - shipped - delivered - cancelled + /api/orders/{orderId}/process: + put: + summary: Process an order + description: Updates the status of an order for the authenticated buyer + tags: [Orders] + security: + - bearerAuth: [] + parameters: + - in: path + name: orderId + required: true + schema: + type: string + description: The order ID + responses: + '200': + description: Order processed successfully + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: true + message: + type: string + example: 'Order processed successfully' + data: + $ref: '#/components/schemas/Order' + '404': + description: Order not found + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: false + message: + type: string + example: 'Order not found' + '500': + description: Internal server error + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: false + message: + type: string + example: 'Internal server error' + + /api/orders/{orderId}/change-status: + put: + summary: Change order status + description: Updates the status of an order for the authenticated admin + tags: [Orders] + security: + - bearerAuth: [] + parameters: + - in: path + name: orderId + required: true + schema: + type: string + description: The order ID + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: 'delivered' + expectedDeliveryDate: + type: string + format: date + example: '2024-12-31' + responses: + '200': + description: Order status updated successfully + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: true + message: + type: string + example: 'Order status updated successfully' + data: + $ref: '#/components/schemas/Order' + '404': + description: Order not found + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: false + message: + type: string + example: 'Order not found' + '500': + description: Internal server error + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: false + message: + type: string + example: 'Internal server error' + + /api/orders/{orderId}/cancel: + put: + summary: Cancel an order + description: Allows a buyer to cancel an order if it is in the "Paid" status + tags: [Orders] + security: + - bearerAuth: [] + parameters: + - in: path + name: orderId + required: true + schema: + type: string + description: The order ID + responses: + '200': + description: Order cancelled successfully + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: true + message: + type: string + example: 'Order cancelled successfully' + data: + $ref: '#/components/schemas/Order' + '404': + description: Order not found + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: false + message: + type: string + example: 'Order not found' + '500': + description: Internal server error + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + example: false + message: + type: string + example: 'Internal server error' + +components: + schemas: + Order: + type: object + properties: + _id: + type: string + example: '5f5f12345678' + buyer: + type: string + example: 'buyer123' + orderItems: + type: array + items: + $ref: '#/components/schemas/OrderItem' + shippingAddress1: + type: string + example: '123 Main St' + shippingAddress2: + type: string + example: 'Apt 4B' + city: + type: string + example: 'New York' + country: + type: string + example: 'USA' + phone: + type: string + example: '123-456-7890' + zipCode: + type: string + example: '10001' + status: + type: string + example: 'Pending' + expectedDeliveryDate: + type: string + format: date + example: '2024-12-31' + totalPrice: + type: number + format: double + example: 99.99 + paymentMethod: + type: string + example: 'Credit Card' + transactionId: + type: string + example: 'trans_123456' + createdAt: + type: string + format: date-time + example: '2024-06-01T12:34:56Z' + updatedAt: + type: string + format: date-time + example: '2024-06-10T12:34:56Z' + + OrderItem: + type: object + properties: + product: + $ref: '#/components/schemas/Product' + quantity: + type: integer + example: 2 + + Product: + type: object + properties: + _id: + type: string + example: 'product123' + name: + type: string + example: 'Product Name' + price: + type: number + format: double + example: 49.99 + category: + type: string + example: 'Electronics' + description: + type: string + example: 'Product description here' + imageUrl: + type: string + example: 'https://example.com/product.jpg' + seller: + type: string + example: 'seller123' + +securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file diff --git a/src/routes/orderRoute.ts b/src/routes/orderRoute.ts index 6aec9dad..9dce194d 100644 --- a/src/routes/orderRoute.ts +++ b/src/routes/orderRoute.ts @@ -1,6 +1,7 @@ import { Router } from 'express'; import { checkUserRoles, isAuthenticated } from '../middlewares/authMiddlewares'; import { getUserOrders, createOrder, deleteOrder, sellerProductOrders } from '../controllers/orderController'; +import { processOrder, cancelOrder, sellerChangeOrderStatus } from '../controllers/orderStatusController'; const orderRouter = Router(); orderRouter @@ -9,4 +10,8 @@ orderRouter .post(isAuthenticated, checkUserRoles('buyer'), createOrder); orderRouter.route('/get-orders').get(isAuthenticated, checkUserRoles('seller'), sellerProductOrders); orderRouter.route('/:id').delete(isAuthenticated, checkUserRoles('admin'), deleteOrder); +orderRouter.get('/:orderId/check-status', isAuthenticated, checkUserRoles('buyer'), processOrder); +orderRouter.put('/:orderId/cancel', isAuthenticated, checkUserRoles('buyer'), cancelOrder); +orderRouter.put('/:orderId/seller-change-status', isAuthenticated, checkUserRoles('seller'), sellerChangeOrderStatus); +orderRouter.get('/seller-products-status', isAuthenticated, checkUserRoles('seller'), sellerProductOrders); export default orderRouter; diff --git a/src/server.ts b/src/server.ts index 157e25d9..739440e0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -68,3 +68,4 @@ const server = app.listen(PORT, () => { }); socketSetUp(server); +export const io = socketSetUp(server);