codeweaver 2.0.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.
- package/README.md +84 -144
- package/package.json +6 -3
- package/src/app.ts +3 -0
- package/src/config.ts +11 -50
- package/src/constants.ts +1 -0
- package/src/db.ts +183 -0
- package/src/entities/order.entity.ts +68 -0
- package/src/entities/product.entity.ts +75 -0
- package/src/entities/user.entity.ts +38 -0
- package/src/main.ts +10 -7
- package/src/routers/orders/dto/order.dto.ts +54 -30
- package/src/routers/orders/index.router.ts +10 -6
- package/src/routers/orders/order.controller.ts +88 -77
- package/src/routers/products/dto/product.dto.ts +86 -31
- package/src/routers/products/index.router.ts +11 -5
- package/src/routers/products/product.controller.ts +112 -117
- package/src/routers/users/dto/user.dto.ts +13 -19
- package/src/routers/users/index.router.ts +5 -3
- package/src/routers/users/user.controller.ts +74 -97
- package/src/swagger-options.ts +7 -22
- package/src/utilities/assign.ts +44 -59
- package/src/utilities/cache/memory-cache.ts +74 -0
- package/src/utilities/cache/redis-cache.ts +111 -0
- package/src/utilities/conversion.ts +158 -0
- package/src/utilities/error-handling.ts +62 -26
- package/src/utilities/router.ts +0 -0
- package/src/utilities/types.ts +0 -23
|
@@ -1,37 +1,92 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ZodProduct } from "@/entities/product.entity";
|
|
2
|
+
import z from "zod";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @property {number} id - Unique identifier (min 1)
|
|
7
|
-
* @property {string} name - Product name (min 2 chars)
|
|
8
|
-
* @property {number} price - Product price (min 1000)
|
|
9
|
-
* @property {string} [description] - Optional description (min 10 chars)
|
|
10
|
-
* @property {string} category - Product category from predefined enum
|
|
11
|
-
* @property {number} stock - Available stock quantity (min 0)
|
|
5
|
+
* DTO for creating a Product.
|
|
6
|
+
* Derived from the full Product schema by omitting the system-generated id.
|
|
12
7
|
*/
|
|
13
|
-
export const ZodProduct = z.object({
|
|
14
|
-
id: z.number().min(1).int(),
|
|
15
|
-
name: z.string().min(2),
|
|
16
|
-
price: z.number().min(1000),
|
|
17
|
-
description: z.string().min(10).optional(),
|
|
18
|
-
category: z.enum([
|
|
19
|
-
"Electronics",
|
|
20
|
-
"Appliances",
|
|
21
|
-
"Sports",
|
|
22
|
-
"Kitchen",
|
|
23
|
-
"Mobile Accessories",
|
|
24
|
-
"Computer Accessories",
|
|
25
|
-
"Home Appliances",
|
|
26
|
-
"Books",
|
|
27
|
-
]),
|
|
28
|
-
stock: z.number().min(0).int(),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
8
|
export const ZodProductCreationDto = ZodProduct.omit({ id: true });
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DTO for updating a Product.
|
|
12
|
+
* All fields are optional to support partial updates (PATCH semantics).
|
|
13
|
+
*/
|
|
32
14
|
export const ZodProductUpdateDto = ZodProductCreationDto.partial();
|
|
33
15
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Product categories supported by the system.
|
|
18
|
+
* Centralized to avoid repeating the union type in multiple places.
|
|
19
|
+
*/
|
|
20
|
+
export type ProductCategory =
|
|
21
|
+
| "Electronics"
|
|
22
|
+
| "Appliances"
|
|
23
|
+
| "Sports"
|
|
24
|
+
| "Kitchen"
|
|
25
|
+
| "Mobile Accessories"
|
|
26
|
+
| "Computer Accessories"
|
|
27
|
+
| "Home Appliances"
|
|
28
|
+
| "Books";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Data required to create a Product.
|
|
32
|
+
*/
|
|
33
|
+
export type ProductCreationDto = {
|
|
34
|
+
/** Product name. */
|
|
35
|
+
name: string;
|
|
36
|
+
|
|
37
|
+
/** Product price. */
|
|
38
|
+
price: number;
|
|
39
|
+
|
|
40
|
+
/** Category from the predefined list. */
|
|
41
|
+
category: ProductCategory;
|
|
42
|
+
|
|
43
|
+
/** Stock count in inventory. Non-negative. */
|
|
44
|
+
stock: number;
|
|
45
|
+
|
|
46
|
+
/** Optional product description. */
|
|
47
|
+
description?: string | undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Data for updating a Product.
|
|
52
|
+
* All fields are optional to support partial updates.
|
|
53
|
+
*/
|
|
54
|
+
export type ProductUpdateDto = {
|
|
55
|
+
/** Optional product name. */
|
|
56
|
+
name?: string | undefined;
|
|
57
|
+
|
|
58
|
+
/** Optional product price. */
|
|
59
|
+
price?: number | undefined;
|
|
60
|
+
|
|
61
|
+
/** Optional product description. */
|
|
62
|
+
description?: string | undefined;
|
|
63
|
+
|
|
64
|
+
/** Optional product category. Must be one of the predefined categories if provided. */
|
|
65
|
+
category?: ProductCategory | undefined;
|
|
66
|
+
|
|
67
|
+
/** Optional stock count in inventory. */
|
|
68
|
+
stock?: number | undefined;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Data Transfer Object representing a full Product (as returned by APIs, etc.).
|
|
73
|
+
*/
|
|
74
|
+
export type ProductDto = {
|
|
75
|
+
/** Unique identifier for the product. */
|
|
76
|
+
id: number;
|
|
77
|
+
|
|
78
|
+
/** Product name. */
|
|
79
|
+
name: string;
|
|
80
|
+
|
|
81
|
+
/** Product price. */
|
|
82
|
+
price: number;
|
|
83
|
+
|
|
84
|
+
/** Category of the product. */
|
|
85
|
+
category: ProductCategory;
|
|
86
|
+
|
|
87
|
+
/** Stock count in inventory. */
|
|
88
|
+
stock: number;
|
|
89
|
+
|
|
90
|
+
/** Optional product description. */
|
|
91
|
+
description?: string | undefined;
|
|
92
|
+
};
|
|
@@ -63,8 +63,11 @@ const productController = new ProductController();
|
|
|
63
63
|
router.post(
|
|
64
64
|
"/",
|
|
65
65
|
asyncHandler(async (req: Request, res: Response) => {
|
|
66
|
-
const product = await productController.
|
|
67
|
-
|
|
66
|
+
const product = await productController.validateProductCreationDto(
|
|
67
|
+
req.body
|
|
68
|
+
);
|
|
69
|
+
await productController.create(product);
|
|
70
|
+
res.status(201).send();
|
|
68
71
|
})
|
|
69
72
|
);
|
|
70
73
|
|
|
@@ -103,7 +106,8 @@ router.get(
|
|
|
103
106
|
router.get(
|
|
104
107
|
"/:id",
|
|
105
108
|
asyncHandler(async (req: Request, res: Response) => {
|
|
106
|
-
const
|
|
109
|
+
const id = await productController.validateId(req.params.id);
|
|
110
|
+
const product = await productController.get(id);
|
|
107
111
|
res.json(product);
|
|
108
112
|
})
|
|
109
113
|
);
|
|
@@ -164,7 +168,8 @@ router.get(
|
|
|
164
168
|
router.put(
|
|
165
169
|
"/:id",
|
|
166
170
|
asyncHandler(async (req: Request, res: Response) => {
|
|
167
|
-
const
|
|
171
|
+
const id = await productController.validateId(req.params.id);
|
|
172
|
+
const product = await productController.update(id, req.body);
|
|
168
173
|
res.json(product);
|
|
169
174
|
})
|
|
170
175
|
);
|
|
@@ -192,7 +197,8 @@ router.put(
|
|
|
192
197
|
router.delete(
|
|
193
198
|
"/:id",
|
|
194
199
|
asyncHandler(async (req: Request, res: Response) => {
|
|
195
|
-
const
|
|
200
|
+
const id = await productController.validateId(req.params.id);
|
|
201
|
+
const product = await productController.delete(id);
|
|
196
202
|
res.json(product);
|
|
197
203
|
})
|
|
198
204
|
);
|
|
@@ -1,133 +1,124 @@
|
|
|
1
|
-
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
2
1
|
import {
|
|
3
|
-
|
|
2
|
+
memoizeAsync,
|
|
3
|
+
onError,
|
|
4
|
+
rateLimit,
|
|
5
|
+
timeout,
|
|
6
|
+
before,
|
|
7
|
+
} from "utils-decorators";
|
|
8
|
+
import {
|
|
4
9
|
ProductCreationDto,
|
|
5
10
|
ProductDto,
|
|
6
11
|
ProductUpdateDto,
|
|
7
12
|
ZodProductCreationDto,
|
|
8
13
|
ZodProductUpdateDto,
|
|
9
14
|
} from "./dto/product.dto";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { parseId } from "@/utilities/error-handling";
|
|
15
|
+
import { MapAsyncCache } from "@/utilities/cache/memory-cache";
|
|
16
|
+
import { convert, stringToInteger } from "@/utilities/conversion";
|
|
13
17
|
import config from "@/config";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
id: 1,
|
|
19
|
-
name: "ASUS ROG Zephyrus G15",
|
|
20
|
-
price: 45000000,
|
|
21
|
-
description: "Gaming laptop with AMD Ryzen 9 5900HS and RTX 3080 GPU",
|
|
22
|
-
category: "Electronics",
|
|
23
|
-
stock: 15,
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: 2,
|
|
27
|
-
name: "Sony WH-1000XM5 Wireless Headphones",
|
|
28
|
-
price: 12000000,
|
|
29
|
-
description:
|
|
30
|
-
"Premium noise-canceling over-ear headphones with 30hr battery",
|
|
31
|
-
category: "Electronics",
|
|
32
|
-
stock: 8,
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: 3,
|
|
36
|
-
name: "LG Smart Inverter Microwave",
|
|
37
|
-
price: 25000000,
|
|
38
|
-
description: "1.7 cu.ft countertop microwave with smart sensor cooking",
|
|
39
|
-
category: "Appliances",
|
|
40
|
-
stock: 5,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: 4,
|
|
44
|
-
name: "Trek Marlin 5 Mountain Bike",
|
|
45
|
-
price: 18000000,
|
|
46
|
-
description: "Entry-level mountain bike with aluminum frame and 21 speeds",
|
|
47
|
-
category: "Sports",
|
|
48
|
-
stock: 3,
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
id: 5,
|
|
52
|
-
name: "DeLonghi Espresso Machine",
|
|
53
|
-
price: 6500000,
|
|
54
|
-
description: "Compact espresso maker with manual milk frother",
|
|
55
|
-
category: "Kitchen",
|
|
56
|
-
stock: 12,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: 6,
|
|
60
|
-
name: "Anker Wireless Charger",
|
|
61
|
-
price: 1200000,
|
|
62
|
-
description: "15W fast wireless charger with anti-slip surface",
|
|
63
|
-
category: "Mobile Accessories",
|
|
64
|
-
stock: 30,
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: 7,
|
|
68
|
-
name: "Logitech MX Master 3 Mouse",
|
|
69
|
-
price: 4500000,
|
|
70
|
-
description: "Ergonomic wireless mouse with Darkfield tracking",
|
|
71
|
-
category: "Computer Accessories",
|
|
72
|
-
stock: 18,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: 8,
|
|
76
|
-
name: "Kindle Paperwhite",
|
|
77
|
-
price: 3800000,
|
|
78
|
-
description: 'Waterproof e-reader with 6.8" 300ppi display',
|
|
79
|
-
category: "Electronics",
|
|
80
|
-
stock: 9,
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: 9,
|
|
84
|
-
name: "Dyson V11 Vacuum Cleaner",
|
|
85
|
-
price: 32000000,
|
|
86
|
-
description: "Cordless stick vacuum with LCD screen and 60min runtime",
|
|
87
|
-
category: "Home Appliances",
|
|
88
|
-
stock: 7,
|
|
89
|
-
},
|
|
90
|
-
];
|
|
18
|
+
import { ResponseError } from "@/utilities/error-handling";
|
|
19
|
+
import { products } from "@/db";
|
|
20
|
+
import { Product, ZodProduct } from "@/entities/product.entity";
|
|
91
21
|
|
|
92
22
|
function exceedHandler() {
|
|
93
23
|
const message = "Too much call in allowed window";
|
|
94
24
|
throw new ResponseError(message, 429);
|
|
95
25
|
}
|
|
96
26
|
|
|
97
|
-
function
|
|
27
|
+
function productNotFoundHandler(e: ResponseError) {
|
|
98
28
|
const message = "Product not found.";
|
|
99
29
|
throw new ResponseError(message, 404, e.message);
|
|
100
30
|
}
|
|
101
31
|
|
|
32
|
+
function invalidInputHandler(e: ResponseError) {
|
|
33
|
+
const message = "Invalid input";
|
|
34
|
+
throw new ResponseError(message, 400, e.message);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const productsCache = new MapAsyncCache<ProductDto[]>(config.cacheSize);
|
|
38
|
+
const productCache = new MapAsyncCache<ProductDto>(config.cacheSize);
|
|
39
|
+
|
|
102
40
|
/**
|
|
103
41
|
* Controller for handling product-related operations
|
|
104
42
|
* @class ProductController
|
|
105
43
|
* @desc Provides methods for product management including CRUD operations
|
|
106
44
|
*/
|
|
107
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
|
+
|
|
108
94
|
@rateLimit({
|
|
109
95
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
110
96
|
allowedCalls: config.rateLimitAllowedCalls,
|
|
111
97
|
exceedHandler,
|
|
112
98
|
})
|
|
113
|
-
@Validate
|
|
114
99
|
/**
|
|
115
100
|
* Creates a new product with validated data
|
|
116
|
-
* @param product - Product creation data validated by Zod schema
|
|
117
|
-
* @returns
|
|
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
|
|
118
105
|
*/
|
|
119
|
-
public async create(
|
|
120
|
-
|
|
121
|
-
): Promise<ProductDto> {
|
|
122
|
-
products.push({
|
|
106
|
+
public async create(product: Product): Promise<void> {
|
|
107
|
+
const newProduct: Product = {
|
|
123
108
|
...product,
|
|
124
109
|
id: products.length + 1,
|
|
125
|
-
}
|
|
110
|
+
};
|
|
126
111
|
|
|
127
|
-
|
|
112
|
+
products.push(newProduct);
|
|
113
|
+
await productCache.set(newProduct.id.toString(), newProduct as ProductDto);
|
|
114
|
+
await productsCache.delete("key");
|
|
128
115
|
}
|
|
129
116
|
|
|
130
|
-
@memoizeAsync(
|
|
117
|
+
@memoizeAsync({
|
|
118
|
+
cache: productsCache,
|
|
119
|
+
keyResolver: () => "key",
|
|
120
|
+
expirationTimeMs: config.memoizeTime,
|
|
121
|
+
})
|
|
131
122
|
@timeout(config.timeout)
|
|
132
123
|
@rateLimit({
|
|
133
124
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
@@ -139,15 +130,16 @@ export default class ProductController {
|
|
|
139
130
|
* @returns List of products with summarized descriptions
|
|
140
131
|
*/
|
|
141
132
|
public async getAll(): Promise<ProductDto[]> {
|
|
142
|
-
return products
|
|
143
|
-
...product,
|
|
144
|
-
description: product.description?.substring(0, 50) + "..." || "",
|
|
145
|
-
}));
|
|
133
|
+
return products as ProductDto[];
|
|
146
134
|
}
|
|
147
135
|
|
|
148
|
-
@memoizeAsync(
|
|
136
|
+
@memoizeAsync({
|
|
137
|
+
cache: productCache,
|
|
138
|
+
keyResolver: (id: number) => id.toString(),
|
|
139
|
+
expirationTimeMs: config.memoizeTime,
|
|
140
|
+
})
|
|
149
141
|
@onError({
|
|
150
|
-
func:
|
|
142
|
+
func: productNotFoundHandler,
|
|
151
143
|
})
|
|
152
144
|
@rateLimit({
|
|
153
145
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
@@ -156,14 +148,15 @@ export default class ProductController {
|
|
|
156
148
|
})
|
|
157
149
|
/**
|
|
158
150
|
* Finds a product by its ID
|
|
159
|
-
* @param id - Product ID as string
|
|
151
|
+
* @param {number} id - Product ID as string
|
|
160
152
|
* @returns Product details or error object if not found
|
|
161
153
|
*/
|
|
162
|
-
public async get(id:
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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);
|
|
167
160
|
}
|
|
168
161
|
|
|
169
162
|
@rateLimit({
|
|
@@ -171,24 +164,23 @@ export default class ProductController {
|
|
|
171
164
|
allowedCalls: config.rateLimitAllowedCalls,
|
|
172
165
|
exceedHandler,
|
|
173
166
|
})
|
|
174
|
-
@Validate
|
|
175
167
|
/**
|
|
176
168
|
* Updates an existing product
|
|
177
|
-
* @param {
|
|
169
|
+
* @param {number} id - Product ID to update
|
|
178
170
|
* @param {ProductUpdateDto} updateData - Partial product data to update
|
|
179
171
|
* @returns {Promise<Product>} Updated product or error object
|
|
180
172
|
* @throws {ResponseError} 404 - Product not found
|
|
181
173
|
* @throws {ResponseError} 400 - Invalid ID format or update data
|
|
182
174
|
*/
|
|
183
175
|
public async update(
|
|
184
|
-
id:
|
|
185
|
-
|
|
176
|
+
id: number,
|
|
177
|
+
updateData: ProductUpdateDto
|
|
186
178
|
): Promise<ProductDto> {
|
|
187
179
|
const product = await this.get(id);
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
if (product) {
|
|
180
|
+
if (product != null) {
|
|
191
181
|
Object.assign(product, updateData);
|
|
182
|
+
await productCache.set(id.toString(), product);
|
|
183
|
+
await productsCache.delete("key");
|
|
192
184
|
} else {
|
|
193
185
|
throw new ResponseError("Product dose not exist.", 404);
|
|
194
186
|
}
|
|
@@ -203,15 +195,18 @@ export default class ProductController {
|
|
|
203
195
|
})
|
|
204
196
|
/**
|
|
205
197
|
* Deletes a product by ID
|
|
206
|
-
* @param {
|
|
198
|
+
* @param {number} id - Product ID to delete
|
|
207
199
|
* @returns {Promise<Product>} Deleted product or error object
|
|
208
200
|
* @throws {ResponseError} 404 - Product not found
|
|
209
201
|
* @throws {ResponseError} 400 - Invalid ID format
|
|
210
202
|
*/
|
|
211
|
-
public async delete(id:
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
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");
|
|
215
210
|
return products.splice(index, 1)[0];
|
|
216
211
|
}
|
|
217
212
|
}
|
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import {
|
|
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.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 });
|
|
19
5
|
export const ZodUserDto = ZodUser.omit({ password: true });
|
|
20
6
|
|
|
21
|
-
export type
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
export type UserCreationDto = {
|
|
8
|
+
username: string;
|
|
9
|
+
email: string;
|
|
10
|
+
password: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type UserDto = {
|
|
14
|
+
id: number;
|
|
15
|
+
username: string;
|
|
16
|
+
email: string;
|
|
17
|
+
};
|
|
@@ -45,8 +45,9 @@ const userController = new UserController();
|
|
|
45
45
|
router.post(
|
|
46
46
|
"/",
|
|
47
47
|
asyncHandler(async (req: Request, res: Response) => {
|
|
48
|
-
const user = await userController.
|
|
49
|
-
|
|
48
|
+
const user = await userController.validateUserCreationDto(req.body);
|
|
49
|
+
await userController.create(user);
|
|
50
|
+
res.status(201).send();
|
|
50
51
|
})
|
|
51
52
|
);
|
|
52
53
|
|
|
@@ -71,7 +72,8 @@ router.post(
|
|
|
71
72
|
router.get(
|
|
72
73
|
"/:id",
|
|
73
74
|
asyncHandler(async (req: Request, res: Response) => {
|
|
74
|
-
const
|
|
75
|
+
const id = await userController.validateId(req.params.id);
|
|
76
|
+
const user = await userController.get(id);
|
|
75
77
|
res.json(user);
|
|
76
78
|
})
|
|
77
79
|
);
|