codeweaver 2.3.1 → 3.0.1
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 +13 -30
- package/command.js +51 -53
- package/package.json +9 -19
- package/src/routers/orders/dto/order.dto.ts +6 -0
- package/src/routers/orders/index.router.ts +4 -4
- package/src/routers/orders/order.controller.ts +15 -17
- package/src/routers/products/dto/product.dto.ts +17 -9
- package/src/routers/products/index.router.ts +4 -4
- package/src/routers/products/product.controller.ts +21 -34
- package/src/routers/users/user.controller.ts +9 -12
- package/src/utilities/assignment.ts +48 -0
- package/src/utilities/conversion.ts +38 -31
- package/src/utilities/error-handling.ts +45 -30
- package/src/utilities/parallel/chanel.ts +142 -0
- package/src/utilities/parallel/parallel.ts +135 -0
- package/src/utilities/parallel/worker-pool.ts +99 -0
- package/tsconfig.json +1 -1
- package/src/utilities/assign.ts +0 -66
package/README.md
CHANGED
|
@@ -31,51 +31,34 @@ To get started with the project, follow these steps:
|
|
|
31
31
|
cd my-app
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
2. **
|
|
34
|
+
2. **Clone the repository**:
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Using pnpm:
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
npm install
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
- Using pnpm:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
pnpm install
|
|
48
|
-
```
|
|
38
|
+
```bash
|
|
39
|
+
pnpm install
|
|
40
|
+
```
|
|
49
41
|
|
|
50
|
-
|
|
42
|
+
Using npm:
|
|
51
43
|
|
|
52
44
|
```bash
|
|
53
|
-
npm
|
|
45
|
+
npm install
|
|
54
46
|
```
|
|
55
47
|
|
|
56
|
-
|
|
48
|
+
3. **Run the application**:
|
|
57
49
|
|
|
58
50
|
```bash
|
|
59
|
-
|
|
51
|
+
npm start
|
|
60
52
|
```
|
|
61
53
|
|
|
62
54
|
4. **Visit the Swagger UI**: Open your browser and go to `http://localhost:3000/api-docs` to view the automatically generated API documentation.
|
|
63
55
|
|
|
64
56
|
5. **Build**: Compile the TypeScript files for the production environment:
|
|
65
57
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
npm run serve
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
- Using pnpm:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
pnpm run build
|
|
77
|
-
pnpm run serve
|
|
78
|
-
```
|
|
58
|
+
```bash
|
|
59
|
+
npm run build
|
|
60
|
+
npm run serve
|
|
61
|
+
```
|
|
79
62
|
|
|
80
63
|
## Sample Project Structure
|
|
81
64
|
|
package/command.js
CHANGED
|
@@ -1,37 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
|
-
const { exec } = require("child_process");
|
|
5
4
|
const path = require("path");
|
|
6
|
-
const {
|
|
7
|
-
const {
|
|
5
|
+
const { exec: _exec } = require("child_process");
|
|
6
|
+
const { promisify } = require("util");
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
const
|
|
8
|
+
const exec = promisify(_exec);
|
|
9
|
+
const readFile = promisify(fs.readFile);
|
|
10
|
+
const writeFile = promisify(fs.writeFile);
|
|
11
|
+
const access = promisify(fs.access);
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
(async () => {
|
|
14
|
+
try {
|
|
15
|
+
// Get the project name from command-line arguments or use a default name
|
|
16
|
+
const projectName = process.argv[2] || "my-app";
|
|
14
17
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
if (error) {
|
|
18
|
-
console.error(`Error cloning repository: ${error.message}`);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
18
|
+
// Set the URL of the GitHub repository
|
|
19
|
+
const repoUrl = "https://github.com/js-code-crafter/codeweaver.git";
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
// Clone the repository into the specified project name
|
|
22
|
+
await exec(`git clone ${repoUrl} ${projectName}`);
|
|
23
|
+
console.log(`Cloned repository into ${projectName}`);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
25
|
+
// Path to the cloned package.json file
|
|
26
|
+
const packageJsonPath = path.join(projectName, "package.json");
|
|
27
|
+
|
|
28
|
+
// Ensure package.json exists before trying to read
|
|
29
|
+
await access(packageJsonPath);
|
|
31
30
|
|
|
32
|
-
//
|
|
33
|
-
const
|
|
31
|
+
// Read and parse package.json
|
|
32
|
+
const data = await readFile(packageJsonPath, "utf8");
|
|
33
|
+
const parsed = JSON.parse(data);
|
|
34
34
|
|
|
35
|
+
// Remove the bin field if it exists (as in your original logic)
|
|
36
|
+
const { bin, ...packageJson } = parsed;
|
|
37
|
+
|
|
38
|
+
// Update fields as requested
|
|
35
39
|
Object.assign(packageJson, {
|
|
36
40
|
name: projectName,
|
|
37
41
|
version: "1.0.0",
|
|
@@ -42,35 +46,29 @@ exec(`git clone ${repoUrl} ${projectName}`, (error) => {
|
|
|
42
46
|
});
|
|
43
47
|
|
|
44
48
|
// Write the updated package.json back to the file
|
|
45
|
-
|
|
46
|
-
packageJsonPath,
|
|
47
|
-
JSON.stringify(packageJson, null, 2),
|
|
48
|
-
(writeError) => {
|
|
49
|
-
if (writeError) {
|
|
50
|
-
console.error(`Error writing package.json: ${writeError.message}`);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Remove the command.js file
|
|
55
|
-
exec(`rm -f ${path.join(projectName, "command.js")}`, (rmError) => {
|
|
56
|
-
if (rmError) {
|
|
57
|
-
console.error(`Error removing command.js file: ${rmError.message}`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
49
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
60
50
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
});
|
|
51
|
+
// Remove the command.js file if it exists
|
|
52
|
+
const commandJsPath = path.join(projectName, "command.js");
|
|
53
|
+
try {
|
|
54
|
+
await exec(`rm -f ${commandJsPath}`);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
// If removal fails because the file doesn't exist, ignore
|
|
57
|
+
// Otherwise, log the error
|
|
58
|
+
if (err && err.code !== 1) {
|
|
59
|
+
console.error(`Error removing command.js file: ${err.message}`);
|
|
60
|
+
// Continue anyway
|
|
73
61
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Remove the .git folder
|
|
65
|
+
try {
|
|
66
|
+
await exec(`rm -rf ${path.join(projectName, ".git")}`);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error(`Error removing .git folder: ${err.message}`);
|
|
69
|
+
// continue anyway
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`Error: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeweaver",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"main": "src/main.ts",
|
|
5
|
-
"bin": {
|
|
6
|
-
"create-codeweaver-app": "./command.js"
|
|
7
|
-
},
|
|
8
5
|
"scripts": {
|
|
9
6
|
"start": "nodemon -r tsconfig-paths/register src/main.ts",
|
|
10
7
|
"dev": "nodemon -r tsconfig-paths/register src/main.ts",
|
|
@@ -12,26 +9,19 @@
|
|
|
12
9
|
"serve": "cross-env TSCONFIG_PATH=tsconfig.paths.json node -r tsconfig-paths/register dist/main.js"
|
|
13
10
|
},
|
|
14
11
|
"keywords": [
|
|
15
|
-
"codeweaver",
|
|
16
|
-
"node framework",
|
|
17
|
-
"js framework",
|
|
18
|
-
"javascript",
|
|
19
|
-
"typescript",
|
|
20
|
-
"code",
|
|
21
|
-
"weaver",
|
|
22
12
|
"framework",
|
|
23
|
-
"boilerplate",
|
|
24
|
-
"bootstrap",
|
|
25
13
|
"lightweight",
|
|
26
|
-
"
|
|
14
|
+
"node",
|
|
15
|
+
"Express",
|
|
16
|
+
"boilerplate",
|
|
17
|
+
"utility",
|
|
27
18
|
"decorator",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"utils-decorators"
|
|
19
|
+
"controller",
|
|
20
|
+
"microframework"
|
|
31
21
|
],
|
|
32
|
-
"author": "js-code-crafter",
|
|
22
|
+
"author": "@js-code-crafter - Ehsan Afzali",
|
|
33
23
|
"license": "MIT",
|
|
34
|
-
"description": "
|
|
24
|
+
"description": "An unopinionated microframework built with Express, TypeScript, Zod, Swagger",
|
|
35
25
|
"dependencies": {
|
|
36
26
|
"cors": "^2.8.5",
|
|
37
27
|
"dotenv": "^17.2.3",
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { ZodOrder } from "@/entities/order.entity";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* DTO for an order.
|
|
5
|
+
* This is derived from the full Order schema.
|
|
6
|
+
*/
|
|
7
|
+
export const ZodOrderDto = ZodOrder;
|
|
8
|
+
|
|
3
9
|
/**
|
|
4
10
|
* DTO for creating an order.
|
|
5
11
|
* This is derived from the full Order schema by omitting fields
|
|
@@ -127,8 +127,8 @@ router.patch(
|
|
|
127
127
|
"/:id/cancel",
|
|
128
128
|
asyncHandler(async (req: Request, res: Response) => {
|
|
129
129
|
const id = await orderController.validateId(req.params.id);
|
|
130
|
-
|
|
131
|
-
res.
|
|
130
|
+
await orderController.cancel(id);
|
|
131
|
+
res.status(200).send();
|
|
132
132
|
})
|
|
133
133
|
);
|
|
134
134
|
|
|
@@ -154,8 +154,8 @@ router.patch(
|
|
|
154
154
|
"/:id/deliver",
|
|
155
155
|
asyncHandler(async (req: Request, res: Response) => {
|
|
156
156
|
const id = await orderController.validateId(req.params.id);
|
|
157
|
-
|
|
158
|
-
res.
|
|
157
|
+
await orderController.deliver(id);
|
|
158
|
+
res.status(200).send();
|
|
159
159
|
})
|
|
160
160
|
);
|
|
161
161
|
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
2
|
-
import {
|
|
3
|
-
OrderDto,
|
|
4
|
-
OrderCreationDto,
|
|
5
|
-
ZodOrderCreationDto,
|
|
6
|
-
} from "./dto/order.dto";
|
|
2
|
+
import { OrderDto, OrderCreationDto, ZodOrderDto } from "./dto/order.dto";
|
|
7
3
|
import { ResponseError } from "@/utilities/error-handling";
|
|
8
4
|
import { convert, stringToInteger } from "@/utilities/conversion";
|
|
9
5
|
import config from "@/config";
|
|
@@ -11,6 +7,7 @@ import { orders } from "@/db";
|
|
|
11
7
|
import { Order, ZodOrder } from "@/entities/order.entity";
|
|
12
8
|
import { MapAsyncCache } from "@/utilities/cache/memory-cache";
|
|
13
9
|
import { Injectable } from "@/utilities/container";
|
|
10
|
+
import { parallelMap } from "@/utilities/parallel/parallel";
|
|
14
11
|
|
|
15
12
|
function exceedHandler() {
|
|
16
13
|
const message = "Too much call in allowed window";
|
|
@@ -59,13 +56,14 @@ export default class OrderController {
|
|
|
59
56
|
public async validateOrderCreationDto(
|
|
60
57
|
order: OrderCreationDto
|
|
61
58
|
): Promise<Order> {
|
|
62
|
-
const newOrder =
|
|
63
|
-
|
|
64
|
-
...newOrder,
|
|
59
|
+
const newOrder: Order = {
|
|
60
|
+
...order,
|
|
65
61
|
id: orders.length + 1,
|
|
66
62
|
status: "Processing",
|
|
67
63
|
createdAt: new Date(),
|
|
68
64
|
};
|
|
65
|
+
|
|
66
|
+
return convert(newOrder, ZodOrder);
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
@rateLimit({
|
|
@@ -82,7 +80,6 @@ export default class OrderController {
|
|
|
82
80
|
*/
|
|
83
81
|
public async create(order: Order): Promise<void> {
|
|
84
82
|
orders.push(order);
|
|
85
|
-
await orderCache.set(order.id.toString(), order as OrderDto);
|
|
86
83
|
await ordersCache.delete("key");
|
|
87
84
|
}
|
|
88
85
|
|
|
@@ -102,7 +99,10 @@ export default class OrderController {
|
|
|
102
99
|
* @returns List of orders
|
|
103
100
|
*/
|
|
104
101
|
public async getAll(): Promise<OrderDto[]> {
|
|
105
|
-
return
|
|
102
|
+
return await parallelMap(
|
|
103
|
+
orders,
|
|
104
|
+
async (order) => await convert(order, ZodOrderDto)
|
|
105
|
+
);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
@memoizeAsync({
|
|
@@ -125,7 +125,7 @@ export default class OrderController {
|
|
|
125
125
|
if (order == null) {
|
|
126
126
|
throw new ResponseError("Order not found");
|
|
127
127
|
}
|
|
128
|
-
return convert(order
|
|
128
|
+
return await convert(order, ZodOrder);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
@rateLimit({
|
|
@@ -140,7 +140,7 @@ export default class OrderController {
|
|
|
140
140
|
* @throws {ResponseError} 404 - Order not found
|
|
141
141
|
* @throws {ResponseError} 400 - Invalid ID format or invalid status for cancellation
|
|
142
142
|
*/
|
|
143
|
-
public async cancel(id: number): Promise<
|
|
143
|
+
public async cancel(id: number): Promise<void> {
|
|
144
144
|
let order = await this.get(id);
|
|
145
145
|
if (order.status != "Processing") {
|
|
146
146
|
throw new ResponseError(
|
|
@@ -151,9 +151,8 @@ export default class OrderController {
|
|
|
151
151
|
order.status = "Canceled";
|
|
152
152
|
order.deliveredAt = new Date();
|
|
153
153
|
|
|
154
|
-
await orderCache.
|
|
154
|
+
await orderCache.delete(id.toString());
|
|
155
155
|
await ordersCache.delete("key");
|
|
156
|
-
return order;
|
|
157
156
|
}
|
|
158
157
|
|
|
159
158
|
@rateLimit({
|
|
@@ -168,7 +167,7 @@ export default class OrderController {
|
|
|
168
167
|
* @throws {ResponseError} 404 - Order not found
|
|
169
168
|
* @throws {ResponseError} 400 - Invalid ID format or invalid status for delivery
|
|
170
169
|
*/
|
|
171
|
-
public async deliver(id: number): Promise<
|
|
170
|
+
public async deliver(id: number): Promise<void> {
|
|
172
171
|
let order = await this.get(id);
|
|
173
172
|
if (order.status != "Processing") {
|
|
174
173
|
throw new ResponseError(
|
|
@@ -179,8 +178,7 @@ export default class OrderController {
|
|
|
179
178
|
order.status = "Delivered";
|
|
180
179
|
order.deliveredAt = new Date();
|
|
181
180
|
|
|
182
|
-
await orderCache.
|
|
181
|
+
await orderCache.delete(id.toString());
|
|
183
182
|
await ordersCache.delete("key");
|
|
184
|
-
return order;
|
|
185
183
|
}
|
|
186
184
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { ZodProduct } from "@/entities/product.entity";
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DTO for a Product.
|
|
5
|
+
* Derived from the full Product schema.
|
|
6
|
+
*/
|
|
7
|
+
export const ZodProductDto = ZodProduct;
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* DTO for creating a Product.
|
|
@@ -11,7 +16,7 @@ export const ZodProductCreationDto = ZodProduct.omit({ id: true });
|
|
|
11
16
|
* DTO for updating a Product.
|
|
12
17
|
* All fields are optional to support partial updates (PATCH semantics).
|
|
13
18
|
*/
|
|
14
|
-
export const ZodProductUpdateDto =
|
|
19
|
+
export const ZodProductUpdateDto = ZodProductDto.partial();
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* Product categories supported by the system.
|
|
@@ -44,7 +49,7 @@ export type ProductCreationDto = {
|
|
|
44
49
|
stock: number;
|
|
45
50
|
|
|
46
51
|
/** Optional product description. */
|
|
47
|
-
description?: string
|
|
52
|
+
description?: string;
|
|
48
53
|
};
|
|
49
54
|
|
|
50
55
|
/**
|
|
@@ -52,20 +57,23 @@ export type ProductCreationDto = {
|
|
|
52
57
|
* All fields are optional to support partial updates.
|
|
53
58
|
*/
|
|
54
59
|
export type ProductUpdateDto = {
|
|
60
|
+
/** Product ID */
|
|
61
|
+
id: number;
|
|
62
|
+
|
|
55
63
|
/** Optional product name. */
|
|
56
|
-
name?: string
|
|
64
|
+
name?: string;
|
|
57
65
|
|
|
58
66
|
/** Optional product price. */
|
|
59
|
-
price?: number
|
|
67
|
+
price?: number;
|
|
60
68
|
|
|
61
69
|
/** Optional product description. */
|
|
62
|
-
description?: string
|
|
70
|
+
description?: string;
|
|
63
71
|
|
|
64
72
|
/** Optional product category. Must be one of the predefined categories if provided. */
|
|
65
|
-
category?: ProductCategory
|
|
73
|
+
category?: ProductCategory;
|
|
66
74
|
|
|
67
75
|
/** Optional stock count in inventory. */
|
|
68
|
-
stock?: number
|
|
76
|
+
stock?: number;
|
|
69
77
|
};
|
|
70
78
|
|
|
71
79
|
/**
|
|
@@ -88,5 +96,5 @@ export type ProductDto = {
|
|
|
88
96
|
stock: number;
|
|
89
97
|
|
|
90
98
|
/** Optional product description. */
|
|
91
|
-
description?: string
|
|
99
|
+
description?: string;
|
|
92
100
|
};
|
|
@@ -170,8 +170,9 @@ router.put(
|
|
|
170
170
|
"/:id",
|
|
171
171
|
asyncHandler(async (req: Request, res: Response) => {
|
|
172
172
|
const id = await productController.validateId(req.params.id);
|
|
173
|
-
const product = await productController.
|
|
174
|
-
|
|
173
|
+
const product = await productController.validateProductUpdateDto(req.body);
|
|
174
|
+
await productController.update(id, product);
|
|
175
|
+
res.status(200).send();
|
|
175
176
|
})
|
|
176
177
|
);
|
|
177
178
|
|
|
@@ -199,8 +200,7 @@ router.delete(
|
|
|
199
200
|
"/:id",
|
|
200
201
|
asyncHandler(async (req: Request, res: Response) => {
|
|
201
202
|
const id = await productController.validateId(req.params.id);
|
|
202
|
-
|
|
203
|
-
res.json(product);
|
|
203
|
+
await productController.delete(id);
|
|
204
204
|
})
|
|
205
205
|
);
|
|
206
206
|
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
memoizeAsync,
|
|
3
|
-
onError,
|
|
4
|
-
rateLimit,
|
|
5
|
-
timeout,
|
|
6
|
-
before,
|
|
7
|
-
} from "utils-decorators";
|
|
1
|
+
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
8
2
|
import {
|
|
9
3
|
ProductCreationDto,
|
|
10
4
|
ProductDto,
|
|
11
5
|
ProductUpdateDto,
|
|
12
|
-
|
|
13
|
-
ZodProductUpdateDto,
|
|
6
|
+
ZodProductDto,
|
|
14
7
|
} from "./dto/product.dto";
|
|
15
8
|
import { MapAsyncCache } from "@/utilities/cache/memory-cache";
|
|
16
9
|
import { convert, stringToInteger } from "@/utilities/conversion";
|
|
@@ -19,6 +12,8 @@ import { ResponseError } from "@/utilities/error-handling";
|
|
|
19
12
|
import { products } from "@/db";
|
|
20
13
|
import { Product, ZodProduct } from "@/entities/product.entity";
|
|
21
14
|
import { Injectable } from "@/utilities/container";
|
|
15
|
+
import assign from "@/utilities/assignment";
|
|
16
|
+
import { parallelMap } from "@/utilities/parallel/parallel";
|
|
22
17
|
|
|
23
18
|
function exceedHandler() {
|
|
24
19
|
const message = "Too much call in allowed window";
|
|
@@ -67,8 +62,7 @@ export default class ProductController {
|
|
|
67
62
|
public async validateProductCreationDto(
|
|
68
63
|
product: ProductCreationDto
|
|
69
64
|
): Promise<Product> {
|
|
70
|
-
|
|
71
|
-
return { ...newProduct, id: products.length + 1 };
|
|
65
|
+
return await convert({ ...product, id: products.length + 1 }, ZodProduct);
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
@onError({
|
|
@@ -81,11 +75,9 @@ export default class ProductController {
|
|
|
81
75
|
* @returns {Product} A fully formed Product object ready for persistence.
|
|
82
76
|
*/
|
|
83
77
|
public async validateProductUpdateDto(
|
|
84
|
-
product:
|
|
78
|
+
product: ProductUpdateDto
|
|
85
79
|
): Promise<Product> {
|
|
86
|
-
|
|
87
|
-
let updatedProduct: Product = convert(productDto, ZodProduct);
|
|
88
|
-
return { ...updatedProduct, id: products.length + 1 };
|
|
80
|
+
return await convert(product, ZodProduct);
|
|
89
81
|
}
|
|
90
82
|
|
|
91
83
|
@rateLimit({
|
|
@@ -101,13 +93,7 @@ export default class ProductController {
|
|
|
101
93
|
* @throws {ResponseError} 400 - Invalid input data
|
|
102
94
|
*/
|
|
103
95
|
public async create(product: Product): Promise<void> {
|
|
104
|
-
|
|
105
|
-
...product,
|
|
106
|
-
id: products.length + 1,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
products.push(newProduct);
|
|
110
|
-
await productCache.set(newProduct.id.toString(), newProduct as ProductDto);
|
|
96
|
+
products.push(product);
|
|
111
97
|
await productsCache.delete("key");
|
|
112
98
|
}
|
|
113
99
|
|
|
@@ -127,7 +113,10 @@ export default class ProductController {
|
|
|
127
113
|
* @returns List of products with summarized descriptions
|
|
128
114
|
*/
|
|
129
115
|
public async getAll(): Promise<ProductDto[]> {
|
|
130
|
-
return
|
|
116
|
+
return await parallelMap(
|
|
117
|
+
products,
|
|
118
|
+
async (product) => await convert(product, ZodProductDto)
|
|
119
|
+
);
|
|
131
120
|
}
|
|
132
121
|
|
|
133
122
|
@memoizeAsync({
|
|
@@ -150,7 +139,7 @@ export default class ProductController {
|
|
|
150
139
|
if (product == null) {
|
|
151
140
|
throw new ResponseError("Product not found");
|
|
152
141
|
}
|
|
153
|
-
return convert(product
|
|
142
|
+
return await convert(product, ZodProduct);
|
|
154
143
|
}
|
|
155
144
|
|
|
156
145
|
@rateLimit({
|
|
@@ -166,20 +155,18 @@ export default class ProductController {
|
|
|
166
155
|
* @throws {ResponseError} 404 - Product not found
|
|
167
156
|
* @throws {ResponseError} 400 - Invalid ID format or update data
|
|
168
157
|
*/
|
|
169
|
-
public async update(
|
|
170
|
-
id
|
|
171
|
-
|
|
172
|
-
|
|
158
|
+
public async update(id: number, updateData: Product): Promise<void> {
|
|
159
|
+
if (id != updateData.id) {
|
|
160
|
+
throw new ResponseError("Product ID is immutable.", 400);
|
|
161
|
+
}
|
|
173
162
|
const product = await this.get(id);
|
|
174
163
|
if (product != null) {
|
|
175
|
-
|
|
176
|
-
await productCache.
|
|
164
|
+
await assign(updateData, product, ZodProduct);
|
|
165
|
+
await productCache.delete(updateData.id.toString());
|
|
177
166
|
await productsCache.delete("key");
|
|
178
167
|
} else {
|
|
179
168
|
throw new ResponseError("Product dose not exist.", 404);
|
|
180
169
|
}
|
|
181
|
-
|
|
182
|
-
return product;
|
|
183
170
|
}
|
|
184
171
|
|
|
185
172
|
@rateLimit({
|
|
@@ -194,13 +181,13 @@ export default class ProductController {
|
|
|
194
181
|
* @throws {ResponseError} 404 - Product not found
|
|
195
182
|
* @throws {ResponseError} 400 - Invalid ID format
|
|
196
183
|
*/
|
|
197
|
-
public async delete(id: number): Promise<
|
|
184
|
+
public async delete(id: number): Promise<void> {
|
|
198
185
|
const index = products.findIndex((product) => product.id === id);
|
|
199
186
|
if (index == -1) {
|
|
200
187
|
throw new ResponseError("Product dose not exist.", 404);
|
|
201
188
|
}
|
|
202
189
|
await productCache.delete(id.toString());
|
|
203
190
|
await productsCache.delete("key");
|
|
204
|
-
|
|
191
|
+
products.splice(index, 1)[0];
|
|
205
192
|
}
|
|
206
193
|
}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ZodUserCreationDto,
|
|
3
|
-
UserCreationDto,
|
|
4
|
-
UserDto,
|
|
5
|
-
ZodUserDto,
|
|
6
|
-
} from "./dto/user.dto";
|
|
1
|
+
import { UserCreationDto, UserDto, ZodUserDto } from "./dto/user.dto";
|
|
7
2
|
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
8
3
|
import { ResponseError } from "@/utilities/error-handling";
|
|
9
4
|
import { convert, stringToInteger } from "@/utilities/conversion";
|
|
10
5
|
import config from "@/config";
|
|
11
6
|
import { users } from "@/db";
|
|
12
|
-
import { User } from "@/entities/user.entity";
|
|
7
|
+
import { User, ZodUser } from "@/entities/user.entity";
|
|
13
8
|
import { MapAsyncCache } from "@/utilities/cache/memory-cache";
|
|
14
9
|
import { Injectable } from "@/utilities/container";
|
|
10
|
+
import { parallelMap } from "@/utilities/parallel/parallel";
|
|
15
11
|
|
|
16
12
|
function exceedHandler() {
|
|
17
13
|
const message = "Too much call in allowed window";
|
|
@@ -58,8 +54,7 @@ export default class UserController {
|
|
|
58
54
|
* @returns {User} A fully formed User object ready for persistence.
|
|
59
55
|
*/
|
|
60
56
|
public async validateUserCreationDto(user: UserCreationDto): Promise<User> {
|
|
61
|
-
|
|
62
|
-
return { ...newUser, id: users.length + 1 };
|
|
57
|
+
return await convert(user, ZodUser);
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
@rateLimit({
|
|
@@ -76,7 +71,6 @@ export default class UserController {
|
|
|
76
71
|
*/
|
|
77
72
|
public async create(user: User): Promise<void> {
|
|
78
73
|
users.push(user);
|
|
79
|
-
await userCache.set(user.id.toString(), user as User);
|
|
80
74
|
await usersCache.delete("key");
|
|
81
75
|
}
|
|
82
76
|
|
|
@@ -97,7 +91,10 @@ export default class UserController {
|
|
|
97
91
|
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
98
92
|
*/
|
|
99
93
|
public async getAll(): Promise<UserDto[]> {
|
|
100
|
-
return
|
|
94
|
+
return await parallelMap(
|
|
95
|
+
users,
|
|
96
|
+
async (user) => await convert(user, ZodUserDto)
|
|
97
|
+
);
|
|
101
98
|
}
|
|
102
99
|
|
|
103
100
|
@memoizeAsync({
|
|
@@ -122,6 +119,6 @@ export default class UserController {
|
|
|
122
119
|
if (user == null) {
|
|
123
120
|
throw new ResponseError("Product not found");
|
|
124
121
|
}
|
|
125
|
-
return convert(user
|
|
122
|
+
return convert(user, ZodUserDto);
|
|
126
123
|
}
|
|
127
124
|
}
|