codeweaver 1.1.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.
Files changed (37) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/README.md +114 -181
  3. package/package.json +12 -7
  4. package/src/app.ts +2 -112
  5. package/src/config.ts +33 -64
  6. package/src/constants.ts +7 -0
  7. package/src/db.ts +183 -0
  8. package/src/entities/order.entity.ts +68 -0
  9. package/src/entities/product.entity.ts +75 -0
  10. package/src/entities/user.entity.ts +38 -0
  11. package/src/main.ts +85 -0
  12. package/src/routers/orders/dto/order.dto.ts +54 -29
  13. package/src/routers/orders/{index.ts → index.router.ts} +13 -13
  14. package/src/routers/orders/order.controller.ts +118 -120
  15. package/src/routers/products/dto/product.dto.ts +86 -30
  16. package/src/routers/products/{index.ts → index.router.ts} +14 -15
  17. package/src/routers/products/product.controller.ts +136 -161
  18. package/src/routers/users/dto/user.dto.ts +14 -18
  19. package/src/routers/users/{index.ts → index.router.ts} +6 -7
  20. package/src/routers/users/user.controller.ts +87 -118
  21. package/src/swagger-options.ts +39 -0
  22. package/src/utilities/assign.ts +66 -0
  23. package/src/utilities/cache/memory-cache.ts +74 -0
  24. package/src/utilities/cache/redis-cache.ts +111 -0
  25. package/src/utilities/conversion.ts +158 -0
  26. package/src/utilities/error-handling.ts +156 -0
  27. package/src/utilities/router.ts +0 -0
  28. package/tsconfig.json +1 -4
  29. package/tsconfig.paths.json +8 -10
  30. package/src/packages/ts-zod-decorators/index.ts +0 -3
  31. package/src/packages/ts-zod-decorators/validate.decorator.ts +0 -20
  32. package/src/packages/ts-zod-decorators/validator.class.ts +0 -72
  33. package/src/packages/ts-zod-decorators/zod-input.decorator.ts +0 -12
  34. package/src/packages/ts-zod-decorators/zod-output.decorator.ts +0 -11
  35. package/src/types.ts +0 -16
  36. package/src/utilities.ts +0 -47
  37. /package/src/routers/{index.ts → index.router.ts} +0 -0
@@ -1,237 +1,212 @@
1
- import { onError, rateLimit, timeout } from "utils-decorators";
2
1
  import {
3
- Product,
2
+ memoizeAsync,
3
+ onError,
4
+ rateLimit,
5
+ timeout,
6
+ before,
7
+ } from "utils-decorators";
8
+ import {
4
9
  ProductCreationDto,
10
+ ProductDto,
5
11
  ProductUpdateDto,
6
12
  ZodProductCreationDto,
7
13
  ZodProductUpdateDto,
8
14
  } from "./dto/product.dto";
9
- import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
10
- import { ResponseError } from "@/types";
11
- import { tryParseId } from "@/utilities";
12
-
13
- // Array to store products (as a mock database)
14
- const products: Product[] = [
15
- {
16
- id: 1,
17
- name: "ASUS ROG Zephyrus G15",
18
- price: 45000000,
19
- description: "Gaming laptop with AMD Ryzen 9 5900HS and RTX 3080 GPU",
20
- category: "Electronics",
21
- stock: 15,
22
- },
23
- {
24
- id: 2,
25
- name: "Sony WH-1000XM5 Wireless Headphones",
26
- price: 12000000,
27
- description:
28
- "Premium noise-canceling over-ear headphones with 30hr battery",
29
- category: "Electronics",
30
- stock: 8,
31
- },
32
- {
33
- id: 3,
34
- name: "LG Smart Inverter Microwave",
35
- price: 25000000,
36
- description: "1.7 cu.ft countertop microwave with smart sensor cooking",
37
- category: "Appliances",
38
- stock: 5,
39
- },
40
- {
41
- id: 4,
42
- name: "Trek Marlin 5 Mountain Bike",
43
- price: 18000000,
44
- description: "Entry-level mountain bike with aluminum frame and 21 speeds",
45
- category: "Sports",
46
- stock: 3,
47
- },
48
- {
49
- id: 5,
50
- name: "DeLonghi Espresso Machine",
51
- price: 6500000,
52
- description: "Compact espresso maker with manual milk frother",
53
- category: "Kitchen",
54
- stock: 12,
55
- },
56
- {
57
- id: 6,
58
- name: "Anker Wireless Charger",
59
- price: 1200000,
60
- description: "15W fast wireless charger with anti-slip surface",
61
- category: "Mobile Accessories",
62
- stock: 30,
63
- },
64
- {
65
- id: 7,
66
- name: "Logitech MX Master 3 Mouse",
67
- price: 4500000,
68
- description: "Ergonomic wireless mouse with Darkfield tracking",
69
- category: "Computer Accessories",
70
- stock: 18,
71
- },
72
- {
73
- id: 8,
74
- name: "Kindle Paperwhite",
75
- price: 3800000,
76
- description: 'Waterproof e-reader with 6.8" 300ppi display',
77
- category: "Electronics",
78
- stock: 9,
79
- },
80
- {
81
- id: 9,
82
- name: "Dyson V11 Vacuum Cleaner",
83
- price: 32000000,
84
- description: "Cordless stick vacuum with LCD screen and 60min runtime",
85
- category: "Home Appliances",
86
- stock: 7,
87
- },
88
- ];
15
+ import { MapAsyncCache } from "@/utilities/cache/memory-cache";
16
+ import { convert, stringToInteger } from "@/utilities/conversion";
17
+ import config from "@/config";
18
+ import { ResponseError } from "@/utilities/error-handling";
19
+ import { products } from "@/db";
20
+ import { Product, ZodProduct } from "@/entities/product.entity";
89
21
 
