codeweaver 1.0.15 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Zod schema for Order entity
5
+ * @typedef {Object} ZodOrder
6
+ * @property {number} id - Unique identifier (min 1)
7
+ * @property {number} userId - Associated user ID (min 1)
8
+ * @property {{productId: number, quantity: number}[]} products - Array of ordered products
9
+ * @property {"Processing"|"Delivered"|"Canceled"} status - Order status
10
+ * @property {number} total - Total price (min 1000)
11
+ * @property {Date} createdAt - Creation timestamp
12
+ * @property {Date} [canceledAt] - Cancellation timestamp
13
+ * @property {Date} [deliveredAt] - Delivery timestamp
14
+ */
15
+ export const ZodOrder = z.object({
16
+ id: z.number().min(1).int(),
17
+ userId: z.number().min(1).int(),
18
+ products: z.array(
19
+ z.object({
20
+ productId: z.number().min(1).int(),
21
+ quantity: z.number().min(1).int(),
22
+ })
23
+ ),
24
+ status: z.enum(["Processing", "Delivered", "Canceled"]),
25
+ total: z.number().min(1000),
26
+ createdAt: z.date(),
27
+ canceledAt: z.date().optional(),
28
+ deliveredAt: z.date().optional(),
29
+ });
30
+
31
+ export const ZodOrderCreationDto = ZodOrder.omit({
32
+ id: true,
33
+ status: true,
34
+ createdAt: true,
35
+ canceledAt: true,
36
+ deliveredAt: true,
37
+ });
38
+
39
+ export type Order = z.infer<typeof ZodOrder>;
40
+ export type OrderCreationDto = z.infer<typeof ZodOrderCreationDto>;
@@ -1,39 +1,160 @@
1
1
  import { Router, Request, Response } from "express";
2
2
  import asyncHandler from "express-async-handler";
3
+ import OrderController from "./order.controller";
4
+ import { sendError } from "@/utilities";
3
5
 
4
6
  const router = Router();
7
+ const orderController = new OrderController();
8
+
9
+ /**
10
+ * @swagger
11
+ * /orders:
12
+ * post:
13
+ * summary: Create a new order
14
+ * description: Creates an order with user details and products.
15
+ * consumes:
16
+ * - application/json
17
+ * produces:
18
+ * - application/json
19
+ * parameters:
20
+ * - in: body
21
+ * name: order
22
+ * required: true
23
+ * schema:
24
+ * type: object
25
+ * required:
26
+ * - userId
27
+ * - products
28
+ * - total
29
+ * properties:
30
+ * userId:
31
+ * type: integer
32
+ * minimum: 1
33
+ * example: 1
34
+ * products:
35
+ * type: array
36
+ * description: Array of products in the order
37
+ * items:
38
+ * type: object
39
+ * required:
40
+ * - productId
41
+ * - quantity
42
+ * properties:
43
+ * productId:
44
+ * type: integer
45
+ * minimum: 1
46
+ * example: 3
47
+ * quantity:
48
+ * type: integer
49
+ * minimum: 1
50
+ * example: 2
51
+ * total:
52
+ * type: number
53
+ * minimum: 1000
54
+ * example: 250000
55
+ * responses:
56
+ * 201:
57
+ * description: Order created
58
+ */
59
+ router.post(
60
+ "/",
61
+ asyncHandler(async (req: Request, res: Response) => {
62
+ const order = await orderController.create(req.body);
63
+ res.status(201).json(order);
64
+ })
65
+ );
5
66
 
6
67
  /**
7
68
  * @swagger
8
69
  * /orders:
9
70
  * get:
10
- * summary: Get order home
11
- * description: Returns the order home page.
71
+ * summary: Get all orders
12
72
  * responses:
13
73
  * 200:
14
- * description: Order home page
74
+ * description: List of orders
15
75
  */
16
76
  router.get(
17
77
  "/",
18
78
  asyncHandler(async (req: Request, res: Response) => {
19
- res.send("Order Home");
79
+ const orders = await orderController.getAll();
80
+ res.json(orders);
20
81
  })
21
82
  );
22
83
 
23
84
  /**
24
85
  * @swagger
25
- * /orders/history:
86
+ * /orders/{id}:
26
87
  * get:
27
- * summary: Get order history
28
- * description: Returns order history.
88
+ * summary: Get order details
89
+ * parameters:
90
+ * - name: id
91
+ * in: path
92
+ * required: true
93
+ * description: Numeric ID of the order to retrieve
94
+ * type: integer
95
+ * example: 101
29
96
  * responses:
30
97
  * 200:
31
- * description: Order history
98
+ * description: Order details
32
99
  */
