codeweaver 1.0.15 → 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.
- package/README.md +252 -40
- package/package.json +17 -13
- package/src/app.ts +21 -3
- package/src/config.ts +33 -0
- package/src/packages/ts-zod-decorators/index.ts +3 -0
- package/src/packages/ts-zod-decorators/validate.decorator.ts +20 -0
- package/src/packages/ts-zod-decorators/validator.class.ts +72 -0
- package/src/packages/ts-zod-decorators/zod-input.decorator.ts +12 -0
- package/src/packages/ts-zod-decorators/zod-output.decorator.ts +11 -0
- package/src/routers/index.ts +2 -2
- package/src/routers/orders/dto/order.dto.ts +40 -0
- package/src/routers/orders/index.ts +131 -10
- package/src/routers/orders/order.controller.ts +191 -0
- package/src/routers/products/dto/product.dto.ts +36 -0
- package/src/routers/products/index.ts +104 -64
- package/src/routers/products/product.controller.ts +237 -0
- package/src/routers/users/dto/user.dto.ts +14 -2
- package/src/routers/users/index.ts +54 -36
- package/src/routers/users/user.controller.ts +144 -16
- package/src/types.ts +16 -0
- package/src/utilities.ts +47 -0
- package/tsconfig.json +9 -3
- package/tsconfig.paths.json +10 -0
|
@@ -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
|
-
|
|
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
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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:
|
|
44
|
+
* description: User created
|
|
38
45
|
*/
|
|
39
46
|
router.post(
|
|
40
47
|
"/",
|
|
41
48
|
asyncHandler(async (req: Request, res: Response) => {
|
|
42
|
-
const
|
|
43
|
-
res.status(201).json(
|
|
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
|
-
* /
|
|
56
|
+
* /users/{id}:
|
|
50
57
|
* get:
|
|
51
|
-
* summary: Get user
|
|
52
|
-
*
|
|
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:
|
|
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
|
-
|
|
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
|
-
* /
|
|
84
|
+
* /users:
|
|
67
85
|
* get:
|
|
68
|
-
* summary: Get
|
|
69
|
-
* description: Returns user
|
|
86
|
+
* summary: Get users
|
|
87
|
+
* description: Returns a list of user objects.
|
|
70
88
|
* responses:
|
|
71
89
|
* 200:
|
|
72
|
-
* description:
|
|
90
|
+
* description: A list of user objects
|
|
73
91
|
*/
|
|
74
92
|
router.get(
|
|
75
|
-
"/
|
|
93
|
+
"/",
|
|
76
94
|
asyncHandler(async (req: Request, res: Response) => {
|
|
77
|
-
res.
|
|
95
|
+
res.json(await userController.getAll());
|
|
78
96
|
})
|
|
79
97
|
);
|
|
80
98
|
|
|
@@ -1,33 +1,161 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { User, ZodUserCreationDto, UserCreationDto } from "./dto/user.dto";
|
|
2
|
+
import { onError, rateLimit, timeout } from "utils-decorators";
|
|
3
|
+
import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
|
|
4
|
+
import { ResponseError } from "@/types";
|
|
5
|
+
import { tryParseId } from "@/utilities";
|
|
6
|
+
|
|
7
|
+
// Array to store users (as a mock database)
|
|
8
|
+
const users = [
|
|
9
|
+
{
|
|
10
|
+
id: 1,
|
|
11
|
+
username: "johndoe",
|
|
12
|
+
email: "johndoe@gmail.com",
|
|
13
|
+
password: "S3cur3P@ssw0rd",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 2,
|
|
17
|
+
username: "janesmith",
|
|
18
|
+
email: "janesmith@yahoo.com",
|
|
19
|
+
password: "P@ssw0rd2024",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 3,
|
|
23
|
+
username: "michael89",
|
|
24
|
+
email: "michael89@hotmail.com",
|
|
25
|
+
password: "M1chael!2024",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 4,
|
|
29
|
+
username: "lisa.wong",
|
|
30
|
+
email: "lisa.wong@example.com",
|
|
31
|
+
password: "L1saW0ng!2024",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 5,
|
|
35
|
+
username: "alex_k",
|
|
36
|
+
email: "alex.k@gmail.com",
|
|
37
|
+
password: "A1ex#Key2024",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 6,
|
|
41
|
+
username: "emilyj",
|
|
42
|
+
email: "emilyj@hotmail.com",
|
|
43
|
+
password: "Em!ly0101",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 7,
|
|
47
|
+
username: "davidparker",
|
|
48
|
+
email: "david.parker@yahoo.com",
|
|
49
|
+
password: "D@v!d2024",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 8,
|
|
53
|
+
username: "sophia_m",
|
|
54
|
+
email: "sophia.m@gmail.com",
|
|
55
|
+
password: "Sophi@2024",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 9,
|
|
59
|
+
username: "chrisw",
|
|
60
|
+
email: "chrisw@outlook.com",
|
|
61
|
+
password: "Chri$Wong21",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 10,
|
|
65
|
+
username: "natalie_b",
|
|
66
|
+
email: "natalie_b@gmail.com",
|
|
67
|
+
password: "N@talie#B2024",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
4
70
|
|
|
5
71
|
function exceedHandler() {
|
|
6
|
-
|
|
72
|
+
const message = "Too much call in allowed window";
|
|
73
|
+
|
|
74
|
+
throw new Error(message, {
|
|
75
|
+
cause: { status: 500, message } satisfies ResponseError,
|
|
76
|
+
});
|
|
7
77
|
}
|
|
8
78
|
|
|
9
|
-
function
|
|
10
|
-
|
|
79
|
+
function getUserErrorHandler(e: Error) {
|
|
80
|
+
const message = "User not found.";
|
|
81
|
+
|
|
82
|
+
throw new Error(message, {
|
|
83
|
+
cause: { status: 404, message, details: e.message } satisfies ResponseError,
|
|
84
|
+
});
|
|
11
85
|
}
|
|
12
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Controller for handling user-related operations
|
|
89
|
+
* @class UserController
|
|
90
|
+
* @desc Provides methods for user management including CRUD operations
|
|
91
|
+
*/
|
|
13
92
|
export default class UserController {
|
|
14
|
-
//constructor(private readonly userService: UserService) { }
|
|
93
|
+
// constructor(private readonly userService: UserService) { }
|
|
15
94
|
|
|
16
|
-
// Throttle the createUser method to 1 request per 200 milliseconds
|
|
17
95
|
@rateLimit({
|
|
18
96
|
timeSpanMs: 60000,
|
|
19
97
|
allowedCalls: 300,
|
|
20
98
|
exceedHandler,
|
|
21
99
|
})
|
|
100
|
+
@Validate
|
|
101
|
+
/**
|
|
102
|
+
* Create a new user
|
|
103
|
+
* @param {UserCreationDto} user - User creation data validated by Zod schema
|
|
104
|
+
* @returns {Promise<void>}
|
|
105
|
+
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
106
|
+
* @throws {ResponseError} 400 - Invalid input data
|
|
107
|
+
*/
|
|
108
|
+
public async create(@ZodInput(ZodUserCreationDto) user: UserCreationDto) {
|
|
109
|
+
users.push({ ...user, id: users.length + 1 } satisfies User);
|
|
110
|
+
}
|
|
111
|
+
|
|
22
112
|
@onError({
|
|
23
|
-
func:
|
|
113
|
+
func: getUserErrorHandler,
|
|
24
114
|
})
|
|
25
|
-
@
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
115
|
+
@rateLimit({
|
|
116
|
+
timeSpanMs: 60000,
|
|
117
|
+
allowedCalls: 300,
|
|
118
|
+
exceedHandler,
|
|
119
|
+
})
|
|
120
|
+
/**
|
|
121
|
+
* Get user by ID
|
|
122
|
+
* @param {string} id - User ID as string
|
|
123
|
+
* @returns {Promise<User | ResponseError>} User details or error object
|
|
124
|
+
* @throws {ResponseError} 404 - User not found
|
|
125
|
+
* @throws {ResponseError} 400 - Invalid ID format
|
|
126
|
+
*/
|
|
127
|
+
public async get(id: string): Promise<User | ResponseError> {
|
|
128
|
+
const userId = tryParseId(id);
|
|
129
|
+
if (typeof userId != "number") return userId satisfies ResponseError;
|
|
130
|
+
const user = users.find((user) => user.id === userId);
|
|
131
|
+
|
|
132
|
+
if (!user)
|
|
133
|
+
return {
|
|
134
|
+
status: 404,
|
|
135
|
+
message: "User dose not exist.",
|
|
136
|
+
} satisfies ResponseError;
|
|
137
|
+
|
|
138
|
+
return user satisfies User;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@timeout(20000)
|
|
142
|
+
@rateLimit({
|
|
143
|
+
timeSpanMs: 60000,
|
|
144
|
+
allowedCalls: 300,
|
|
145
|
+
exceedHandler,
|
|
146
|
+
})
|
|
147
|
+
/**
|
|
148
|
+
* Get all users with masked passwords
|
|
149
|
+
* @returns {Promise<User[]>} List of users with hidden password fields
|
|
150
|
+
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
151
|
+
*/
|
|
152
|
+
public async getAll(): Promise<User[]> {
|
|
153
|
+
return users.map(
|
|
154
|
+
(user) =>
|
|
155
|
+
({
|
|
156
|
+
...user,
|
|
157
|
+
password: "?",
|
|
158
|
+
} satisfies User)
|
|
159
|
+
);
|
|
32
160
|
}
|
|
33
161
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a standardized error response structure for API endpoints
|
|
3
|
+
* @interface
|
|
4
|
+
* @property {number} status - HTTP status code
|
|
5
|
+
* @property {string} [name] - Error name/type
|
|
6
|
+
* @property {string} message - Human-readable error message
|
|
7
|
+
* @property {string} [stack] - Error stack trace (development only)
|
|
8
|
+
* @property {string} [details] - Additional error details
|
|
9
|
+
*/
|
|
10
|
+
export interface ResponseError {
|
|
11
|
+
status: number;
|
|
12
|
+
name?: string;
|
|
13
|
+
message: string;
|
|
14
|
+
stack?: string;
|
|
15
|
+
details?: string;
|
|
16
|
+
}
|
package/src/utilities.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Response } from "express";
|
|
2
|
+
import { ResponseError } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sends a standardized error response
|
|
6
|
+
* @param {Response} res - Express response object
|
|
7
|
+
* @param {ResponseError} error - Error details object
|
|
8
|
+
*/
|
|
9
|
+
export function sendError(res: Response, error: ResponseError): void {
|
|
10
|
+
res.status(error.status).json(error);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parses and validates ID parameter from string to number
|
|
15
|
+
* @param {string} input - Input string to parse
|
|
16
|
+
* @returns {number|ResponseError} Parsed number or error object
|
|
17
|
+
*/
|
|
18
|
+
export function tryParseId(input: string): number | ResponseError {
|
|
19
|
+
try {
|
|
20
|
+
return parseInt(input) satisfies number;
|
|
21
|
+
} catch {
|
|
22
|
+
return {
|
|
23
|
+
status: 400,
|
|
24
|
+
message: "wrong input",
|
|
25
|
+
details: "The id parameter must be an integer number.",
|
|
26
|
+
} satisfies ResponseError;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Checks if the provided object is a ResponseError.
|
|
32
|
+
*
|
|
33
|
+
* A ResponseError is an object that contains at least the properties:
|
|
34
|
+
* - message: string
|
|
35
|
+
* - status: number
|
|
36
|
+
*
|
|
37
|
+
* @param obj - The object to check.
|
|
38
|
+
* @returns true if the object is a ResponseError, false otherwise.
|
|
39
|
+
*/
|
|
40
|
+
export function isResponseError(obj: unknown): boolean {
|
|
41
|
+
return (
|
|
42
|
+
obj != null &&
|
|
43
|
+
typeof obj === "object" &&
|
|
44
|
+
"message" in obj &&
|
|
45
|
+
"status" in obj
|
|
46
|
+
);
|
|
47
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
3
|
+
"target": "ES2023",
|
|
4
4
|
"module": "commonjs",
|
|
5
5
|
"outDir": "./dist",
|
|
6
6
|
"strict": true,
|
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
"skipLibCheck": true,
|
|
9
9
|
"forceConsistentCasingInFileNames": true,
|
|
10
10
|
"experimentalDecorators": true,
|
|
11
|
-
"emitDecoratorMetadata": true
|
|
11
|
+
"emitDecoratorMetadata": true,
|
|
12
|
+
"baseUrl": ".",
|
|
13
|
+
"paths": {
|
|
14
|
+
"@pkg/*": ["src/packages/*"],
|
|
15
|
+
"@r/*": ["src/routers/*"],
|
|
16
|
+
"@/*": ["src/*"]
|
|
17
|
+
}
|
|
12
18
|
},
|
|
13
|
-
"include": ["
|
|
19
|
+
"include": ["**/*.ts", "**/*.js"],
|
|
14
20
|
"exclude": ["node_modules"]
|
|
15
21
|
}
|