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.
@@ -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
 
@@ -0,0 +1,237 @@
1
+ import { onError, rateLimit, timeout } from "utils-decorators";
2
+ import {
3
+ Product,
4
+ ProductCreationDto,
5
+ ProductUpdateDto,
6
+ ZodProductCreationDto,
7
+ ZodProductUpdateDto,
8
+ } 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
+ ];
89
+
90
+ function exceedHandler() {
91
+ const message = "Too much call in allowed window";
92
+
93
+ throw new Error(message, {
94
+ cause: { status: 500, message } satisfies ResponseError,
95
+ });
96
+ }
97
+
98
+ function getProductErrorHandler(e: Error) {
99
+ const message = "User not found.";
100
+
101
+ throw new Error(message, {
102
+ cause: { status: 404, message, details: e.message } satisfies ResponseError,
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Controller for handling product-related operations
108
+ * @class ProductController
109
+ * @desc Provides methods for product management including CRUD operations
110
+ */
111
+ export default class ProductController {
112
+ @rateLimit({
113
+ timeSpanMs: 60000,
114
+ allowedCalls: 300,
115
+ exceedHandler,
116
+ })
117
+ @Validate
118
+ /**
119
+ * 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
122
+ */
123
+ public async create(
124
+ @ZodInput(ZodProductCreationDto) product: ProductCreationDto
125
+ ) {
126
+ products.push({
127
+ ...product,
128
+ id: products.length + 1,
129
+ } satisfies Product);
130
+
131
+ return product;
132
+ }
133
+
134
+ @timeout(20000)
135
+ @rateLimit({
136
+ timeSpanMs: 60000,
137
+ allowedCalls: 300,
138
+ exceedHandler,
139
+ })
140
+ /**
141
+ * Retrieves all products with truncated descriptions
142
+ * @returns List of products with summarized descriptions
143
+ */
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
+ );
152
+ }
153
+
154
+ @onError({
155
+ func: getProductErrorHandler,
156
+ })
157
+ @rateLimit({
158
+ timeSpanMs: 60000,
159
+ allowedCalls: 300,
160
+ exceedHandler,
161
+ })
162
+ /**
163
+ * Finds a product by its ID
164
+ * @param id - Product ID as string
165
+ * @returns Product details or error object if not found
166
+ */
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;
179
+ }
180
+
181
+ @rateLimit({
182
+ timeSpanMs: 60000,
183
+ allowedCalls: 300,
184
+ exceedHandler,
185
+ })
186
+ @Validate
187
+ /**
188
+ * Updates an existing product
189
+ * @param {string} id - Product ID to update
190
+ * @param {ProductUpdateDto} updateData - Partial product data to update
191
+ * @returns {Promise<Product | ResponseError>} Updated product or error object
192
+ * @throws {ResponseError} 404 - Product not found
193
+ * @throws {ResponseError} 400 - Invalid ID format or update data
194
+ */
195
+ public async update(
196
+ id: string,
197
+ @ZodInput(ZodProductUpdateDto) updateData: ProductUpdateDto
198
+ ): Promise<Product | ResponseError> {
199
+ 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;
208
+
209
+ return product;
210
+ }
211
+
212
+ @rateLimit({
213
+ timeSpanMs: 60000,
214
+ allowedCalls: 300,
215
+ exceedHandler,
216
+ })
217
+ /**
218
+ * Deletes a product by ID
219
+ * @param {string} id - Product ID to delete
220
+ * @returns {Promise<Product | ResponseError>} Deleted product or error object
221
+ * @throws {ResponseError} 404 - Product not found
222
+ * @throws {ResponseError} 400 - Invalid ID format
223
+ */
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;
236
+ }
237
+ }
@@ -1,9 +1,21 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const CreateUserDto = z.object({
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(),
4
13
  username: z.string().min(3),
5
14
  email: z.string().email(),
6
15
  password: z.string().min(6),
7
16
  });
8
17
 
9
- export type CreateUser = z.infer<typeof CreateUserDto>;
18
+ export const ZodUserCreationDto = ZodUser.omit({ id: true });
19
+
20
+ export type User = z.infer<typeof ZodUser>;
21
+ export type UserCreationDto = z.infer<typeof ZodUserCreationDto>;
@@ -1,6 +1,7 @@
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";
4
5
 
5
6
  const router = Router();
6
7
  const userController = new UserController();
@@ -11,70 +12,87 @@ const userController = new UserController();
11
12
  * post:
12
13
  * summary: Create a user
13
14
  * description: Create a new user.
14
- * requestBody:
15
- * required: true
16
- * content:
17
- * application/json:
18
- * schema:
19
- * type: object
20
- * required:
21
- * - username
22
- * - email
23
- * - password
24
- * properties:
25
- * username:
26
- * type: string
27
- * email:
28
- * type: string
29
- * password:
30
- * type: string
31
- * example: # Sample object
32
- * username: JessicaSmith
33
- * email: william.howard.taft@my-own-personal-domain.com
34
- * password: password123
15
+ * consumes:
16
+ * - application/json
17
+ * produces:
18
+ * - application/json
19
+ * parameters:
20
+ * - in: body
21
+ * name: user
22
+ * required: true
23
+ * schema:
24
+ * type: object
25
+ * required:
26
+ * - username
27
+ * - email
28
+ * - password
29
+ * properties:
30
+ * username:
31
+ * type: string
32
+ * minLength: 3
33
+ * example: JessicaSmith
34
+ * email:
35
+ * type: string
36
+ * format: email
37
+ * example: user@example.com
38
+ * password:
39
+ * type: string
40
+ * minLength: 6
41
+ * example: securePassword123
35
42
  * responses:
36
43
  * 201:
37
- * description: product created
44
+ * description: User created
38
45
  */
39
46
  router.post(
40
47
  "/",
41
48
  asyncHandler(async (req: Request, res: Response) => {
42
- const result = await userController.createUser(req.body);
43
- res.status(201).json({ message: result });
49
+ const user = await userController.create(req.body);
50
+ res.status(201).json(user);
44
51
  })
45
52
  );
46
53
 
47
54
  /**
48
55
  * @swagger
49
- * /user:
56
+ * /users/{id}:
50
57
  * get:
51
- * summary: Get user home
52
- * description: Returns the user home page.
58
+ * summary: Get a user by ID
59
+ * parameters:
60
+ * - name: id
61
+ * in: path
62
+ * required: true
63
+ * description: The ID of the product
64
+ * schema:
65
+ * type: integer
53
66
  * responses:
54
67
  * 200:
55
- * description: User home page
68
+ * description: A user object
69
+ * 404:
70
+ * description: user not found
56
71
  */
57
72
  router.get(
58
- "/",
73
+ "/:id",
59
74
  asyncHandler(async (req: Request, res: Response) => {
60
- res.send("User Home");
75
+ const user = await userController.get(req.params.id);
76
+
77
+ if ("id" in user == false) sendError(res, user);
78
+ else res.json(user);
61
79
  })
62
80
  );
63
81
 
64
82
  /**
65
83
  * @swagger
66
- * /user/profile:
84
+ * /users:
67
85
  * get:
68
- * summary: Get user profile
69
- * description: Returns user profile information.
86
+ * summary: Get users
87
+ * description: Returns a list of user objects.
70
88
  * responses:
71
89
  * 200:
72
- * description: User profile information
90
+ * description: A list of user objects
73
91
  */
74
92
  router.get(
75
- "/profile",
93
+ "/",
76
94
  asyncHandler(async (req: Request, res: Response) => {
77
- res.send("User Profile");
95
+ res.json(await userController.getAll());
78
96
  })
79
97
  );
80
98