codeweaver 3.1.3 → 4.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 +56 -73
- package/package.json +23 -1
- package/src/config.ts +17 -15
- package/src/constants.ts +1 -0
- package/src/core/aws/api-gateway.ts +187 -0
- package/src/core/aws/basic-types.ts +147 -0
- package/src/core/aws/dynamodb.ts +187 -0
- package/src/core/aws/index.ts +9 -0
- package/src/core/aws/lambda.ts +199 -0
- package/src/core/aws/message-broker.ts +167 -0
- package/src/core/aws/message.ts +259 -0
- package/src/core/aws/s3.ts +136 -0
- package/src/core/aws/utilities.ts +44 -0
- package/src/core/cache/basic-types.ts +17 -0
- package/src/core/cache/decorator.ts +72 -0
- package/src/core/cache/index.ts +4 -0
- package/src/core/cache/memory-cache.class.ts +119 -0
- package/src/{utilities/cache/redis-cache.ts → core/cache/redis-cache.class.ts} +58 -10
- package/src/core/container/basic-types.ts +10 -0
- package/src/{utilities → core/container}/container.ts +7 -17
- package/src/core/container/index.ts +2 -0
- package/src/{utilities → core/error}/error-handling.ts +1 -65
- package/src/core/error/index.ts +3 -0
- package/src/core/error/response-error.ts +45 -0
- package/src/core/error/send-http-error.ts +15 -0
- package/src/core/file/file-helpers.ts +166 -0
- package/src/core/file/index.ts +1 -0
- package/src/{utilities → core/helpers}/assignment.ts +2 -2
- package/src/core/helpers/comparison.ts +86 -0
- package/src/{utilities → core/helpers}/conversion.ts +2 -2
- package/src/core/helpers/decorators.ts +316 -0
- package/src/core/helpers/format.ts +9 -0
- package/src/core/helpers/index.ts +7 -0
- package/src/core/helpers/range.ts +67 -0
- package/src/core/helpers/types.ts +3 -0
- package/src/core/logger/index.ts +4 -0
- package/src/{utilities/logger/logger.config.ts → core/logger/winston-logger.config.ts} +1 -1
- package/src/{utilities → core}/logger/winston-logger.service.ts +3 -3
- package/src/core/message-broker/bullmq/basic-types.ts +67 -0
- package/src/core/message-broker/bullmq/broker.ts +141 -0
- package/src/core/message-broker/bullmq/index.ts +3 -0
- package/src/core/message-broker/bullmq/queue.ts +58 -0
- package/src/core/message-broker/bullmq/worker.ts +68 -0
- package/src/core/message-broker/kafka/basic-types.ts +45 -0
- package/src/core/message-broker/kafka/consumer.ts +95 -0
- package/src/core/message-broker/kafka/index.ts +3 -0
- package/src/core/message-broker/kafka/producer.ts +113 -0
- package/src/core/message-broker/rabitmq/basic-types.ts +44 -0
- package/src/core/message-broker/rabitmq/channel.ts +95 -0
- package/src/core/message-broker/rabitmq/consumer.ts +94 -0
- package/src/core/message-broker/rabitmq/index.ts +4 -0
- package/src/core/message-broker/rabitmq/producer.ts +100 -0
- package/src/core/message-broker/utilities.ts +50 -0
- package/src/core/middlewares/basic-types.ts +39 -0
- package/src/core/middlewares/decorators.ts +244 -0
- package/src/core/middlewares/index.ts +3 -0
- package/src/core/middlewares/middlewares.ts +246 -0
- package/src/core/parallel/index.ts +3 -0
- package/src/{utilities → core}/parallel/parallel.ts +11 -1
- package/src/core/rate-limit/basic-types.ts +43 -0
- package/src/core/rate-limit/index.ts +4 -0
- package/src/core/rate-limit/memory-store.ts +65 -0
- package/src/core/rate-limit/rate-limit.ts +134 -0
- package/src/core/rate-limit/redis-store.ts +141 -0
- package/src/core/retry/basic-types.ts +21 -0
- package/src/core/retry/decorator.ts +139 -0
- package/src/core/retry/index.ts +2 -0
- package/src/main.ts +6 -8
- package/src/routers/orders/index.router.ts +5 -1
- package/src/routers/orders/order.controller.ts +54 -64
- package/src/routers/products/index.router.ts +2 -1
- package/src/routers/products/product.controller.ts +33 -68
- package/src/routers/users/index.router.ts +1 -1
- package/src/routers/users/user.controller.ts +25 -50
- package/src/utilities/cache/memory-cache.ts +0 -74
- /package/src/{utilities → core}/logger/base-logger.interface.ts +0 -0
- /package/src/{utilities → core}/logger/logger.service.ts +0 -0
- /package/src/{utilities → core}/parallel/chanel.ts +0 -0
- /package/src/{utilities → core}/parallel/worker-pool.ts +0 -0
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
2
1
|
import { OrderDto, OrderCreationDto, ZodOrderDto } from "./dto/order.dto";
|
|
3
|
-
import { ResponseError } from "@/
|
|
4
|
-
import { convert, stringToInteger } from "@/
|
|
2
|
+
import { ResponseError } from "@/core/error";
|
|
3
|
+
import { convert, stringToInteger } from "@/core/helpers";
|
|
5
4
|
import { config } from "@/config";
|
|
6
5
|
import { orders } from "@/db";
|
|
7
6
|
import { Order, ZodOrder } from "@/entities/order.entity";
|
|
8
|
-
import { MapAsyncCache } from "@/
|
|
9
|
-
import { Injectable } from "@/
|
|
10
|
-
import { parallelMap } from "@/
|
|
7
|
+
import { Invalidate, MapAsyncCache, Memoize } from "@/core/cache";
|
|
8
|
+
import { Injectable } from "@/core/container";
|
|
9
|
+
import { parallelMap } from "@/core/parallel";
|
|
10
|
+
import { RateLimit } from "@/core/rate-limit";
|
|
11
|
+
import { ErrorHandler, Timeout } from "@/core/middlewares";
|
|
11
12
|
|
|
12
|
-
function exceedHandler() {
|
|
13
|
+
async function exceedHandler() {
|
|
13
14
|
const message = "Too much call in allowed window";
|
|
14
15
|
throw new ResponseError(message, 429);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
function invalidInputHandler(e: ResponseError) {
|
|
18
|
+
async function invalidInputHandler(e: ResponseError) {
|
|
18
19
|
const message = "Invalid input";
|
|
19
20
|
throw new ResponseError(message, 400, e.message);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const ordersCache = new MapAsyncCache<OrderDto[]>(
|
|
23
|
+
const ordersCache = new MapAsyncCache<OrderDto[]>(1);
|
|
23
24
|
const orderCache = new MapAsyncCache<OrderDto>(config.cacheSize);
|
|
24
25
|
|
|
25
26
|
@Injectable()
|
|
@@ -31,9 +32,7 @@ const orderCache = new MapAsyncCache<OrderDto>(config.cacheSize);
|
|
|
31
32
|
export default class OrderController {
|
|
32
33
|
// constructor(private readonly orderService: OrderService) { }
|
|
33
34
|
|
|
34
|
-
@
|
|
35
|
-
func: invalidInputHandler,
|
|
36
|
-
})
|
|
35
|
+
@ErrorHandler(invalidInputHandler)
|
|
37
36
|
/**
|
|
38
37
|
* Validates a string ID and converts it to a number.
|
|
39
38
|
*
|
|
@@ -44,9 +43,7 @@ export default class OrderController {
|
|
|
44
43
|
return stringToInteger(id);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
@
|
|
48
|
-
func: invalidInputHandler,
|
|
49
|
-
})
|
|
46
|
+
@ErrorHandler(invalidInputHandler)
|
|
50
47
|
/**
|
|
51
48
|
* Validates and creates a new Order from the given DTO.
|
|
52
49
|
*
|
|
@@ -66,11 +63,12 @@ export default class OrderController {
|
|
|
66
63
|
return convert(newOrder, ZodOrder);
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
@
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
@Invalidate(ordersCache, true)
|
|
67
|
+
@RateLimit(
|
|
68
|
+
config.rateLimitTimeSpan,
|
|
69
|
+
config.rateLimitAllowedCalls,
|
|
70
|
+
exceedHandler
|
|
71
|
+
)
|
|
74
72
|
/**
|
|
75
73
|
* Create a new order
|
|
76
74
|
* @param {Order} order - Order creation data
|
|
@@ -80,41 +78,35 @@ export default class OrderController {
|
|
|
80
78
|
*/
|
|
81
79
|
public async create(order: Order): Promise<void> {
|
|
82
80
|
orders.push(order);
|
|
83
|
-
await ordersCache.delete("key");
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
@
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
timeSpanMs: config.rateLimitTimeSpan,
|
|
94
|
-
allowedCalls: config.rateLimitAllowedCalls,
|
|
95
|
-
exceedHandler,
|
|
96
|
-
})
|
|
83
|
+
@Memoize(ordersCache, () => "key")
|
|
84
|
+
@Timeout(config.timeout)
|
|
85
|
+
@RateLimit(
|
|
86
|
+
config.rateLimitTimeSpan,
|
|
87
|
+
config.rateLimitAllowedCalls,
|
|
88
|
+
exceedHandler
|
|
89
|
+
)
|
|
97
90
|
/**
|
|
98
91
|
* Retrieves all orders
|
|
99
92
|
* @returns List of orders
|
|
100
93
|
*/
|
|
101
|
-
public async getAll(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
public async getAll(
|
|
95
|
+
timeoutSignal?: AbortSignal
|
|
96
|
+
): Promise<(OrderDto | null)[]> {
|
|
97
|
+
return await parallelMap(orders, async (order) =>
|
|
98
|
+
timeoutSignal?.aborted == false
|
|
99
|
+
? await convert<Order, OrderDto>(order, ZodOrderDto)
|
|
100
|
+
: null
|
|
105
101
|
);
|
|
106
102
|
}
|
|
107
103
|
|
|
108
|
-
@
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
timeSpanMs: config.rateLimitTimeSpan,
|
|
115
|
-
allowedCalls: config.rateLimitAllowedCalls,
|
|
116
|
-
exceedHandler,
|
|
117
|
-
})
|
|
104
|
+
@Memoize(orderCache)
|
|
105
|
+
@RateLimit(
|
|
106
|
+
config.rateLimitTimeSpan,
|
|
107
|
+
config.rateLimitAllowedCalls,
|
|
108
|
+
exceedHandler
|
|
109
|
+
)
|
|
118
110
|
/**
|
|
119
111
|
* Finds an order by its ID
|
|
120
112
|
* @param {number} id - Order ID as string
|
|
@@ -128,11 +120,13 @@ export default class OrderController {
|
|
|
128
120
|
return await convert(order, ZodOrder);
|
|
129
121
|
}
|
|
130
122
|
|
|
131
|
-
@
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
@Invalidate(orderCache)
|
|
124
|
+
@Invalidate(ordersCache, true)
|
|
125
|
+
@RateLimit(
|
|
126
|
+
config.rateLimitTimeSpan,
|
|
127
|
+
config.rateLimitAllowedCalls,
|
|
128
|
+
exceedHandler
|
|
129
|
+
)
|
|
136
130
|
/**
|
|
137
131
|
* Cancel an existing order
|
|
138
132
|
* @param {number} id - Order ID to cancel
|
|
@@ -145,21 +139,20 @@ export default class OrderController {
|
|
|
145
139
|
if (order.status != "Processing") {
|
|
146
140
|
throw new ResponseError(
|
|
147
141
|
"Cancellation is not available unless the order is in processing status.",
|
|
148
|
-
|
|
142
|
+
409
|
|
149
143
|
);
|
|
150
144
|
}
|
|
151
145
|
order.status = "Canceled";
|
|
152
146
|
order.deliveredAt = new Date();
|
|
153
|
-
|
|
154
|
-
await orderCache.delete(id.toString());
|
|
155
|
-
await ordersCache.delete("key");
|
|
156
147
|
}
|
|
157
148
|
|
|
158
|
-
@
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
149
|
+
@Invalidate(orderCache)
|
|
150
|
+
@Invalidate(ordersCache, true)
|
|
151
|
+
@RateLimit(
|
|
152
|
+
config.rateLimitTimeSpan,
|
|
153
|
+
config.rateLimitAllowedCalls,
|
|
154
|
+
exceedHandler
|
|
155
|
+
)
|
|
163
156
|
/**
|
|
164
157
|
* Mark an order as delivered
|
|
165
158
|
* @param {number} id - Order ID to mark as delivered
|
|
@@ -172,13 +165,10 @@ export default class OrderController {
|
|
|
172
165
|
if (order.status != "Processing") {
|
|
173
166
|
throw new ResponseError(
|
|
174
167
|
"Delivery is only available when the order is in processing status.",
|
|
175
|
-
|
|
168
|
+
409
|
|
176
169
|
);
|
|
177
170
|
}
|
|
178
171
|
order.status = "Delivered";
|
|
179
172
|
order.deliveredAt = new Date();
|
|
180
|
-
|
|
181
|
-
await orderCache.delete(id.toString());
|
|
182
|
-
await ordersCache.delete("key");
|
|
183
173
|
}
|
|
184
174
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Router, Request, Response } from "express";
|
|
2
2
|
import asyncHandler from "express-async-handler";
|
|
3
3
|
import ProductController from "./product.controller";
|
|
4
|
-
import { resolve } from "@/
|
|
4
|
+
import { resolve } from "@/core/container";
|
|
5
5
|
|
|
6
6
|
const router = Router();
|
|
7
7
|
const productController = resolve(ProductController);
|
|
@@ -201,6 +201,7 @@ router.delete(
|
|
|
201
201
|
asyncHandler(async (req: Request, res: Response) => {
|
|
202
202
|
const id = await productController.validateId(req.params.id);
|
|
203
203
|
await productController.delete(id);
|
|
204
|
+
res.status(204).send();
|
|
204
205
|
})
|
|
205
206
|
);
|
|
206
207
|
|
|
@@ -1,31 +1,27 @@
|
|
|
1
|
-
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
2
1
|
import {
|
|
3
2
|
ProductCreationDto,
|
|
4
3
|
ProductDto,
|
|
5
4
|
ProductUpdateDto,
|
|
6
5
|
ZodProductDto,
|
|
7
6
|
} from "./dto/product.dto";
|
|
8
|
-
import { MapAsyncCache } from "@/
|
|
9
|
-
import { convert, stringToInteger } from "@/
|
|
7
|
+
import { Invalidate, MapAsyncCache, Memoize } from "@/core/cache";
|
|
8
|
+
import { convert, stringToInteger } from "@/core/helpers";
|
|
10
9
|
import { config } from "@/config";
|
|
11
|
-
import { ResponseError } from "@/
|
|
10
|
+
import { ResponseError } from "@/core/error";
|
|
12
11
|
import { products } from "@/db";
|
|
13
12
|
import { Product, ZodProduct } from "@/entities/product.entity";
|
|
14
|
-
import { Injectable } from "@/
|
|
15
|
-
import assign from "@/
|
|
16
|
-
import { parallelMap } from "@/
|
|
13
|
+
import { Injectable } from "@/core/container";
|
|
14
|
+
import { assign } from "@/core/helpers";
|
|
15
|
+
import { parallelMap } from "@/core/parallel";
|
|
16
|
+
import { ErrorHandler, Timeout } from "@/core/middlewares";
|
|
17
|
+
import { RateLimit } from "@/core/rate-limit";
|
|
17
18
|
|
|
18
|
-
function
|
|
19
|
-
const message = "Too much call in allowed window";
|
|
20
|
-
throw new ResponseError(message, 429);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function invalidInputHandler(e: ResponseError) {
|
|
19
|
+
async function invalidInputHandler(e: ResponseError) {
|
|
24
20
|
const message = "Invalid input";
|
|
25
21
|
throw new ResponseError(message, 400, e.message);
|
|
26
22
|
}
|
|
27
23
|
|
|
28
|
-
const productsCache = new MapAsyncCache<ProductDto[]>(
|
|
24
|
+
const productsCache = new MapAsyncCache<ProductDto[]>(1);
|
|
29
25
|
const productCache = new MapAsyncCache<ProductDto>(config.cacheSize);
|
|
30
26
|
|
|
31
27
|
@Injectable()
|
|
@@ -37,9 +33,7 @@ const productCache = new MapAsyncCache<ProductDto>(config.cacheSize);
|
|
|
37
33
|
export default class ProductController {
|
|
38
34
|
// constructor(private readonly productService: ProductService) { }
|
|
39
35
|
|
|
40
|
-
@
|
|
41
|
-
func: invalidInputHandler,
|
|
42
|
-
})
|
|
36
|
+
@ErrorHandler(invalidInputHandler)
|
|
43
37
|
/**
|
|
44
38
|
* Validates a string ID and converts it to a number.
|
|
45
39
|
*
|
|
@@ -50,9 +44,7 @@ export default class ProductController {
|
|
|
50
44
|
return stringToInteger(id);
|
|
51
45
|
}
|
|
52
46
|
|
|
53
|
-
@
|
|
54
|
-
func: invalidInputHandler,
|
|
55
|
-
})
|
|
47
|
+
@ErrorHandler(invalidInputHandler)
|
|
56
48
|
/**
|
|
57
49
|
* Validates and creates a new Product from the given DTO.
|
|
58
50
|
*
|
|
@@ -65,9 +57,7 @@ export default class ProductController {
|
|
|
65
57
|
return await convert({ ...product, id: products.length + 1 }, ZodProduct);
|
|
66
58
|
}
|
|
67
59
|
|
|
68
|
-
@
|
|
69
|
-
func: invalidInputHandler,
|
|
70
|
-
})
|
|
60
|
+
@ErrorHandler(invalidInputHandler)
|
|
71
61
|
/**
|
|
72
62
|
* Validates and creates a new Product from the given DTO.
|
|
73
63
|
*
|
|
@@ -80,11 +70,8 @@ export default class ProductController {
|
|
|
80
70
|
return await convert(product, ZodProduct);
|
|
81
71
|
}
|
|
82
72
|
|
|
83
|
-
@
|
|
84
|
-
|
|
85
|
-
allowedCalls: config.rateLimitAllowedCalls,
|
|
86
|
-
exceedHandler,
|
|
87
|
-
})
|
|
73
|
+
@Invalidate(productsCache, true)
|
|
74
|
+
@RateLimit(config.rateLimitTimeSpan, config.rateLimitAllowedCalls)
|
|
88
75
|
/**
|
|
89
76
|
* Creates a new product with validated data
|
|
90
77
|
* @param {Product} product - Product creation data validated by Zod schema
|
|
@@ -94,41 +81,27 @@ export default class ProductController {
|
|
|
94
81
|
*/
|
|
95
82
|
public async create(product: Product): Promise<void> {
|
|
96
83
|
products.push(product);
|
|
97
|
-
await productsCache.delete("key");
|
|
98
84
|
}
|
|
99
85
|
|
|
100
|
-
@
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
expirationTimeMs: config.memoizeTime,
|
|
104
|
-
})
|
|
105
|
-
@timeout(config.timeout)
|
|
106
|
-
@rateLimit({
|
|
107
|
-
timeSpanMs: config.rateLimitTimeSpan,
|
|
108
|
-
allowedCalls: config.rateLimitAllowedCalls,
|
|
109
|
-
exceedHandler,
|
|
110
|
-
})
|
|
86
|
+
@Memoize(productsCache, () => "key")
|
|
87
|
+
@Timeout(config.timeout)
|
|
88
|
+
@RateLimit(config.rateLimitTimeSpan, config.rateLimitAllowedCalls)
|
|
111
89
|
/**
|
|
112
90
|
* Retrieves all products with truncated descriptions
|
|
113
91
|
* @returns List of products with summarized descriptions
|
|
114
92
|
*/
|
|
115
|
-
public async getAll(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
93
|
+
public async getAll(
|
|
94
|
+
timeoutSignal?: AbortSignal
|
|
95
|
+
): Promise<(ProductDto | null)[]> {
|
|
96
|
+
return await parallelMap(products, async (product) =>
|
|
97
|
+
timeoutSignal?.aborted == false
|
|
98
|
+
? await convert<Product, ProductDto>(product, ZodProductDto)
|
|
99
|
+
: null
|
|
119
100
|
);
|
|
120
101
|
}
|
|
121
102
|
|
|
122
|
-
@
|
|
123
|
-
|
|
124
|
-
keyResolver: (id: number) => id.toString(),
|
|
125
|
-
expirationTimeMs: config.memoizeTime,
|
|
126
|
-
})
|
|
127
|
-
@rateLimit({
|
|
128
|
-
timeSpanMs: config.rateLimitTimeSpan,
|
|
129
|
-
allowedCalls: config.rateLimitAllowedCalls,
|
|
130
|
-
exceedHandler,
|
|
131
|
-
})
|
|
103
|
+
@Memoize(productCache)
|
|
104
|
+
@RateLimit(config.rateLimitTimeSpan, config.rateLimitAllowedCalls)
|
|
132
105
|
/**
|
|
133
106
|
* Finds a product by its ID
|
|
134
107
|
* @param {number} id - Product ID as string
|
|
@@ -142,11 +115,9 @@ export default class ProductController {
|
|
|
142
115
|
return await convert(product, ZodProduct);
|
|
143
116
|
}
|
|
144
117
|
|
|
145
|
-
@
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
exceedHandler,
|
|
149
|
-
})
|
|
118
|
+
@Invalidate(productCache)
|
|
119
|
+
@Invalidate(productsCache, true)
|
|
120
|
+
@RateLimit(config.rateLimitTimeSpan, config.rateLimitAllowedCalls)
|
|
150
121
|
/**
|
|
151
122
|
* Updates an existing product
|
|
152
123
|
* @param {number} id - Product ID to update
|
|
@@ -162,18 +133,14 @@ export default class ProductController {
|
|
|
162
133
|
const product = await this.get(id);
|
|
163
134
|
if (product != null) {
|
|
164
135
|
await assign(product, updateData, ZodProduct);
|
|
165
|
-
await productCache.delete(updateData.id.toString());
|
|
166
|
-
await productsCache.delete("key");
|
|
167
136
|
} else {
|
|
168
137
|
throw new ResponseError("Product dose not exist.", 404);
|
|
169
138
|
}
|
|
170
139
|
}
|
|
171
140
|
|
|
172
|
-
@
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
exceedHandler,
|
|
176
|
-
})
|
|
141
|
+
@Invalidate(productCache)
|
|
142
|
+
@Invalidate(productsCache, true)
|
|
143
|
+
@RateLimit(config.rateLimitTimeSpan, config.rateLimitAllowedCalls)
|
|
177
144
|
/**
|
|
178
145
|
* Deletes a product by ID
|
|
179
146
|
* @param {number} id - Product ID to delete
|
|
@@ -186,8 +153,6 @@ export default class ProductController {
|
|
|
186
153
|
if (index == -1) {
|
|
187
154
|
throw new ResponseError("Product dose not exist.", 404);
|
|
188
155
|
}
|
|
189
|
-
await productCache.delete(id.toString());
|
|
190
|
-
await productsCache.delete("key");
|
|
191
156
|
products.splice(index, 1)[0];
|
|
192
157
|
}
|
|
193
158
|
}
|
|
@@ -1,7 +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 { resolve } from "@/
|
|
4
|
+
import { resolve } from "@/core/container";
|
|
5
5
|
|
|
6
6
|
const router = Router();
|
|
7
7
|
const userController = resolve(UserController);
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import { UserCreationDto, UserDto, ZodUserDto } from "./dto/user.dto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { convert, stringToInteger } from "@/utilities/conversion";
|
|
2
|
+
import { ResponseError } from "@/core/error";
|
|
3
|
+
import { convert, stringToInteger } from "@/core/helpers";
|
|
5
4
|
import { config } from "@/config";
|
|
6
5
|
import { users } from "@/db";
|
|
7
6
|
import { User, ZodUser } from "@/entities/user.entity";
|
|
8
|
-
import { MapAsyncCache } from "@/
|
|
9
|
-
import { Injectable } from "@/
|
|
10
|
-
import { parallelMap } from "@/
|
|
7
|
+
import { Invalidate, MapAsyncCache, Memoize } from "@/core/cache";
|
|
8
|
+
import { Injectable } from "@/core/container";
|
|
9
|
+
import { parallelMap } from "@/core/parallel";
|
|
10
|
+
import { ErrorHandler, LogMethod, Timeout } from "@/core/middlewares";
|
|
11
|
+
import { RateLimit } from "@/core/rate-limit";
|
|
11
12
|
|
|
12
|
-
function
|
|
13
|
-
const message = "Too much call in allowed window";
|
|
14
|
-
throw new ResponseError(message, 429);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function invalidInputHandler(e: ResponseError) {
|
|
13
|
+
async function invalidInputHandler(e: ResponseError) {
|
|
18
14
|
const message = "Invalid input";
|
|
19
15
|
throw new ResponseError(message, 400, e?.message);
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
const usersCache = new MapAsyncCache<UserDto[]>(config.cacheSize);
|
|
23
18
|
const userCache = new MapAsyncCache<UserDto>(config.cacheSize);
|
|
19
|
+
const usersCache = new MapAsyncCache<UserDto[]>(1);
|
|
24
20
|
|
|
25
21
|
@Injectable()
|
|
26
22
|
/**
|
|
@@ -31,9 +27,7 @@ const userCache = new MapAsyncCache<UserDto>(config.cacheSize);
|
|
|
31
27
|
export default class UserController {
|
|
32
28
|
// constructor(private readonly userService: UserService) { }
|
|
33
29
|
|
|
34
|
-
@
|
|
35
|
-
func: invalidInputHandler,
|
|
36
|
-
})
|
|
30
|
+
@ErrorHandler(invalidInputHandler)
|
|
37
31
|
/**
|
|
38
32
|
* Validates a string ID and converts it to a number.
|
|
39
33
|
*
|
|
@@ -44,9 +38,7 @@ export default class UserController {
|
|
|
44
38
|
return stringToInteger(id);
|
|
45
39
|
}
|
|
46
40
|
|
|
47
|
-
@
|
|
48
|
-
func: invalidInputHandler,
|
|
49
|
-
})
|
|
41
|
+
@ErrorHandler(invalidInputHandler)
|
|
50
42
|
/**
|
|
51
43
|
* Validates and creates a new User from the given DTO.
|
|
52
44
|
*
|
|
@@ -57,11 +49,8 @@ export default class UserController {
|
|
|
57
49
|
return await convert(user, ZodUser);
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
@
|
|
61
|
-
|
|
62
|
-
allowedCalls: config.rateLimitAllowedCalls,
|
|
63
|
-
exceedHandler,
|
|
64
|
-
})
|
|
52
|
+
@Invalidate(usersCache, true)
|
|
53
|
+
@RateLimit(config.rateLimitTimeSpan, config.rateLimitAllowedCalls)
|
|
65
54
|
/**
|
|
66
55
|
* Create a new user
|
|
67
56
|
* @param {User} user - User creation data validated by Zod schema
|
|
@@ -71,42 +60,28 @@ export default class UserController {
|
|
|
71
60
|
*/
|
|
72
61
|
public async create(user: User): Promise<void> {
|
|
73
62
|
users.push(user);
|
|
74
|
-
await usersCache.delete("key");
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
@
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
expirationTimeMs: config.memoizeTime,
|
|
81
|
-
})
|
|
82
|
-
@timeout(config.timeout)
|
|
83
|
-
@rateLimit({
|
|
84
|
-
timeSpanMs: config.rateLimitTimeSpan,
|
|
85
|
-
allowedCalls: config.rateLimitAllowedCalls,
|
|
86
|
-
exceedHandler,
|
|
87
|
-
})
|
|
65
|
+
@Memoize(usersCache, () => "key")
|
|
66
|
+
@Timeout(config.timeout)
|
|
67
|
+
@RateLimit(config.rateLimitTimeSpan, config.rateLimitAllowedCalls)
|
|
88
68
|
/**
|
|
89
69
|
* Get all users
|
|
90
70
|
* @returns {Promise<UserDto[]>} List of users with hidden password fields
|
|
91
71
|
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
92
72
|
*/
|
|
93
|
-
public async getAll(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
73
|
+
public async getAll(
|
|
74
|
+
timeoutSignal?: AbortSignal
|
|
75
|
+
): Promise<(UserDto | null)[]> {
|
|
76
|
+
return await parallelMap(users, async (user) =>
|
|
77
|
+
timeoutSignal?.aborted == false
|
|
78
|
+
? await convert<User, UserDto>(user!, ZodUserDto)
|
|
79
|
+
: null
|
|
97
80
|
);
|
|
98
81
|
}
|
|
99
82
|
|
|
100
|
-
@
|
|
101
|
-
|
|
102
|
-
keyResolver: (id: number) => id.toString(),
|
|
103
|
-
expirationTimeMs: config.memoizeTime,
|
|
104
|
-
})
|
|
105
|
-
@rateLimit({
|
|
106
|
-
timeSpanMs: config.rateLimitTimeSpan,
|
|
107
|
-
allowedCalls: config.rateLimitAllowedCalls,
|
|
108
|
-
exceedHandler,
|
|
109
|
-
})
|
|
83
|
+
@Memoize(userCache)
|
|
84
|
+
@RateLimit(config.rateLimitTimeSpan, config.rateLimitAllowedCalls)
|
|
110
85
|
/**
|
|
111
86
|
* Get user by ID
|
|
112
87
|
* @param {number} id - User ID as string
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { AsyncCache } from "utils-decorators";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A simple in-memory map-based cache implementing AsyncCache<T>.
|
|
5
|
-
* This is a straightforward wrapper around a Map<string, T>.
|
|
6
|
-
*/
|
|
7
|
-
export class MapAsyncCache<T> implements AsyncCache<T> {
|
|
8
|
-
public constructor(
|
|
9
|
-
private capacity?: number,
|
|
10
|
-
private cache?: Map<string, T>
|
|
11
|
-
) {
|
|
12
|
-
this.capacity = this.capacity ?? Number.POSITIVE_INFINITY;
|
|
13
|
-
this.cache = this.cache ?? new Map<string, T>();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Asynchronously set a value by key.
|
|
18
|
-
* @param key The cache key
|
|
19
|
-
* @param value The value to cache
|
|
20
|
-
*/
|
|
21
|
-
async set(key: string, value: T): Promise<void> {
|
|
22
|
-
if (value != null) {
|
|
23
|
-
if (this.cache?.has(key)) {
|
|
24
|
-
this.cache?.set(key, value);
|
|
25
|
-
} else {
|
|
26
|
-
if (
|
|
27
|
-
(this.capacity ?? Number.POSITIVE_INFINITY) > (this.cache?.size ?? 0)
|
|
28
|
-
) {
|
|
29
|
-
this.cache?.set(key, value);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
this.cache?.delete(key);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Asynchronously get a value by key.
|
|
39
|
-
* - If the key exists, returns the value.
|
|
40
|
-
* - If the key does not exist, returns undefined cast to T to satisfy the Promise<T> return type.
|
|
41
|
-
*
|
|
42
|
-
* Note: Returning undefined may be surprising for callers expecting a strict A/B.
|
|
43
|
-
* Consider returning `T | undefined` from AsyncCache if you can adjust the interface,
|
|
44
|
-
* or throw a ResponseError for "not found" depending on your usage.
|
|
45
|
-
*/
|
|
46
|
-
async get(key: string): Promise<T> {
|
|
47
|
-
if (this.cache?.has(key)) {
|
|
48
|
-
return this.cache?.get(key) ?? (null as T);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return null as T;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Asynchronously delete a value by key.
|
|
56
|
-
*/
|
|
57
|
-
async delete(key: string): Promise<void> {
|
|
58
|
-
this.cache?.delete(key);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Asynchronously check if a key exists in the cache.
|
|
63
|
-
*/
|
|
64
|
-
async has(key: string): Promise<boolean> {
|
|
65
|
-
return this.cache?.has(key) ?? false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Asynchronously clear the cache.
|
|
70
|
-
*/
|
|
71
|
-
async clear(key: string): Promise<void> {
|
|
72
|
-
this.cache?.clear();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|