codeweaver 1.0.14 → 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.
- package/README.md +252 -40
- package/command.js +11 -10
- package/package.json +17 -13
- package/src/app.ts +21 -3
- package/src/config.ts +33 -0
- package/src/packages/ts-zod-decorators/index.ts +3 -0
- package/src/packages/ts-zod-decorators/validate.decorator.ts +20 -0
- package/src/packages/ts-zod-decorators/validator.class.ts +72 -0
- package/src/packages/ts-zod-decorators/zod-input.decorator.ts +12 -0
- package/src/packages/ts-zod-decorators/zod-output.decorator.ts +11 -0
- package/src/routers/index.ts +2 -2
- package/src/routers/orders/dto/order.dto.ts +40 -0
- package/src/routers/orders/index.ts +131 -10
- package/src/routers/orders/order.controller.ts +191 -0
- package/src/routers/products/dto/product.dto.ts +36 -0
- package/src/routers/products/index.ts +104 -64
- package/src/routers/products/product.controller.ts +237 -0
- package/src/routers/users/dto/user.dto.ts +14 -2
- package/src/routers/users/index.ts +54 -36
- package/src/routers/users/user.controller.ts +144 -16
- package/src/types.ts +16 -0
- package/src/utilities.ts +47 -0
- package/tsconfig.json +9 -3
- package/tsconfig.paths.json +10 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ZodObject } from "zod";
|
|
2
|
+
import ZodValidator from "./validator.class";
|
|
3
|
+
|
|
4
|
+
export const ZodOutput =
|
|
5
|
+
<T extends ZodObject<any, any>>(schema: T): MethodDecorator =>
|
|
6
|
+
(target, propertyKey) =>
|
|
7
|
+
ZodValidator.registerMethodValidationOutputSchema(
|
|
8
|
+
target,
|
|
9
|
+
propertyKey as string,
|
|
10
|
+
schema
|
|
11
|
+
);
|
package/src/routers/index.ts
CHANGED
|
@@ -11,12 +11,12 @@ const router = Router();
|
|
|
11
11
|
* description: Returns the home page.
|
|
12
12
|
* responses:
|
|
13
13
|
* 200:
|
|
14
|
-
* description:
|
|
14
|
+
* description: Hi there!
|
|
15
15
|
*/
|
|
16
16
|
router.get(
|
|
17
17
|
"/",
|
|
18
18
|
asyncHandler(async (req: Request, res: Response) => {
|
|
19
|
-
res.send("
|
|
19
|
+
res.send("Hi there!");
|
|
20
20
|
})
|
|
21
21
|
);
|
|
22
22
|
|
|
@@ -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
|
|
11
|
-
* description: Returns the order home page.
|
|
71
|
+
* summary: Get all orders
|
|
12
72
|
* responses:
|
|
13
73
|
* 200:
|
|
14
|
-
* description:
|
|
74
|
+
* description: List of orders
|
|
15
75
|
*/
|
|
16
76
|
router.get(
|
|
17
77
|
"/",
|
|
18
78
|
asyncHandler(async (req: Request, res: Response) => {
|
|
19
|
-
|
|
79
|
+
const orders = await orderController.getAll();
|
|
80
|
+
res.json(orders);
|
|
20
81
|
})
|
|
21
82
|
);
|
|
22
83
|
|
|
23
84
|
/**
|
|
24
85
|
* @swagger
|
|
25
|
-
* /orders/
|
|
86
|
+
* /orders/{id}:
|
|
26
87
|
* get:
|
|
27
|
-
* summary: Get order
|
|
28
|
-
*
|
|
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
|
|
98
|
+
* description: Order details
|
|
32
99
|
*/
|
|
33
100
|
router.get(
|
|
34
|
-
"
|
|
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
|
-
|
|
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>;
|