codeweaver 2.0.0 → 2.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,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,8 @@ 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
+ require("./app");
5
7
 
6
8
  /**
7
9
  * Recursively loads Express routers from directory
@@ -53,14 +55,12 @@ const app = express();
53
55
  app.use(express.json());
54
56
  app.use(express.urlencoded({ extended: true }));
55
57
 
56
- //app.use(cors());
57
-
58
58
  // Automatically import all routers from the /src/routers directory
59
59
  const routersPath = path.join(__dirname, "/routers");
60
60
  loadRouters(routersPath);
61
61
 
62
62
  // Swagger setup
63
- if (config.devMode) {
63
+ if (config.swagger) {
64
64
  const swaggerJsDoc = require("swagger-jsdoc");
65
65
  const swaggerUi = require("swagger-ui-express");
66
66
  const swaggerDocs = swaggerJsDoc(config.swaggerOptions);
@@ -68,15 +68,18 @@ if (config.devMode) {
68
68
  }
69
69
 
70
70
  // General error handler
71
- app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
72
- res.status(500).json(err);
73
- });
71
+ app.use(
72
+ (err: ResponseError, req: Request, res: Response, next: NextFunction) => {
73
+ res.status(err.status ?? 500).json(err);
74
+ }
75
+ );
74
76
 
75
77
  // Start the server
76
78
  app.listen(config.port, () => {
77
79
  console.log(`Server is running on http://localhost:${config.port}`);
78
- if (config.devMode)
80
+ if (config.devMode) {
79
81
  console.log(
80
82
  `Swagger UI is available at http://localhost:${config.port}/api-docs`
81
83
  );
84
+ }
82
85
  });
@@ -1,33 +1,10 @@
1
- import { z } from "zod";
1
+ import { ZodOrder } from "@/entities/order.entity";
2
2
 
3
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
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
- export type Order = z.infer<typeof ZodOrder>;
40
- export type OrderCreationDto = z.infer<typeof ZodOrderCreationDto>;
41
- export type OrderDto = z.infer<typeof ZodOrder>;
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
+ };
@@ -10,7 +10,7 @@ const orderController = new OrderController();
10
10
  * /orders:
11
11
  * post:
12
12
  * summary: Create a new order
13
- * description: Creates an order with user details and products.
13
+ * description: Creates an order with order details and products.
14
14
  * consumes:
15
15
  * - application/json
16
16
  * produces:
@@ -58,8 +58,9 @@ const orderController = new OrderController();
58
58
  router.post(
59
59
  "/",
60
60
  asyncHandler(async (req: Request, res: Response) => {
61
- const order = await orderController.create(req.body);
62
- res.status(201).json(order);
61
+ const order = await orderController.validateOrderCreationDto(req.body);
62
+ await orderController.create(order);
63
+ res.status(201).send();;
63
64
  })
64
65
  );
65
66
 
@@ -99,7 +100,8 @@ router.get(
99
100
  router.get(
100
101
  "/:id",
101
102
  asyncHandler(async (req: Request, res: Response) => {
102
- const order = await orderController.get(req.params.id);
103
+ const id = await orderController.validateId(req.params.id);
104
+ const order = await orderController.get(id);
103
105
  res.json(order);
104
106
  })
105
107
  );
@@ -123,7 +125,8 @@ router.get(
123
125
  router.patch(
124
126
  "/:id/cancel",
125
127
  asyncHandler(async (req: Request, res: Response) => {
126
- const order = await orderController.cancel(req.params.id);
128
+ const id = await orderController.validateId(req.params.id);
129
+ const order = await orderController.cancel(id);
127
130
  res.json(order);
128
131
  })
129
132
  );
@@ -149,7 +152,8 @@ router.patch(
149
152
  router.patch(
150
153
  "/:id/deliver",
151
154
  asyncHandler(async (req: Request, res: Response) => {
152
- const order = await orderController.deliver(req.params.id);
155
+ const id = await orderController.validateId(req.params.id);
156
+ const order = await orderController.deliver(id);
153
157
  res.json(order);
154
158
  })
155
159
  );
@@ -1,94 +1,99 @@
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 { Validate, ZodInput } from "ts-zod4-decorators";
9
- import { ResponseError } from "@/utilities/types";
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
- // Array to store orders (as a mock database)
14
- const orders: Order[] = [
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";
51
13
 
52
14
  function exceedHandler() {
53
15
  const message = "Too much call in allowed window";
54
16
  throw new ResponseError(message, 429);
55
17
  }
56
18
 
57
- function getOrderErrorHandler(e: Error) {
19
+ function orderNotFoundHandler(e: ResponseError) {
58
20
  const message = "Order not found.";
59
21
  throw new ResponseError(message, 404, e.message);
60
22
  }
61
23
 
24
+ function invalidInputHandler(e: ResponseError) {
25
+ const message = "Invalid input";
26
+ throw new ResponseError(message, 400, e.message);
27
+ }
28
+
29
+ const ordersCache = new MapAsyncCache<OrderDto[]>(config.cacheSize);
30
+ const orderCache = new MapAsyncCache<OrderDto>(config.cacheSize);
31
+
62
32
  /**
63
33
  * Controller for handling order-related operations
64
34
  * @class OrderController
65
35
  * @desc Provides methods for order management including creation, status updates and retrieval
66
36
  */