90
22
  function exceedHandler() {
91
23
  const message = "Too much call in allowed window";
92
-
93
- throw new Error(message, {
94
- cause: { status: 500, message } satisfies ResponseError,
95
- });
24
+ throw new ResponseError(message, 429);
96
25
  }
97
26
 
98
- function getProductErrorHandler(e: Error) {
99
- const message = "User not found.";
27
+ function productNotFoundHandler(e: ResponseError) {
28
+ const message = "Product not found.";
29
+ throw new ResponseError(message, 404, e.message);
30
+ }
100
31
 
101
- throw new Error(message, {
102
- cause: { status: 404, message, details: e.message } satisfies ResponseError,
103
- });
32
+ function invalidInputHandler(e: ResponseError) {
33
+ const message = "Invalid input";
34
+ throw new ResponseError(message, 400, e.message);
104
35
  }
105
36
 
37
+ const productsCache = new MapAsyncCache<ProductDto[]>(config.cacheSize);
38
+ const productCache = new MapAsyncCache<ProductDto>(config.cacheSize);
39
+
106
40
  /**
107
41
  * Controller for handling product-related operations
108
42
  * @class ProductController
109
43
  * @desc Provides methods for product management including CRUD operations
110
44
  */
111
45
  export default class ProductController {
46
+ // constructor(private readonly productService: ProductService) { }
47
+
48
+ @onError({
49
+ func: productNotFoundHandler,
50
+ })
51
+ /**
52
+ * Validates a string ID and converts it to a number.
53
+ *
54
+ * @param {string} id - The ID to validate and convert.
55
+ * @returns {number} The numeric value of the provided ID.
56
+ */
57
+ public async validateId(id: string): Promise<number> {
58
+ return stringToInteger(id);
59
+ }
60
+
61
+ @onError({
62
+ func: invalidInputHandler,
63
+ })
64
+ /**
65
+ * Validates and creates a new Product from the given DTO.
66
+ *
67
+ * @param {ProductCreationDto} product - The incoming ProductCreationDto to validate and transform.
68
+ * @returns {Product} A fully formed Product object ready for persistence.
69
+ */
70
+ public async validateProductCreationDto(
71
+ product: ProductCreationDto
72
+ ): Promise<Product> {
73
+ const newProduct = await ZodProductCreationDto.parseAsync(product);
74
+ return { ...newProduct, id: products.length + 1 };
75
+ }
76
+
77
+ @onError({
78
+ func: invalidInputHandler,
79
+ })
80
+ /**
81
+ * Validates and creates a new Product from the given DTO.
82
+ *
83
+ * @param {ProductUpdateDto} product - The incoming ProductCreationDto to validate and transform.
84
+ * @returns {Product} A fully formed Product object ready for persistence.
85
+ */
86
+ public async validateProductUpdateDto(
87
+ product: ProductCreationDto
88
+ ): Promise<Product> {
89
+ const productDto = await ZodProductUpdateDto.parseAsync(product);
90
+ let updatedProduct: Product = convert(productDto, ZodProduct);
91
+ return { ...updatedProduct, id: products.length + 1 };
92
+ }
93
+
112
94
  @rateLimit({
113
- timeSpanMs: 60000,
114
- allowedCalls: 300,
95
+ timeSpanMs: config.rateLimitTimeSpan,
96
+ allowedCalls: config.rateLimitAllowedCalls,
115
97
  exceedHandler,
116
98
  })
117
- @Validate
118
99
  /**
119
100
  * Creates a new product with validated data
120
- * @param product - Product creation data validated by Zod schema
121
- * @returns Newly created product with generated ID
101
+ * @param {Product} product - Product creation data validated by Zod schema
102
+ * @returns {Promise<void>}
103
+ * @throws {ResponseError} 500 - When rate limit exceeded
104
+ * @throws {ResponseError} 400 - Invalid input data
122
105
  */
123
- public async create(
124
- @ZodInput(ZodProductCreationDto) product: ProductCreationDto
125
- ) {
126
- products.push({
106
+ public async create(product: Product): Promise<void> {
107
+ const newProduct: Product = {
127
108
  ...product,
128
109
  id: products.length + 1,
129
- } satisfies Product);
110
+ };
130
111
 
131
- return product;
112
+ products.push(newProduct);
113
+ await productCache.set(newProduct.id.toString(), newProduct as ProductDto);
114
+ await productsCache.delete("key");
132
115
  }
133
116
 
134
- @timeout(20000)
117
+ @memoizeAsync({
118
+ cache: productsCache,
119
+ keyResolver: () => "key",
120
+ expirationTimeMs: config.memoizeTime,
121
+ })
122
+ @timeout(config.timeout)
135
123
  @rateLimit({
136
- timeSpanMs: 60000,
137
- allowedCalls: 300,
124
+ timeSpanMs: config.rateLimitTimeSpan,
125
+ allowedCalls: config.rateLimitAllowedCalls,
138
126
  exceedHandler,
139
127
  })
140
128
  /**
141
129
  * Retrieves all products with truncated descriptions
142
130
  * @returns List of products with summarized descriptions
143
131
  */
144
- public async getAll(): Promise<Product[]> {
145
- return products.map(
146
- (product) =>
147
- ({
148
- ...product,
149
- description: product.description?.substring(0, 50) + "..." || "",
150
- } satisfies Product)
151
- );
132
+ public async getAll(): Promise<ProductDto[]> {
133
+ return products as ProductDto[];
152
134
  }
153
135
 
136
+ @memoizeAsync({
137
+ cache: productCache,
138
+ keyResolver: (id: number) => id.toString(),
139
+ expirationTimeMs: config.memoizeTime,
140
+ })
154
141
  @onError({
155
- func: getProductErrorHandler,
142
+ func: productNotFoundHandler,
156
143
  })
157
144
  @rateLimit({
158
- timeSpanMs: 60000,
159
- allowedCalls: 300,
145
+ timeSpanMs: config.rateLimitTimeSpan,
146
+ allowedCalls: config.rateLimitAllowedCalls,
160
147
  exceedHandler,
161
148
  })
162
149
  /**
163
150
  * Finds a product by its ID
164
- * @param id - Product ID as string
151
+ * @param {number} id - Product ID as string
165
152
  * @returns Product details or error object if not found
166
153
  */
167
- public async get(id: string): Promise<Product | ResponseError> {
168
- const productId = tryParseId(id);
169
- if (typeof productId != "number") return productId satisfies ResponseError;
170
- const product = products.find((product) => product.id === productId);
171
-
172
- if (!product)
173
- return {
174
- status: 404,
175
- message: "Product dose not exist.",
176
- } satisfies ResponseError;
177
-
178
- return product satisfies Product;
154
+ public async get(id: number): Promise<ProductDto> {
155
+ const product = products.find((product) => product.id === id);
156
+ if (product == null) {
157
+ throw new ResponseError("Product not found");
158
+ }
159
+ return convert(product!, ZodProduct);
179
160
  }
180
161
 
181
162
  @rateLimit({
182
- timeSpanMs: 60000,
183
- allowedCalls: 300,
163
+ timeSpanMs: config.rateLimitTimeSpan,
164
+ allowedCalls: config.rateLimitAllowedCalls,
184
165
  exceedHandler,
185
166
  })
186
- @Validate
187
167
  /**
188
168
  * Updates an existing product
189
- * @param {string} id - Product ID to update
169
+ * @param {number} id - Product ID to update
190
170
  * @param {ProductUpdateDto} updateData - Partial product data to update
191
- * @returns {Promise<Product | ResponseError>} Updated product or error object
171
+ * @returns {Promise<Product>} Updated product or error object
192
172
  * @throws {ResponseError} 404 - Product not found
193
173
  * @throws {ResponseError} 400 - Invalid ID format or update data
194
174
  */
195
175
  public async update(
196
- id: string,
197
- @ZodInput(ZodProductUpdateDto) updateData: ProductUpdateDto
198
- ): Promise<Product | ResponseError> {
176
+ id: number,
177
+ updateData: ProductUpdateDto
178
+ ): Promise<ProductDto> {
199
179
  const product = await this.get(id);
200
- if ("id" in product == false) return product satisfies ResponseError;
201
-
202
- if (product) Object.assign(product, updateData);
203
- else
204
- return {
205
- status: 404,
206
- message: "Product not found",
207
- } satisfies ResponseError;
180
+ if (product != null) {
181
+ Object.assign(product, updateData);
182
+ await productCache.set(id.toString(), product);
183
+ await productsCache.delete("key");
184
+ } else {
185
+ throw new ResponseError("Product dose not exist.", 404);
186
+ }
208
187
 
209
188
  return product;
210
189
  }
211
190
 
212
191
  @rateLimit({
213
- timeSpanMs: 60000,
214
- allowedCalls: 300,
192
+ timeSpanMs: config.rateLimitTimeSpan,
193
+ allowedCalls: config.rateLimitAllowedCalls,
215
194
  exceedHandler,
216
195
  })
217
196
  /**
218
197
  * Deletes a product by ID
219
- * @param {string} id - Product ID to delete
220
- * @returns {Promise<Product | ResponseError>} Deleted product or error object
198
+ * @param {number} id - Product ID to delete
199
+ * @returns {Promise<Product>} Deleted product or error object
221
200
  * @throws {ResponseError} 404 - Product not found
222
201
  * @throws {ResponseError} 400 - Invalid ID format
223
202
  */
224
- public async delete(id: string): Promise<Product | ResponseError> {
225
- const productId = tryParseId(id);
226
- if (typeof productId != "number") return productId satisfies ResponseError;
227
- const index = products.findIndex((product) => product.id === productId);
228
-
229
- if (index == -1)
230
- return {
231
- status: 404,
232
- message: "Product dose not exist.",
233
- } satisfies ResponseError;
234
-
235
- return products.splice(index, 1)[0] satisfies Product;
203
+ public async delete(id: number): Promise<ProductDto> {
204
+ const index = products.findIndex((product) => product.id === id);
205
+ if (index == -1) {
206
+ throw new ResponseError("Product dose not exist.", 404);
207
+ }
208
+ await productCache.delete(id.toString());
209
+ await productsCache.delete("key");
210
+ return products.splice(index, 1)[0];
236
211
  }
237
212
  }
@@ -1,21 +1,17 @@
1
- import { z } from "zod";
2
-
3
- /**
4
- * Zod schema for User entity
5
- * @typedef {Object} ZodUser
6
- * @property {number} id - Unique identifier (min 1)
7
- * @property {string} username - Username (min 3 chars)
8
- * @property {string} email - Valid email format
9
- * @property {string} password - Password (min 6 chars)
10
- */
11
- export const ZodUser = z.object({
12
- id: z.number().min(1).int(),
13
- username: z.string().min(3),
14
- email: z.string().email(),
15
- password: z.string().min(6),
16
- });
1
+ import { ZodUser } from "@/entities/user.entity";
2
+ import z from "zod";
17
3
 
18
4
  export const ZodUserCreationDto = ZodUser.omit({ id: true });
5
+ export const ZodUserDto = ZodUser.omit({ password: true });
6
+
7
+ export type UserCreationDto = {
8
+ username: string;
9
+ email: string;
10
+ password: string;
11
+ };
19
12
 
20
- export type User = z.infer<typeof ZodUser>;
21
- export type UserCreationDto = z.infer<typeof ZodUserCreationDto>;
13
+ export type UserDto = {
14
+ id: number;
15
+ username: string;
16
+ email: string;
17
+ };
@@ -1,7 +1,6 @@
1
1
  import { Router, Request, Response } from "express";
2
2
  import asyncHandler from "express-async-handler";
3
3
  import UserController from "./user.controller";
4
- import { sendError } from "@/utilities";
5
4
 
6
5
  const router = Router();
7
6
  const userController = new UserController();
@@ -46,8 +45,9 @@ const userController = new UserController();
46
45
  router.post(
47
46
  "/",
48
47
  asyncHandler(async (req: Request, res: Response) => {
49
- const user = await userController.create(req.body);
50
- res.status(201).json(user);
48
+ const user = await userController.validateUserCreationDto(req.body);
49
+ await userController.create(user);
50
+ res.status(201).send();
51
51
  })
52
52
  );
53
53
 
@@ -72,10 +72,9 @@ router.post(
72
72
  router.get(
73
73
  "/:id",
74
74
  asyncHandler(async (req: Request, res: Response) => {
75
- const user = await userController.get(req.params.id);
76
-
77
- if ("id" in user == false) sendError(res, user);
78
- else res.json(user);
75
+ const id = await userController.validateId(req.params.id);
76
+ const user = await userController.get(id);
77
+ res.json(user);
79
78
  })
80
79
  );
81
80