33
100
  router.get(
34
- "/history",
101
+ "/:id",
102
+ asyncHandler(async (req: Request, res: Response) => {
103
+ const order = await orderController.get(req.params.id);
104
+ if ("id" in order == false) sendError(res, order);
105
+ else res.json(order);
106
+ })
107
+ );
108
+
109
+ /**
110
+ * @swagger
111
+ * /orders/{id}/cancel:
112
+ * patch:
113
+ * summary: Cancel order
114
+ * parameters:
115
+ * - name: id
116
+ * in: path
117
+ * required: true
118
+ * description: Numeric ID of the order to cancel
119
+ * type: integer
120
+ * example: 101
121
+ * responses:
122
+ * 200:
123
+ * description: Order canceled successfully
124
+ */
125
+ router.patch(
126
+ "/:id/cancel",
127
+ asyncHandler(async (req: Request, res: Response) => {
128
+ const order = await orderController.cancel(req.params.id);
129
+ if ("id" in order == false) sendError(res, order);
130
+ else res.json(order);
131
+ })
132
+ );
133
+
134
+ /**
135
+ * @swagger
136
+ * /orders/{id}/deliver:
137
+ * patch:
138
+ * summary: Mark order as delivered
139
+ * parameters:
140
+ * - name: id
141
+ * in: path
142
+ * required: true
143
+ * description: Numeric ID of the order to deliver
144
+ * type: integer
145
+ * example: 101
146
+ * responses:
147
+ * 200:
148
+ * description: Order marked as delivered
149
+ * 400:
150
+ * description: Delivery is only available when the order is in processing status.
151
+ */
152
+ router.patch(
153
+ "/:id/deliver",
35
154
  asyncHandler(async (req: Request, res: Response) => {
36
- res.send("Order History");
155
+ const order = await orderController.deliver(req.params.id);
156
+ if ("id" in order == false) sendError(res, order);
157
+ else res.json(order);
37
158
  })
38
159
  );
39
160
 
