codeweaver 2.0.0 → 2.2.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 +11 -3
- 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 +19 -7
- package/src/routers/orders/dto/order.dto.ts +54 -30
- package/src/routers/orders/index.router.ts +12 -7
- package/src/routers/orders/order.controller.ts +87 -79
- package/src/routers/products/dto/product.dto.ts +86 -31
- package/src/routers/products/index.router.ts +13 -6
- package/src/routers/products/product.controller.ts +109 -120
- package/src/routers/users/dto/user.dto.ts +13 -19
- package/src/routers/users/index.router.ts +7 -4
- package/src/routers/users/user.controller.ts +72 -98
- 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/container.ts +159 -0
- package/src/utilities/conversion.ts +158 -0
- package/src/utilities/error-handling.ts +98 -26
- package/src/utilities/logger/base-logger.interface.ts +11 -0
- package/src/utilities/logger/logger.config.ts +95 -0
- package/src/utilities/logger/logger.service.ts +122 -0
- package/src/utilities/logger/winston-logger.service.ts +16 -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
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
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 "@/utilities/container";
|
|
4
5
|
|
|
5
6
|
const router = Router();
|
|
6
|
-
const productController =
|
|
7
|
+
const productController = resolve(ProductController);
|
|
7
8
|
|
|
8
9
|
// CRUD Routes
|
|
9
10
|
|
|
@@ -63,8 +64,11 @@ const productController = new ProductController();
|
|
|
63
64
|
router.post(
|
|
64
65
|
"/",
|
|
65
66
|
asyncHandler(async (req: Request, res: Response) => {
|
|
66
|
-
const product = await productController.
|
|
67
|
-
|
|
67
|
+
const product = await productController.validateProductCreationDto(
|
|
68
|
+
req.body
|
|
69
|
+
);
|
|
70
|
+
await productController.create(product);
|
|
71
|
+
res.status(201).send();
|
|
68
72
|
})
|
|
69
73
|
);
|
|
70
74
|
|
|
@@ -103,7 +107,8 @@ router.get(
|
|
|
103
107
|
router.get(
|
|
104
108
|
"/:id",
|
|
105
109
|
asyncHandler(async (req: Request, res: Response) => {
|
|
106
|
-
const
|
|
110
|
+
const id = await productController.validateId(req.params.id);
|
|
111
|
+
const product = await productController.get(id);
|
|
107
112
|
res.json(product);
|
|
108
113
|
})
|
|
109
114
|
);
|
|
@@ -164,7 +169,8 @@ router.get(
|
|
|
164
169
|
router.put(
|
|
165
170
|
"/:id",
|
|
166
171
|
asyncHandler(async (req: Request, res: Response) => {
|
|
167
|
-
const
|
|
172
|
+
const id = await productController.validateId(req.params.id);
|
|
173
|
+
const product = await productController.update(id, req.body);
|
|
168
174
|
res.json(product);
|
|
169
175
|
})
|
|
170
176
|
);
|
|
@@ -192,7 +198,8 @@ router.put(
|
|
|
192
198
|
router.delete(
|
|
193
199
|
"/:id",
|
|
194
200
|
asyncHandler(async (req: Request, res: Response) => {
|
|
195
|
-
const
|
|
201
|
+
const id = await productController.validateId(req.params.id);
|
|
202
|
+
const product = await productController.delete(id);
|
|
196
203
|
res.json(product);
|
|
197
204
|
})
|
|
198
205
|
);
|
|
@@ -1,133 +1,121 @@
|
|
|
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";
|
|
21
|
+
import { Injectable } from "@/utilities/container";
|
|
91
22
|
|
|
92
23
|
function exceedHandler() {
|
|
93
24
|
const message = "Too much call in allowed window";
|
|
94
25
|
throw new ResponseError(message, 429);
|
|
95
26
|
}
|
|
96
27
|
|
|
97
|
-
function
|
|
98
|
-
const message = "
|
|
99
|
-
throw new ResponseError(message,
|
|
28
|
+
function invalidInputHandler(e: ResponseError) {
|
|
29
|
+
const message = "Invalid input";
|
|
30
|
+
throw new ResponseError(message, 400, e.message);
|
|
100
31
|
}
|
|
101
32
|
|
|
33
|
+
const productsCache = new MapAsyncCache<ProductDto[]>(config.cacheSize);
|
|
34
|
+
const productCache = new MapAsyncCache<ProductDto>(config.cacheSize);
|
|
35
|
+
|
|
36
|
+
@Injectable()
|
|
102
37
|
/**
|
|
103
38
|
* Controller for handling product-related operations
|
|
104
39
|
* @class ProductController
|
|
105
40
|
* @desc Provides methods for product management including CRUD operations
|
|
106
41
|
*/
|
|
107
42
|
export default class ProductController {
|
|
43
|
+
// constructor(private readonly productService: ProductService) { }
|
|
44
|
+
|
|
45
|
+
@onError({
|
|
46
|
+
func: invalidInputHandler,
|
|
47
|
+
})
|
|
48
|
+
/**
|
|
49
|
+
* Validates a string ID and converts it to a number.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} id - The ID to validate and convert.
|
|
52
|
+
* @returns {number} The numeric value of the provided ID.
|
|
53
|
+
*/
|
|
54
|
+
public async validateId(id: string): Promise<number> {
|
|
55
|
+
return stringToInteger(id);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@onError({
|
|
59
|
+
func: invalidInputHandler,
|
|
60
|
+
})
|
|
61
|
+
/**
|
|
62
|
+
* Validates and creates a new Product from the given DTO.
|
|
63
|
+
*
|
|
64
|
+
* @param {ProductCreationDto} product - The incoming ProductCreationDto to validate and transform.
|
|
65
|
+
* @returns {Product} A fully formed Product object ready for persistence.
|
|
66
|
+
*/
|
|
67
|
+
public async validateProductCreationDto(
|
|
68
|
+
product: ProductCreationDto
|
|
69
|
+
): Promise<Product> {
|
|
70
|
+
const newProduct = await ZodProductCreationDto.parseAsync(product);
|
|
71
|
+
return { ...newProduct, id: products.length + 1 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@onError({
|
|
75
|
+
func: invalidInputHandler,
|
|
76
|
+
})
|
|
77
|
+
/**
|
|
78
|
+
* Validates and creates a new Product from the given DTO.
|
|
79
|
+
*
|
|
80
|
+
* @param {ProductUpdateDto} product - The incoming ProductCreationDto to validate and transform.
|
|
81
|
+
* @returns {Product} A fully formed Product object ready for persistence.
|
|
82
|
+
*/
|
|
83
|
+
public async validateProductUpdateDto(
|
|
84
|
+
product: ProductCreationDto
|
|
85
|
+
): Promise<Product> {
|
|
86
|
+
const productDto = await ZodProductUpdateDto.parseAsync(product);
|
|
87
|
+
let updatedProduct: Product = convert(productDto, ZodProduct);
|
|
88
|
+
return { ...updatedProduct, id: products.length + 1 };
|
|
89
|
+
}
|
|
90
|
+
|
|
108
91
|
@rateLimit({
|
|
109
92
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
110
93
|
allowedCalls: config.rateLimitAllowedCalls,
|
|
111
94
|
exceedHandler,
|
|
112
95
|
})
|
|
113
|
-
@Validate
|
|
114
96
|
/**
|
|
115
97
|
* Creates a new product with validated data
|
|
116
|
-
* @param product - Product creation data validated by Zod schema
|
|
117
|
-
* @returns
|
|
98
|
+
* @param {Product} product - Product creation data validated by Zod schema
|
|
99
|
+
* @returns {Promise<void>}
|
|
100
|
+
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
101
|
+
* @throws {ResponseError} 400 - Invalid input data
|
|
118
102
|
*/
|
|
119
|
-
public async create(
|
|
120
|
-
|
|
121
|
-
): Promise<ProductDto> {
|
|
122
|
-
products.push({
|
|
103
|
+
public async create(product: Product): Promise<void> {
|
|
104
|
+
const newProduct: Product = {
|
|
123
105
|
...product,
|
|
124
106
|
id: products.length + 1,
|
|
125
|
-
}
|
|
107
|
+
};
|
|
126
108
|
|
|
127
|
-
|
|
109
|
+
products.push(newProduct);
|
|
110
|
+
await productCache.set(newProduct.id.toString(), newProduct as ProductDto);
|
|
111
|
+
await productsCache.delete("key");
|
|
128
112
|
}
|
|
129
113
|
|
|
130
|
-
@memoizeAsync(
|
|
114
|
+
@memoizeAsync({
|
|
115
|
+
cache: productsCache,
|
|
116
|
+
keyResolver: () => "key",
|
|
117
|
+
expirationTimeMs: config.memoizeTime,
|
|
118
|
+
})
|
|
131
119
|
@timeout(config.timeout)
|
|
132
120
|
@rateLimit({
|
|
133
121
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
@@ -139,15 +127,13 @@ export default class ProductController {
|
|
|
139
127
|
* @returns List of products with summarized descriptions
|
|
140
128
|
*/
|
|
141
129
|
public async getAll(): Promise<ProductDto[]> {
|
|
142
|
-
return products
|
|
143
|
-
...product,
|
|
144
|
-
description: product.description?.substring(0, 50) + "..." || "",
|
|
145
|
-
}));
|
|
130
|
+
return products as ProductDto[];
|
|
146
131
|
}
|
|
147
132
|
|
|
148
|
-
@memoizeAsync(
|
|
149
|
-
|
|
150
|
-
|
|
133
|
+
@memoizeAsync({
|
|
134
|
+
cache: productCache,
|
|
135
|
+
keyResolver: (id: number) => id.toString(),
|
|
136
|
+
expirationTimeMs: config.memoizeTime,
|
|
151
137
|
})
|
|
152
138
|
@rateLimit({
|
|
153
139
|
timeSpanMs: config.rateLimitTimeSpan,
|
|
@@ -156,14 +142,15 @@ export default class ProductController {
|
|
|
156
142
|
})
|
|
157
143
|
/**
|
|
158
144
|
* Finds a product by its ID
|
|
159
|
-
* @param id - Product ID as string
|
|
145
|
+
* @param {number} id - Product ID as string
|
|
160
146
|
* @returns Product details or error object if not found
|
|
161
147
|
*/
|
|
162
|
-
public async get(id:
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
148
|
+
public async get(id: number): Promise<ProductDto> {
|
|
149
|
+
const product = products.find((product) => product.id === id);
|
|
150
|
+
if (product == null) {
|
|
151
|
+
throw new ResponseError("Product not found");
|
|
152
|
+
}
|
|
153
|
+
return convert(product!, ZodProduct);
|
|
167
154
|
}
|
|
168
155
|
|
|
169
156
|
@rateLimit({
|
|
@@ -171,24 +158,23 @@ export default class ProductController {
|
|
|
171
158
|
allowedCalls: config.rateLimitAllowedCalls,
|
|
172
159
|
exceedHandler,
|
|
173
160
|
})
|
|
174
|
-
@Validate
|
|
175
161
|
/**
|
|
176
162
|
* Updates an existing product
|
|
177
|
-
* @param {
|
|
163
|
+
* @param {number} id - Product ID to update
|
|
178
164
|
* @param {ProductUpdateDto} updateData - Partial product data to update
|
|
179
165
|
* @returns {Promise<Product>} Updated product or error object
|
|
180
166
|
* @throws {ResponseError} 404 - Product not found
|
|
181
167
|
* @throws {ResponseError} 400 - Invalid ID format or update data
|
|
182
168
|
*/
|
|
183
169
|
public async update(
|
|
184
|
-
id:
|
|
185
|
-
|
|
170
|
+
id: number,
|
|
171
|
+
updateData: ProductUpdateDto
|
|
186
172
|
): Promise<ProductDto> {
|
|
187
173
|
const product = await this.get(id);
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
if (product) {
|
|
174
|
+
if (product != null) {
|
|
191
175
|
Object.assign(product, updateData);
|
|
176
|
+
await productCache.set(id.toString(), product);
|
|
177
|
+
await productsCache.delete("key");
|
|
192
178
|
} else {
|
|
193
179
|
throw new ResponseError("Product dose not exist.", 404);
|
|
194
180
|
}
|
|
@@ -203,15 +189,18 @@ export default class ProductController {
|
|
|
203
189
|
})
|
|
204
190
|
/**
|
|
205
191
|
* Deletes a product by ID
|
|
206
|
-
* @param {
|
|
192
|
+
* @param {number} id - Product ID to delete
|
|
207
193
|
* @returns {Promise<Product>} Deleted product or error object
|
|
208
194
|
* @throws {ResponseError} 404 - Product not found
|
|
209
195
|
* @throws {ResponseError} 400 - Invalid ID format
|
|
210
196
|
*/
|
|
211
|
-
public async delete(id:
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
197
|
+
public async delete(id: number): Promise<ProductDto> {
|
|
198
|
+
const index = products.findIndex((product) => product.id === id);
|
|
199
|
+
if (index == -1) {
|
|
200
|
+
throw new ResponseError("Product dose not exist.", 404);
|
|
201
|
+
}
|
|
202
|
+
await productCache.delete(id.toString());
|
|
203
|
+
await productsCache.delete("key");
|
|
215
204
|
return products.splice(index, 1)[0];
|
|
216
205
|
}
|
|
217
206
|
}
|
|
@@ -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
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
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 "@/utilities/container";
|
|
4
5
|
|
|
5
6
|
const router = Router();
|
|
6
|
-
const userController =
|
|
7
|
+
const userController = resolve(UserController);
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @swagger
|
|
@@ -45,8 +46,9 @@ const userController = new UserController();
|
|
|
45
46
|
router.post(
|
|
46
47
|
"/",
|
|
47
48
|
asyncHandler(async (req: Request, res: Response) => {
|
|
48
|
-
const user = await userController.
|
|
49
|
-
|
|
49
|
+
const user = await userController.validateUserCreationDto(req.body);
|
|
50
|
+
await userController.create(user);
|
|
51
|
+
res.status(201).send();
|
|
50
52
|
})
|
|
51
53
|
);
|
|
52
54
|
|
|
@@ -71,7 +73,8 @@ router.post(
|
|
|
71
73
|
router.get(
|
|
72
74
|
"/:id",
|
|
73
75
|
asyncHandler(async (req: Request, res: Response) => {
|
|
74
|
-
const
|
|
76
|
+
const id = await userController.validateId(req.params.id);
|
|
77
|
+
const user = await userController.get(id);
|
|
75
78
|
res.json(user);
|
|
76
79
|
})
|
|
77
80
|
);
|