67
37
  export default class OrderController {
38
+ // constructor(private readonly orderService: OrderService) { }
39
+
40
+ @onError({
41
+ func: orderNotFoundHandler,
42
+ })
43
+ /**
44
+ * Validates a string ID and converts it to a number.
45
+ *
46
+ * @param {string} id - The ID to validate and convert.
47
+ * @returns {number} The numeric value of the provided ID.
48
+ */
49
+ public async validateId(id: string): Promise<number> {
50
+ return stringToInteger(id);
51
+ }
52
+
53
+ @onError({
54
+ func: invalidInputHandler,
55
+ })
56
+ /**
57
+ * Validates and creates a new Order from the given DTO.
58
+ *
59
+ * @param {OrderCreationDto} order - The incoming OrderCreationDto to validate and transform.
60
+ * @returns {Order} A fully formed Order object ready for persistence.
61
+ */
62
+ public async validateOrderCreationDto(
63
+ order: OrderCreationDto
64
+ ): Promise<Order> {
65
+ const newOrder = await ZodOrderCreationDto.parseAsync(order);
66
+ return {
67
+ ...newOrder,
68
+ id: orders.length + 1,
69
+ status: "Processing",
70
+ createdAt: new Date(),
71
+ };
72
+ }
73
+
68
74
  @rateLimit({
69
75
  timeSpanMs: config.rateLimitTimeSpan,
70
76
  allowedCalls: config.rateLimitAllowedCalls,
71
77
  exceedHandler,
72
78
  })
73
- @Validate
74
79
  /**
75
80
  * Create a new order
76
- * @param {OrderCreationDto} order - Order creation data
77
- * @returns {Promise<Order>} Newly created order
81
+ * @param {Order} order - Order creation data
82
+ * @returns {Promise<void>}
83
+ * @throws {ResponseError} 500 - When rate limit exceeded
84
+ * @throws {ResponseError} 400 - Invalid input data
78
85
  */
79
- public async create(@ZodInput(ZodOrderCreationDto) order: OrderCreationDto) {
80
- const newOrder = {
81
- ...order,
82
- id: orders.length + 1,
83
- createdAt: new Date(),
84
- status: "Processing",
85
- } satisfies Order;
86
-
87
- orders.push(newOrder);
88
- return newOrder;
86
+ public async create(order: Order): Promise<void> {
87
+ orders.push(order);
88
+ await orderCache.set(order.id.toString(), order as OrderDto);
89
+ await ordersCache.delete("key");
89
90
  }
90
91
 
91
- @memoizeAsync(config.memoizeTime)
92
+ @memoizeAsync({
93
+ cache: ordersCache,
94
+ keyResolver: () => "key",
95
+ expirationTimeMs: config.memoizeTime,
96
+ })
92
97
  @timeout(config.timeout)
93
98
  @rateLimit({
94
99
  timeSpanMs: config.rateLimitTimeSpan,
@@ -100,28 +105,30 @@ export default class OrderController {
100
105
  * @returns List of orders
101
106
  */
102
107
  public async getAll(): Promise<OrderDto[]> {
103
- return orders;
108
+ return orders as OrderDto[];
104
109
  }
105
110
 
106
- @memoizeAsync(config.memoizeTime)
111
+ @memoizeAsync({
112
+ cache: orderCache,
113
+ keyResolver: (id: number) => id.toString(),
114
+ expirationTimeMs: config.memoizeTime,
115
+ })
107
116
  @rateLimit({
108
117
  timeSpanMs: config.rateLimitTimeSpan,
109
118
  allowedCalls: config.rateLimitAllowedCalls,
110
119
  exceedHandler,
111
120
  })
112
- @onError({
113
- func: getOrderErrorHandler,
114
- })
115
121
  /**
116
122
  * Finds an order by its ID
117
- * @param id - Order ID as string
123
+ * @param {number} id - Order ID as string
118
124
  * @returns Order details or error object if not found
119
125
  */
120
- public async get(id: string): Promise<OrderDto> {
121
- const orderId = parseId(id);
122
- const order = orders.find((order) => order.id === orderId);
123
- if (order == null) throw new ResponseError("User dose not exist.", 404);
124
- return order satisfies Order;
126
+ public async get(id: number): Promise<OrderDto> {
127
+ const order = orders.find((order) => order.id === id);
128
+ if (order == null) {
129
+ throw new ResponseError("Order not found");
130
+ }
131
+ return convert(order!, ZodOrder);
125
132
  }
126
133
 
127
134
  @rateLimit({
@@ -131,12 +138,12 @@ export default class OrderController {
131
138
  })
132
139
  /**
133
140
  * Cancel an existing order
134
- * @param {string} id - Order ID to cancel
141
+ * @param {number} id - Order ID to cancel
135
142
  * @returns {Promise<Order>} Updated order or error object
136
143
  * @throws {ResponseError} 404 - Order not found
137
144
  * @throws {ResponseError} 400 - Invalid ID format or invalid status for cancellation
138
145
  */
139
- public async cancel(id: string): Promise<OrderDto> {
146
+ public async cancel(id: number): Promise<OrderDto> {
140
147
  let order = await this.get(id);
141
148
  if (order.status != "Processing") {
142
149
  throw new ResponseError(
@@ -144,10 +151,12 @@ export default class OrderController {
144
151
  400
145
152
  );
146
153
  }
147
-
148
154
  order.status = "Canceled";
149
155
  order.deliveredAt = new Date();
150
- return order satisfies Order;
156
+
157
+ await orderCache.set(id.toString(), order);
158
+ await ordersCache.delete("key");
159
+ return order;
151
160
  }
152
161
 
153
162
  @rateLimit({
@@ -157,12 +166,12 @@ export default class OrderController {
157
166
  })
158
167
  /**
159
168
  * Mark an order as delivered
160
- * @param {string} id - Order ID to mark as delivered
169
+ * @param {number} id - Order ID to mark as delivered
161
170
  * @returns {Promise<Order>} Updated order or error object
162
171
  * @throws {ResponseError} 404 - Order not found
163
172
  * @throws {ResponseError} 400 - Invalid ID format or invalid status for delivery
164
173
  */
165
- public async deliver(id: string): Promise<OrderDto> {
174
+ public async deliver(id: number): Promise<OrderDto> {
166
175
  let order = await this.get(id);
167
176
  if (order.status != "Processing") {
168
177
  throw new ResponseError(
@@ -170,9 +179,11 @@ export default class OrderController {
170
179
  400
171
180
  );
172
181
  }
173
-
174
182
  order.status = "Delivered";
175
183
  order.deliveredAt = new Date();
176
- return order satisfies Order;
184
+
185
+ await orderCache.set(id.toString(), order);
186
+ await ordersCache.delete("key");
187
+ return order;
177
188
  }
178
189
  }