codeweaver 2.0.0 → 2.2.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 +84 -144
- package/package.json +11 -3
- package/src/config.ts +11 -50
- package/src/constants.ts +1 -0
- package/src/db.ts +183 -0
- package/src/entities/order.entity.ts +68 -0
- package/src/entities/product.entity.ts +75 -0
- package/src/entities/user.entity.ts +38 -0
- package/src/main.ts +19 -7
- package/src/routers/orders/dto/order.dto.ts +54 -30
- package/src/routers/orders/index.router.ts +12 -7
- package/src/routers/orders/order.controller.ts +87 -79
- package/src/routers/products/dto/product.dto.ts +86 -31
- package/src/routers/products/index.router.ts +13 -6
- package/src/routers/products/product.controller.ts +109 -120
- package/src/routers/users/dto/user.dto.ts +13 -19
- package/src/routers/users/index.router.ts +7 -4
- package/src/routers/users/user.controller.ts +72 -98
- package/src/swagger-options.ts +7 -22
- package/src/utilities/assign.ts +44 -59
- package/src/utilities/cache/memory-cache.ts +74 -0
- package/src/utilities/cache/redis-cache.ts +111 -0
- package/src/utilities/container.ts +159 -0
- package/src/utilities/conversion.ts +158 -0
- package/src/utilities/error-handling.ts +98 -26
- package/src/utilities/logger/base-logger.interface.ts +11 -0
- package/src/utilities/logger/logger.config.ts +95 -0
- package/src/utilities/logger/logger.service.ts +122 -0
- package/src/utilities/logger/winston-logger.service.ts +16 -0
- package/src/utilities/types.ts +0 -23
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for the Order entity.
|
|
5
|
+
* This schema validates orders to ensure data integrity before processing.
|
|
6
|
+
*/
|
|
7
|
+
export const ZodOrder = z.object({
|
|
8
|
+
/** Unique identifier for the order (positive integer). */
|
|
9
|
+
id: z.number().min(1).int(),
|
|
10
|
+
|
|
11
|
+
/** ID of the user who placed the order (positive integer). */
|
|
12
|
+
userId: z.number().min(1).int(),
|
|
13
|
+
|
|
14
|
+
/** Array of ordered products with their quantities. */
|
|
15
|
+
products: z.array(
|
|
16
|
+
z.object({
|
|
17
|
+
/** Product identifier referenced in the catalog */
|
|
18
|
+
productId: z.number().min(1).int(),
|
|
19
|
+
|
|
20
|
+
/** Quantity of the product ordered (at least 1) */
|
|
21
|
+
quantity: z.number().min(1).int(),
|
|
22
|
+
})
|
|
23
|
+
),
|
|
24
|
+
|
|
25
|
+
/** Current status of the order */
|
|
26
|
+
status: z.enum(["Processing", "Delivered", "Canceled"]),
|
|
27
|
+
|
|
28
|
+
/** Total price of the order (minimum value constraint is 1000) */
|
|
29
|
+
total: z.number().min(1000),
|
|
30
|
+
|
|
31
|
+
/** Timestamp when the order was created */
|
|
32
|
+
createdAt: z.date(),
|
|
33
|
+
|
|
34
|
+
/** Optional timestamp when the order was canceled */
|
|
35
|
+
canceledAt: z.date().optional(),
|
|
36
|
+
|
|
37
|
+
/** Optional timestamp when the order was delivered */
|
|
38
|
+
deliveredAt: z.date().optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export type Order = {
|
|
42
|
+
/** Unique identifier for the order */
|
|
43
|
+
id: number;
|
|
44
|
+
|
|
45
|
+
/** User ID who placed the order */
|
|
46
|
+
userId: number;
|
|
47
|
+
|
|
48
|
+
/** List of products in the order with quantities */
|
|
49
|
+
products: {
|
|
50
|
+
productId: number;
|
|
51
|
+
quantity: number;
|
|
52
|
+
}[];
|
|
53
|
+
|
|
54
|
+
/** Order status: Processing, Delivered, or Canceled */
|
|
55
|
+
status: "Processing" | "Delivered" | "Canceled";
|
|
56
|
+
|
|
57
|
+
/** Total price of the order */
|
|
58
|
+
total: number;
|
|
59
|
+
|
|
60
|
+
/** Order creation timestamp */
|
|
61
|
+
createdAt: Date;
|
|
62
|
+
|
|
63
|
+
/** Optional cancellation timestamp (if canceled) */
|
|
64
|
+
canceledAt?: Date | undefined;
|
|
65
|
+
|
|
66
|
+
/** Optional delivery timestamp (if delivered) */
|
|
67
|
+
deliveredAt?: Date | undefined;
|
|
68
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for the Product entity.
|
|
5
|
+
* This schema validates product data to ensure consistency
|
|
6
|
+
* before it is stored or processed.
|
|
7
|
+
*/
|
|
8
|
+
export const ZodProduct = z.object({
|
|
9
|
+
/** Unique identifier for the product. Must be a positive integer. */
|
|
10
|
+
id: z.number().min(1).int(),
|
|
11
|
+
|
|
12
|
+
/** Product name. Minimum length of 2 characters. */
|
|
13
|
+
name: z.string().min(2),
|
|
14
|
+
|
|
15
|
+
/** Product price. Minimum value of 1000 (assuming your currency unit). */
|
|
16
|
+
price: z.number().min(1000),
|
|
17
|
+
|
|
18
|
+
/** Optional product description. Minimum length of 10 characters if provided. */
|
|
19
|
+
description: z.string().min(10).optional(),
|
|
20
|
+
|
|
21
|
+
/** Product category from a predefined list. */
|
|
22
|
+
category: z.enum([
|
|
23
|
+
"Electronics",
|
|
24
|
+
"Appliances",
|
|
25
|
+
"Sports",
|
|
26
|
+
"Kitchen",
|
|
27
|
+
"Mobile Accessories",
|
|
28
|
+
"Computer Accessories",
|
|
29
|
+
"Home Appliances",
|
|
30
|
+
"Books",
|
|
31
|
+
]),
|
|
32
|
+
|
|
33
|
+
/** Stock count in inventory. Non-negative integer. */
|
|
34
|
+
stock: z.number().min(0).int(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* TypeScript type for a Product entity.
|
|
39
|
+
* Mirrors the Zod schema with an optional description.
|
|
40
|
+
*/
|
|
41
|
+
export type Product = {
|
|
42
|
+
/** Unique identifier for the product. */
|
|
43
|
+
id: number;
|
|
44
|
+
|
|
45
|
+
/** Display name of the product. */
|
|
46
|
+
name: string;
|
|
47
|
+
|
|
48
|
+
/** Price of the product in the chosen currency. */
|
|
49
|
+
price: number;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Category of the product.
|
|
53
|
+
* Must be one of the predefined categories:
|
|
54
|
+
* - Electronics, Appliances, Sports, Kitchen, Mobile Accessories,
|
|
55
|
+
* Computer Accessories, Home Appliances, Books
|
|
56
|
+
*/
|
|
57
|
+
category:
|
|
58
|
+
| "Electronics"
|
|
59
|
+
| "Appliances"
|
|
60
|
+
| "Sports"
|
|
61
|
+
| "Kitchen"
|
|
62
|
+
| "Mobile Accessories"
|
|
63
|
+
| "Computer Accessories"
|
|
64
|
+
| "Home Appliances"
|
|
65
|
+
| "Books";
|
|
66
|
+
|
|
67
|
+
/** Current stock level in inventory. Non-negative. */
|
|
68
|
+
stock: number;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Optional product description.
|
|
72
|
+
* Includes more details about the product when provided.
|
|
73
|
+
*/
|
|
74
|
+
description?: string | undefined;
|
|
75
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for the User entity.
|
|
5
|
+
* This schema validates user data to ensure it meets basic integrity requirements
|
|
6
|
+
* before being stored or processed.
|
|
7
|
+
*/
|
|
8
|
+
export const ZodUser = z.object({
|
|
9
|
+
/** Unique identifier for the user. Must be a positive integer. */
|
|
10
|
+
id: z.number().min(1).int(),
|
|
11
|
+
|
|
12
|
+
/** Username chosen by the user. Minimum length of 3 characters. */
|
|
13
|
+
username: z.string().min(3),
|
|
14
|
+
|
|
15
|
+
/** Email address of the user. Must be a valid email format. */
|
|
16
|
+
email: z.email(),
|
|
17
|
+
|
|
18
|
+
/** Password for the user. Minimum length of 6 characters. */
|
|
19
|
+
password: z.string().min(6),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* TypeScript type for a User entity.
|
|
24
|
+
* Mirrors the Zod schema.
|
|
25
|
+
*/
|
|
26
|
+
export type User = {
|
|
27
|
+
/** Unique identifier for the user. */
|
|
28
|
+
id: number;
|
|
29
|
+
|
|
30
|
+
/** Username chosen by the user. */
|
|
31
|
+
username: string;
|
|
32
|
+
|
|
33
|
+
/** Email address of the user. */
|
|
34
|
+
email: string;
|
|
35
|
+
|
|
36
|
+
/** Password for the user. */
|
|
37
|
+
password: string;
|
|
38
|
+
};
|
package/src/main.ts
CHANGED
|
@@ -2,6 +2,9 @@ import express, { NextFunction, Request, Response } from "express";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import config from "./config";
|
|
5
|
+
import { ResponseError } from "./utilities/error-handling";
|
|
6
|
+
import { resolve } from "./utilities/container";
|
|
7
|
+
import { WinstonLoggerService } from "./utilities/logger/winston-logger.service";
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Recursively loads Express routers from directory
|
|
@@ -53,30 +56,39 @@ const app = express();
|
|
|
53
56
|
app.use(express.json());
|
|
54
57
|
app.use(express.urlencoded({ extended: true }));
|
|
55
58
|
|
|
56
|
-
//app.use(cors());
|
|
57
|
-
|
|
58
59
|
// Automatically import all routers from the /src/routers directory
|
|
59
60
|
const routersPath = path.join(__dirname, "/routers");
|
|
60
61
|
loadRouters(routersPath);
|
|
61
62
|
|
|
62
63
|
// Swagger setup
|
|
63
|
-
if (config.
|
|
64
|
+
if (config.swagger) {
|
|
64
65
|
const swaggerJsDoc = require("swagger-jsdoc");
|
|
65
66
|
const swaggerUi = require("swagger-ui-express");
|
|
66
67
|
const swaggerDocs = swaggerJsDoc(config.swaggerOptions);
|
|
67
68
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs));
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
const logger = resolve(WinstonLoggerService);
|
|
72
|
+
|
|
70
73
|
// General error handler
|
|
71
|
-
app.use(
|
|
72
|
-
res
|
|
73
|
-
|
|
74
|
+
app.use(
|
|
75
|
+
(error: ResponseError, req: Request, res: Response, next: NextFunction) => {
|
|
76
|
+
const status = error.status ?? 500;
|
|
77
|
+
res.status(status).json(error);
|
|
78
|
+
if (status < 500) {
|
|
79
|
+
logger.warn(error.message, "Invalid request", error.details);
|
|
80
|
+
} else {
|
|
81
|
+
logger.error(error.message, "Server error", error.details);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
74
85
|
|
|
75
86
|
// Start the server
|
|
76
87
|
app.listen(config.port, () => {
|
|
77
88
|
console.log(`Server is running on http://localhost:${config.port}`);
|
|
78
|
-
if (config.devMode)
|
|
89
|
+
if (config.devMode) {
|
|
79
90
|
console.log(
|
|
80
91
|
`Swagger UI is available at http://localhost:${config.port}/api-docs`
|
|
81
92
|
);
|
|
93
|
+
}
|
|
82
94
|
});
|
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ZodOrder } from "@/entities/order.entity";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
4
|
+
* DTO for creating an order.
|
|
5
|
+
* This is derived from the full Order schema by omitting fields
|
|
6
|
+
* that are typically generated by the system (id, status, timestamps).
|
|
14
7
|
*/
|
|
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
8
|
export const ZodOrderCreationDto = ZodOrder.omit({
|
|
32
9
|
id: true,
|
|
33
10
|
status: true,
|
|
@@ -36,6 +13,53 @@ export const ZodOrderCreationDto = ZodOrder.omit({
|
|
|
36
13
|
deliveredAt: true,
|
|
37
14
|
});
|
|
38
15
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Data Transfer Object for creating an order.
|
|
18
|
+
* Represents the minimal data required to create an order.
|
|
19
|
+
*/
|
|
20
|
+
export type OrderCreationDto = {
|
|
21
|
+
/** ID of the user placing the order. */
|
|
22
|
+
userId: number;
|
|
23
|
+
|
|
24
|
+
/** List of products included in the order with their quantities. */
|
|
25
|
+
products: {
|
|
26
|
+
productId: number;
|
|
27
|
+
quantity: number;
|
|
28
|
+
}[];
|
|
29
|
+
|
|
30
|
+
/** Total price for the order (validated by the server/persistence layer). */
|
|
31
|
+
total: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Data Transfer Object for a full Order view.
|
|
36
|
+
* Represents the complete order data as returned by the API/service.
|
|
37
|
+
*/
|
|
38
|
+
export type OrderDto = {
|
|
39
|
+
/** Unique identifier for the order. */
|
|
40
|
+
id: number;
|
|
41
|
+
|
|
42
|
+
/** User ID who placed the order. */
|
|
43
|
+
userId: number;
|
|
44
|
+
|
|
45
|
+
/** List of products in the order with their quantities. */
|
|
46
|
+
products: {
|
|
47
|
+
productId: number;
|
|
48
|
+
quantity: number;
|
|
49
|
+
}[];
|
|
50
|
+
|
|
51
|
+
/** Current status of the order: Processing, Delivered, or Canceled. */
|
|
52
|
+
status: "Processing" | "Delivered" | "Canceled";
|
|
53
|
+
|
|
54
|
+
/** Total price for the order. */
|
|
55
|
+
total: number;
|
|
56
|
+
|
|
57
|
+
/** Timestamp when the order was created. */
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
|
|
60
|
+
/** Optional timestamp when the order was canceled (if canceled). */
|
|
61
|
+
canceledAt?: Date | undefined;
|
|
62
|
+
|
|
63
|
+
/** Optional timestamp when the order was delivered (if delivered). */
|
|
64
|
+
deliveredAt?: Date | undefined;
|
|
65
|
+
};
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { Router, Request, Response } from "express";
|
|
2
2
|
import asyncHandler from "express-async-handler";
|
|
3
3
|
import OrderController from "./order.controller";
|
|
4
|
+
import { resolve } from "@/utilities/container";
|
|
4
5
|
|
|
5
6
|
const router = Router();
|
|
6
|
-
const orderController =
|
|
7
|
+
const orderController = resolve(OrderController);
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @swagger
|
|
10
11
|
* /orders:
|
|
11
12
|
* post:
|
|
12
13
|
* summary: Create a new order
|
|
13
|
-
* description: Creates an order with
|
|
14
|
+
* description: Creates an order with order details and products.
|
|
14
15
|
* consumes:
|
|
15
16
|
* - application/json
|
|
16
17
|
* produces:
|
|
@@ -58,8 +59,9 @@ const orderController = new OrderController();
|
|
|
58
59
|
router.post(
|
|
59
60
|
"/",
|
|
60
61
|
asyncHandler(async (req: Request, res: Response) => {
|
|
61
|
-
const order = await orderController.
|
|
62
|
-
|
|
62
|
+
const order = await orderController.validateOrderCreationDto(req.body);
|
|
63
|
+
await orderController.create(order);
|
|
64
|
+
res.status(201).send();
|
|
63
65
|
})
|
|
64
66
|
);
|
|
65
67
|
|
|
@@ -99,7 +101,8 @@ router.get(
|
|
|
99
101
|
router.get(
|
|
100
102
|
"/:id",
|
|
101
103
|
asyncHandler(async (req: Request, res: Response) => {
|
|
102
|
-
const
|
|
104
|
+
const id = await orderController.validateId(req.params.id);
|
|
105
|
+
const order = await orderController.get(id);
|
|
103
106
|
res.json(order);
|
|
104
107
|
})
|
|
105
108
|
);
|
|
@@ -123,7 +126,8 @@ router.get(
|
|
|
123
126
|
router.patch(
|
|
124
127
|
"/:id/cancel",
|
|
125
128
|
asyncHandler(async (req: Request, res: Response) => {
|
|
126
|
-
const
|
|
129
|
+
const id = await orderController.validateId(req.params.id);
|
|
130
|
+
const order = await orderController.cancel(id);
|
|
127
131
|
res.json(order);
|
|
128
132
|
})
|
|
129
133
|
);
|
|
@@ -149,7 +153,8 @@ router.patch(
|
|
|
149
153
|
router.patch(
|
|
150
154
|
"/:id/deliver",
|
|
151
155
|
asyncHandler(async (req: Request, res: Response) => {
|
|
152
|
-
const
|
|
156
|
+
const id = await orderController.validateId(req.params.id);
|
|
157
|
+
const order = await orderController.deliver(id);
|
|
153
158
|
res.json(order);
|
|
154
159
|
})
|
|
155
160
|
);
|
|
@@ -1,94 +1,96 @@
|
|
|
1
1
|
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
2
2
|
import {
|
|
3
|
-
Order,
|
|
4
3
|
OrderDto,
|
|
5
4
|
OrderCreationDto,
|
|
6
5
|
ZodOrderCreationDto,
|
|
7
6
|
} from "./dto/order.dto";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { parseId } from "@/utilities/error-handling";
|
|
7
|
+
import { ResponseError } from "@/utilities/error-handling";
|
|
8
|
+
import { convert, stringToInteger } from "@/utilities/conversion";
|
|
11
9
|
import config from "@/config";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
id: 1,
|
|
17
|
-
userId: 1,
|
|
18
|
-
products: [
|
|
19
|
-
{ productId: 2, quantity: 1 },
|
|
20
|
-
{ productId: 6, quantity: 2 },
|
|
21
|
-
],
|
|
22
|
-
status: "Delivered",
|
|
23
|
-
total: 14400,
|
|
24
|
-
createdAt: new Date("2024-01-15"),
|
|
25
|
-
deliveredAt: new Date("2024-02-10"),
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: 2,
|
|
29
|
-
userId: 3,
|
|
30
|
-
products: [
|
|
31
|
-
{ productId: 9, quantity: 1 },
|
|
32
|
-
{ productId: 7, quantity: 1 },
|
|
33
|
-
],
|
|
34
|
-
status: "Processing",
|
|
35
|
-
total: 36500,
|
|
36
|
-
createdAt: new Date("2024-03-20"),
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: 3,
|
|
40
|
-
userId: 2,
|
|
41
|
-
products: [
|
|
42
|
-
{ productId: 1, quantity: 1 },
|
|
43
|
-
{ productId: 4, quantity: 2 },
|
|
44
|
-
],
|
|
45
|
-
status: "Canceled",
|
|
46
|
-
total: 81000,
|
|
47
|
-
createdAt: new Date("2024-05-01"),
|
|
48
|
-
canceledAt: new Date("2024-05-03"),
|
|
49
|
-
},
|
|
50
|
-
];
|
|
10
|
+
import { orders } from "@/db";
|
|
11
|
+
import { Order, ZodOrder } from "@/entities/order.entity";
|
|
12
|
+
import { MapAsyncCache } from "@/utilities/cache/memory-cache";
|
|
13
|
+
import { Injectable } from "@/utilities/container";
|
|
51
14
|
|
|
52
15
|
function exceedHandler() {
|
|
53
16
|
const message = "Too much call in allowed window";
|
|
54
17
|
throw new ResponseError(message, 429);
|
|
55
18
|
}
|
|
56
19
|
|
|
57
|
-
function
|
|
58
|
-
const message = "
|
|
59
|
-
throw new ResponseError(message,
|
|
20
|
+
function invalidInputHandler(e: ResponseError) {
|
|
21
|
+
const message = "Invalid input";
|
|
22
|
+
throw new ResponseError(message, 400, e.message);
|
|
60
23
|
}
|
|
61
24
|
|
|
25
|
+
const ordersCache = new MapAsyncCache<OrderDto[]>(config.cacheSize);
|
|
26
|
+
const orderCache = new MapAsyncCache<OrderDto>(config.cacheSize);
|
|
27
|
+
|
|
28
|
+
@Injectable()
|
|
62
29
|
/**
|
|
63
30
|
* Controller for handling order-related operations
|
|
64
31
|
* @class OrderController
|
|
65
32
|
* @desc Provides methods for order management including creation, status updates and retrieval
|
|
66
33
|
*/
|
|
67
34
|
export default class OrderController {
|
|
35
|
+
// constructor(private readonly orderService: OrderService) { }
|
|
36
|
+
|
|
37
|
+
@onError({
|
|
38
|
+
func: invalidInputHandler,
|
|
39
|
+
})
|
|
40
|
+
/**
|
|
41
|
+
* Validates a string ID and converts it to a number.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} id - The ID to validate and convert.
|
|
44
|
+
* @returns {number} The numeric value of the provided ID.
|
|
45
|
+
*/
|
|
46
|
+
public async validateId(id: string): Promise<number> {
|
|
47
|
+
return stringToInteger(id);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@onError({
|
|
51
|
+
func: invalidInputHandler,
|
|
52
|
+
})
|
|
53
|
+
/**
|
|
54
|
+
* Validates and creates a new Order from the given DTO.
|
|
55
|
+
*
|
|
56
|
+
* @param {OrderCreationDto} order - The incoming OrderCreationDto to validate and transform.
|
|
57
|
+
* @returns {Order} A fully formed Order object ready for persistence.
|
|
58
|
+
*/
|
|
59
|
+
public async validateOrderCreationDto(
|
|
60
|
+
order: OrderCreationDto
|
|
61
|
+
): Promise<Order> {
|
|
62
|
+
const newOrder = await ZodOrderCreationDto.parseAsync(order);
|
|
63
|
+
return {
|
|
64
|
+
...newOrder,
|
|
65
|
+
id: orders.length + 1,
|
|
66
|
+
status: "Processing",
|
|
67
|
+
createdAt: new Date(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
68
71
|
@rateLimit({
|
|
69
72
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
70
73
|
allowedCalls: config.rateLimitAllowedCalls,
|
|
71
74
|
exceedHandler,
|
|
72
75
|
})
|
|
73
|
-
@Validate
|
|
74
76
|
/**
|
|
75
77
|
* Create a new order
|
|
76
|
-
* @param {
|
|
77
|
-
* @returns {Promise<
|
|
78
|
+
* @param {Order} order - Order creation data
|
|
79
|
+
* @returns {Promise<void>}
|
|
80
|
+
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
81
|
+
* @throws {ResponseError} 400 - Invalid input data
|
|
78
82
|
*/
|
|
79
|
-
public async create(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
createdAt: new Date(),
|
|
84
|
-
status: "Processing",
|
|
85
|
-
} satisfies Order;
|
|
86
|
-
|
|
87
|
-
orders.push(newOrder);
|
|
88
|
-
return newOrder;
|
|
83
|
+
public async create(order: Order): Promise<void> {
|
|
84
|
+
orders.push(order);
|
|
85
|
+
await orderCache.set(order.id.toString(), order as OrderDto);
|
|
86
|
+
await ordersCache.delete("key");
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
@memoizeAsync(
|
|
89
|
+
@memoizeAsync({
|
|
90
|
+
cache: ordersCache,
|
|
91
|
+
keyResolver: () => "key",
|
|
92
|
+
expirationTimeMs: config.memoizeTime,
|
|
93
|
+
})
|
|
92
94
|
@timeout(config.timeout)
|
|
93
95
|
@rateLimit({
|
|
94
96
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
@@ -100,28 +102,30 @@ export default class OrderController {
|
|
|
100
102
|
* @returns List of orders
|
|
101
103
|
*/
|
|
102
104
|
public async getAll(): Promise<OrderDto[]> {
|
|
103
|
-
return orders;
|
|
105
|
+
return orders as OrderDto[];
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
@memoizeAsync(
|
|
108
|
+
@memoizeAsync({
|
|
109
|
+
cache: orderCache,
|
|
110
|
+
keyResolver: (id: number) => id.toString(),
|
|
111
|
+
expirationTimeMs: config.memoizeTime,
|
|
112
|
+
})
|
|
107
113
|
@rateLimit({
|
|
108
114
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
109
115
|
allowedCalls: config.rateLimitAllowedCalls,
|
|
110
116
|
exceedHandler,
|
|
111
117
|
})
|
|
112
|
-
@onError({
|
|
113
|
-
func: getOrderErrorHandler,
|
|
114
|
-
})
|
|
115
118
|
/**
|
|
116
119
|
* Finds an order by its ID
|
|
117
|
-
* @param id - Order ID as string
|
|
120
|
+
* @param {number} id - Order ID as string
|
|
118
121
|
* @returns Order details or error object if not found
|
|
119
122
|
*/
|
|
120
|
-
public async get(id:
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
public async get(id: number): Promise<OrderDto> {
|
|
124
|
+
const order = orders.find((order) => order.id === id);
|
|
125
|
+
if (order == null) {
|
|
126
|
+
throw new ResponseError("Order not found");
|
|
127
|
+
}
|
|
128
|
+
return convert(order!, ZodOrder);
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
@rateLimit({
|
|
@@ -131,12 +135,12 @@ export default class OrderController {
|
|
|
131
135
|
})
|
|
132
136
|
/**
|
|
133
137
|
* Cancel an existing order
|
|
134
|
-
* @param {
|
|
138
|
+
* @param {number} id - Order ID to cancel
|
|
135
139
|
* @returns {Promise<Order>} Updated order or error object
|
|
136
140
|
* @throws {ResponseError} 404 - Order not found
|
|
137
141
|
* @throws {ResponseError} 400 - Invalid ID format or invalid status for cancellation
|
|
138
142
|
*/
|
|
139
|
-
public async cancel(id:
|
|
143
|
+
public async cancel(id: number): Promise<OrderDto> {
|
|
140
144
|
let order = await this.get(id);
|
|
141
145
|
if (order.status != "Processing") {
|
|
142
146
|
throw new ResponseError(
|
|
@@ -144,10 +148,12 @@ export default class OrderController {
|
|
|
144
148
|
400
|
|
145
149
|
);
|
|
146
150
|
}
|
|
147
|
-
|
|
148
151
|
order.status = "Canceled";
|
|
149
152
|
order.deliveredAt = new Date();
|
|
150
|
-
|
|
153
|
+
|
|
154
|
+
await orderCache.set(id.toString(), order);
|
|
155
|
+
await ordersCache.delete("key");
|
|
156
|
+
return order;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
@rateLimit({
|
|
@@ -157,12 +163,12 @@ export default class OrderController {
|
|
|
157
163
|
})
|
|
158
164
|
/**
|
|
159
165
|
* Mark an order as delivered
|
|
160
|
-
* @param {
|
|
166
|
+
* @param {number} id - Order ID to mark as delivered
|
|
161
167
|
* @returns {Promise<Order>} Updated order or error object
|
|
162
168
|
* @throws {ResponseError} 404 - Order not found
|
|
163
169
|
* @throws {ResponseError} 400 - Invalid ID format or invalid status for delivery
|
|
164
170
|
*/
|
|
165
|
-
public async deliver(id:
|
|
171
|
+
public async deliver(id: number): Promise<OrderDto> {
|
|
166
172
|
let order = await this.get(id);
|
|
167
173
|
if (order.status != "Processing") {
|
|
168
174
|
throw new ResponseError(
|
|
@@ -170,9 +176,11 @@ export default class OrderController {
|
|
|
170
176
|
400
|
|
171
177
|
);
|
|
172
178
|
}
|
|
173
|
-
|
|
174
179
|
order.status = "Delivered";
|
|
175
180
|
order.deliveredAt = new Date();
|
|
176
|
-
|
|
181
|
+
|
|
182
|
+
await orderCache.set(id.toString(), order);
|
|
183
|
+
await ordersCache.delete("key");
|
|
184
|
+
return order;
|
|
177
185
|
}
|
|
178
186
|
}
|