@@ -0,0 +1,191 @@
1
+ import { onError, rateLimit, timeout } from "utils-decorators";
2
+ import {
3
+ Order,
4
+ OrderCreationDto,
5
+ ZodOrderCreationDto,
6
+ } from "./dto/order.dto";
7
+ import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
8
+ import { ResponseError } from "@/types";
9
+ import { tryParseId } from "@/utilities";
10
+
11
+ // Array to store orders (as a mock database)
12
+ const orders: Order[] = [
13
+ {
14
+ id: 1,
15
+ userId: 1,
16
+ products: [
17
+ { productId: 2, quantity: 1 },
18
+ { productId: 6, quantity: 2 },
19
+ ],
20
+ status: "Delivered",
21
+ total: 14400,
22
+ createdAt: new Date("2024-01-15"),
23
+ deliveredAt: new Date("2024-02-10"),
24
+ },
25
+ {
26
+ id: 2,
27
+ userId: 3,
28
+ products: [
29
+ { productId: 9, quantity: 1 },
30
+ { productId: 7, quantity: 1 },
31
+ ],
32
+ status: "Processing",
33
+ total: 36500,
34
+ createdAt: new Date("2024-03-20"),
35
+ },
36
+ {
37
+ id: 3,
38
+ userId: 2,
39
+ products: [
40
+ { productId: 1, quantity: 1 },
41
+ { productId: 4, quantity: 2 },
42
+ ],
43
+ status: "Canceled",
44
+ total: 81000,
45
+ createdAt: new Date("2024-05-01"),
46
+ canceledAt: new Date("2024-05-03"),
47
+ },
48
+ ];
49
+
50
+ function exceedHandler() {
51
+ const message = "Too much call in allowed window";
52
+
53
+ throw new Error(message, {
54
+ cause: { status: 500, message } satisfies ResponseError,
55
+ });
56
+ }
57
+
58
+ function getOrderErrorHandler(e: Error) {
59
+ const message = "Order not found.";
60
+
61
+ throw new Error(message, {
62
+ cause: { status: 404, message, details: e.message } satisfies ResponseError,
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Controller for handling order-related operations
68
+ * @class OrderController
69
+ * @desc Provides methods for order management including creation, status updates and retrieval
70
+ */
71
+ export default class OrderController {
72
+ @rateLimit({
73
+ timeSpanMs: 60000,
74
+ allowedCalls: 300,
75
+ exceedHandler,
76
+ })
77
+ @Validate
78
+ /**
79
+ * Create a new order
80
+ * @param {OrderCreationDto} order - Order creation data
81
+ * @returns {Promise<Order>} Newly created order
82
+ */
83
+ public async create(@ZodInput(ZodOrderCreationDto) order: OrderCreationDto) {
84
+ const newOrder = {
85
+ ...order,
86
+ id: orders.length + 1,
87
+ createdAt: new Date(),
88
+ status: "Processing",
89
+ } satisfies Order;
90
+
91
+ orders.push(newOrder);
92
+ return newOrder;
93
+ }
94
+
95
+ @timeout(20000)
96
+ @rateLimit({
97
+ timeSpanMs: 60000,
98
+ allowedCalls: 300,
99
+ exceedHandler,
100
+ })
101
+ /**
102
+ * Retrieves all orders
103
+ * @returns List of orders
104
+ */
105
+ public async getAll(): Promise<Order[]> {
106
+ return orders;
107
+ }
108
+
109
+ @rateLimit({
110
+ timeSpanMs: 60000,
111
+ allowedCalls: 300,
112
+ exceedHandler,
113
+ })
114
+ @onError({
115
+ func: getOrderErrorHandler,
116
+ })
117
+ /**
118
+ * Finds an order by its ID
119
+ * @param id - Order ID as string
120
+ * @returns Order details or error object if not found
121
+ */
122
+ public async get(id: string): Promise<Order | ResponseError> {
123
+ const orderId = tryParseId(id);
124
+ if (typeof orderId != "number") return orderId satisfies ResponseError;
125
+ const order = orders.find((order) => order.id === orderId);
126
+
127
+ if (!order)
128
+ return {
129
+ status: 404,
130
+ message: "Order dose not exist.",
131
+ } satisfies ResponseError;
132
+
133
+ return order satisfies Order;
134
+ }
135
+
136
+ @rateLimit({
137
+ timeSpanMs: 60000,
138
+ allowedCalls: 300,
139
+ exceedHandler,
140
+ })
141
+ /**
142
+ * Cancel an existing order
143
+ * @param {string} id - Order ID to cancel
144
+ * @returns {Promise<Order | ResponseError>} Updated order or error object
145
+ * @throws {ResponseError} 404 - Order not found
146
+ * @throws {ResponseError} 400 - Invalid ID format or invalid status for cancellation
147
+ */
148
+ public async cancel(id: string): Promise<Order | ResponseError> {
149
+ let order = await this.get(id);
150
+ if ("id" in order == false) return order satisfies ResponseError;
151
+
152
+ if (order.status != "Processing")
153
+ return {
154
+ status: 400,
155
+ message:
156
+ "Cancellation is not available unless the order is in processing status.",
157
+ } satisfies ResponseError;
158
+
159
+ order.status = "Canceled";
160
+ order.deliveredAt = new Date();
161
+ return order satisfies Order;
162
+ }
163
+
164
+ @rateLimit({
165
+ timeSpanMs: 60000,
166
+ allowedCalls: 300,
167
+ exceedHandler,
168
+ })
169
+ /**
170
+ * Mark an order as delivered
171
+ * @param {string} id - Order ID to mark as delivered
172
+ * @returns {Promise<Order | ResponseError>} Updated order or error object
173
+ * @throws {ResponseError} 404 - Order not found
174
+ * @throws {ResponseError} 400 - Invalid ID format or invalid status for delivery
175
+ */
176
+ public async deliver(id: string): Promise<Order | ResponseError> {
177
+ let order = await this.get(id);
178
+ if ("id" in order == false) return order satisfies ResponseError;
179
+
180
+ if (order.status != "Processing")
181
+ return {
182
+ status: 400,
183
+ message:
184
+ "Delivery is only available when the order is in processing status.",
185
+ } satisfies ResponseError;
186
+
187
+ order.status = "Delivered";
188
+ order.deliveredAt = new Date();
189
+ return order satisfies Order;
190
+ }
191
+ }
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Zod schema for Product entity
5
+ * @typedef {Object} ZodProduct
6
+ * @property {number} id - Unique identifier (min 1)
7
+ * @property {string} name - Product name (min 2 chars)
8
+ * @property {number} price - Product price (min 1000)
9
+ * @property {string} [description] - Optional description (min 10 chars)
10
+ * @property {string} category - Product category from predefined enum
11
+ * @property {number} stock - Available stock quantity (min 0)
12
+ */
13
+ export const ZodProduct = z.object({
14
+ id: z.number().min(1).int(),
15
+ name: z.string().min(2),
16
+ price: z.number().min(1000),
17
+ description: z.string().min(10).optional(),
18
+ category: z.enum([
19
+ "Electronics",
20
+ "Appliances",
21
+ "Sports",
22
+ "Kitchen",
23
+ "Mobile Accessories",
24
+ "Computer Accessories",
25
+ "Home Appliances",
26
+ "Books",
27
+ ]),
28
+ stock: z.number().min(0).int(),
29
+ });
30
+
31
+ export const ZodProductCreationDto = ZodProduct.omit({ id: true });
32
+ export const ZodProductUpdateDto = ZodProductCreationDto.partial();
33
+
34
+ export type Product = z.infer<typeof ZodProduct>;
35
+ export type ProductCreationDto = z.infer<typeof ZodProductCreationDto>;
36
+ export type ProductUpdateDto = z.infer<typeof ZodProductUpdateDto>;
@@ -1,20 +1,10 @@
1
1
  import { Router, Request, Response } from "express";
2
2
  import asyncHandler from "express-async-handler";
3
+ import ProductController from "./product.controller";
4
+ import { sendError } from "@/utilities";
3
5
 
4
6
  const router = Router();
5
-
6
- // Array to store products (as a mock database)
7
- const products = [
8
- { id: 1, name: "Product1" },
9
- { id: 2, name: "Product2" },
10
- { id: 3, name: "Product3" },
11
- { id: 4, name: "Product4" },
12
- { id: 5, name: "Product5" },
13
- { id: 6, name: "Product6" },
14
- { id: 7, name: "Product7" },
15
- { id: 8, name: "Product8" },
16
- { id: 9, name: "Product9" },
17
- ];
7
+ const productController = new ProductController();
18
8
 
19
9
  // CRUD Routes
20
10
 
@@ -22,28 +12,59 @@ const products = [
22
12
  * @swagger
23
13
  * /products:
24
14
  * post:
25
- * summary: Create an product
15
+ * summary: Create a product
26
16
  * description: Create a new product.
27
- * requestBody:
28
- * required: true
29
- * content:
30
- * application/json:
31
- * schema:
32
- * type: object
33
- * properties:
34
- * name:
35
- * type: string
36
- * description:
37
- * type: string
17
+ * consumes:
18
+ * - application/json
19
+ * produces:
20
+ * - application/json
21
+ * parameters:
22
+ * - in: body
23
+ * name: product
24
+ * required: true
25
+ * schema:
26
+ * type: object
27
+ * required:
28
+ * - name
29
+ * - price
30
+ * - category
31
+ * - stock
32
+ * properties:
33
+ * id:
34
+ * type: integer
35
+ * example: 1
36
+ * name:
37
+ * type: string
38
+ * example: "New Product"
39
+ * price:
40
+ * type: number
41
+ * example: 1499
42
+ * description:
43
+ * type: string
44
+ * example: "This is a detailed description."
45
+ * category:
46
+ * type: string
47
+ * enum:
48
+ * - Electronics
49
+ * - Appliances
50
+ * - Sports
51
+ * - Kitchen
52
+ * - Mobile Accessories
53
+ * - Computer Accessories
54
+ * - Home Appliances
55
+ * - Books
56
+ * example: "Electronics"
57
+ * stock:
58
+ * type: integer
59
+ * example: 50
38
60
  * responses:
39
61
  * 201:
40
- * description: product created
62
+ * description: Product created
41
63
  */
42
64
  router.post(
43
65
  "/",
44
66
  asyncHandler(async (req: Request, res: Response) => {
45
- const product = { id: products.length + 1, name: req.body.name };
46
- products.push(product);
67
+ const product = await productController.create(req.body);
47
68
  res.status(201).json(product);
48
69
  })
49
70
  );
@@ -60,7 +81,7 @@ router.post(
60
81
  router.get(
61
82
  "/",
62
83
  asyncHandler(async (req: Request, res: Response) => {
63
- res.json(products);
84
+ res.json(await productController.getAll());
64
85
  })
65
86
  );
66
87
 
@@ -68,7 +89,7 @@ router.get(
68
89
  * @swagger
69
90
  * /products/{id}:
70
91
  * get:
71
- * summary: Get an product by ID
92
+ * summary: Get a product by ID
72
93
  * parameters:
73
94
  * - name: id
74
95
  * in: path
@@ -78,15 +99,14 @@ router.get(
78
99
  * type: integer
79
100
  * responses:
80
101
  * 200:
81
- * description: An product object
82
- * 404:
83
- * description: product not found
102
+ * description: A product object
84
103
  */
85
104
  router.get(
86
105
  "/:id",
87
106
  asyncHandler(async (req: Request, res: Response) => {
88
- const product = products.find((i) => i.id === parseInt(req.params.id));
89
- if (!product) res.status(404).send("product not found");
107
+ const product = await productController.get(req.params.id);
108
+
109
+ if ("id" in product == false) sendError(res, product);
90
110
  else res.json(product);
91
111
  })
92
112
  );
@@ -95,7 +115,12 @@ router.get(
95
115
  * @swagger
96
116
  * /products/{id}:
97
117
  * put:
98
- * summary: Update an product
118
+ * summary: Update a product
119
+ * description: Update an existing product.
120
+ * consumes:
121
+ * - application/json
122
+ * produces:
123
+ * - application/json
99
124
  * parameters:
100
125
  * - name: id
101
126
  * in: path
@@ -103,32 +128,49 @@ router.get(
103
128
  * description: The ID of the product to update
104
129
  * schema:
105
130
  * type: integer
106
- * requestBody:
107
- * required: true
108
- * content:
109
- * application/json:
110
- * schema:
111
- * type: object
112
- * properties:
113
- * name:
114
- * type: string
115
- * description:
116
- * type: string
131
+ * - in: body
132
+ * name: product
133
+ * required: true
134
+ * schema:
135
+ * type: object
136
+ * properties:
137
+ * name:
138
+ * type: string
139
+ * example: "Updated Product"
140
+ * price:
141
+ * type: number
142
+ * example: 2000
143
+ * description:
144
+ * type: string
145
+ * example: "This is a detailed description."
146
+ * category:
147
+ * type: string
148
+ * enum:
149
+ * - Electronics
150
+ * - Appliances
151
+ * - Sports
152
+ * - Kitchen
153
+ * - Mobile Accessories
154
+ * - Computer Accessories
155
+ * - Home Appliances
156
+ * - Books
157
+ * example: "Sports"
158
+ * stock:
159
+ * type: integer
160
+ * example: 70
117
161
  * responses:
118
162
  * 200:
119
- * description: product updated
163
+ * description: Product updated
120
164
  * 404:
121
- * description: product not found
165
+ * description: Product not found
122
166
  */
123
167
  router.put(
124
168
  "/:id",
125
169
  asyncHandler(async (req: Request, res: Response) => {
126
- const product = products.find((i) => i.id === parseInt(req.params.id));
127
- if (!product) res.status(404).send("product not found");
128
- else {
129
- Object.assign(product, { id: req.body.id, name: req.body.name });
130
- res.json(product);
131
- }
170
+ const product = await productController.update(req.params.id, req.body);
171
+
172
+ if ("id" in product == false) sendError(res, product);
173
+ else res.json(product);
132
174
  })
133
175
  );
134
176
 
@@ -136,7 +178,7 @@ router.put(
136
178
  * @swagger
137
179
  * /products/{id}:
138
180
  * delete:
139
- * summary: Delete an product
181
+ * summary: Delete a product
140
182
  * parameters:
141
183
  * - name: id
142
184
  * in: path
@@ -147,20 +189,18 @@ router.put(
147
189
  * responses:
148
190
  * 204:
149
191
  * description: product deleted
192
+ * 400:
193
+ * description: invalid request
150
194
  * 404:
151
195
  * description: product not found
152
196
  */
153
197
  router.delete(
154
198
  "/:id",
155
199
  asyncHandler(async (req: Request, res: Response) => {
156
- const productIndex = products.findIndex(
157
- (i) => i.id === parseInt(req.params.id)
158
- );
159
- if (productIndex === -1) res.status(404).send("product not found");
160
- else {
161
- products.splice(productIndex, 1);
162
- res.status(204).send();
163
- }
200
+ const product = await productController.delete(req.params.id);
201
+
202
+ if ("id" in product == false) sendError(res, product);
203
+ else res.json(product);
164
204
  })
165
205
  );
166
206