codeweaver 1.1.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/.vscode/settings.json +3 -0
- package/README.md +114 -181
- package/package.json +12 -7
- package/src/app.ts +2 -112
- package/src/config.ts +33 -64
- package/src/constants.ts +7 -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 +85 -0
- package/src/routers/orders/dto/order.dto.ts +54 -29
- package/src/routers/orders/{index.ts → index.router.ts} +13 -13
- package/src/routers/orders/order.controller.ts +118 -120
- package/src/routers/products/dto/product.dto.ts +86 -30
- package/src/routers/products/{index.ts → index.router.ts} +14 -15
- package/src/routers/products/product.controller.ts +136 -161
- package/src/routers/users/dto/user.dto.ts +14 -18
- package/src/routers/users/{index.ts → index.router.ts} +6 -7
- package/src/routers/users/user.controller.ts +87 -118
- package/src/swagger-options.ts +39 -0
- package/src/utilities/assign.ts +66 -0
- 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 +156 -0
- package/src/utilities/router.ts +0 -0
- package/tsconfig.json +1 -4
- package/tsconfig.paths.json +8 -10
- package/src/packages/ts-zod-decorators/index.ts +0 -3
- package/src/packages/ts-zod-decorators/validate.decorator.ts +0 -20
- package/src/packages/ts-zod-decorators/validator.class.ts +0 -72
- package/src/packages/ts-zod-decorators/zod-input.decorator.ts +0 -12
- package/src/packages/ts-zod-decorators/zod-output.decorator.ts +0 -11
- package/src/types.ts +0 -16
- package/src/utilities.ts +0 -47
- /package/src/routers/{index.ts → index.router.ts} +0 -0
package/src/config.ts
CHANGED
|
@@ -1,48 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* @interface
|
|
13
|
-
* @property {string} title - API title
|
|
14
|
-
* @property {string} version - API version
|
|
15
|
-
* @property {string} description - API description
|
|
16
|
-
*/
|
|
17
|
-
interface Info {
|
|
18
|
-
title: string;
|
|
19
|
-
version: string;
|
|
20
|
-
description: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Swagger definition structure
|
|
25
|
-
* @interface
|
|
26
|
-
* @property {string} openApi - OpenAPI specification version
|
|
27
|
-
* @property {Info} info - API information
|
|
28
|
-
* @property {Server[]} servers - List of server configurations
|
|
29
|
-
*/
|
|
30
|
-
interface SwaggerDefinition {
|
|
31
|
-
openApi: string;
|
|
32
|
-
info: Info;
|
|
33
|
-
servers: Server[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Swagger configuration options
|
|
38
|
-
* @interface
|
|
39
|
-
* @property {SwaggerDefinition} swaggerDefinition - Swagger definition object
|
|
40
|
-
* @property {string[]} apis - Paths to API documentation files
|
|
41
|
-
*/
|
|
42
|
-
interface SwaggerOptions {
|
|
43
|
-
swaggerDefinition: SwaggerDefinition;
|
|
44
|
-
apis: string[];
|
|
45
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
memoizeTime,
|
|
3
|
+
productionEnvironment,
|
|
4
|
+
rateLimitTimeSpan,
|
|
5
|
+
rateLimitAllowedCalls,
|
|
6
|
+
timeout,
|
|
7
|
+
portNumber,
|
|
8
|
+
cacheSize,
|
|
9
|
+
} from "./constants";
|
|
10
|
+
import { SwaggerOptions } from "./swagger-options";
|
|
11
|
+
import { stringToBoolean } from "./utilities/conversion";
|
|
46
12
|
|
|
47
13
|
/**
|
|
48
14
|
* Main application configuration
|
|
@@ -53,14 +19,22 @@ interface SwaggerOptions {
|
|
|
53
19
|
*/
|
|
54
20
|
interface Config {
|
|
55
21
|
devMode: boolean;
|
|
56
|
-
port:
|
|
22
|
+
port: number;
|
|
23
|
+
swagger: boolean;
|
|
57
24
|
swaggerOptions: SwaggerOptions;
|
|
25
|
+
timeout: number;
|
|
26
|
+
rateLimitTimeSpan: number;
|
|
27
|
+
rateLimitAllowedCalls: number;
|
|
28
|
+
memoizeTime: number;
|
|
29
|
+
cacheSize: number;
|
|
58
30
|
}
|
|
59
31
|
|
|
60
|
-
const port = process.env.PORT ||
|
|
61
|
-
|
|
62
|
-
|
|
32
|
+
const port = Number(process.env.PORT) || portNumber;
|
|
33
|
+
|
|
34
|
+
let config: Config = {
|
|
35
|
+
devMode: process.env.NODE_ENV !== productionEnvironment,
|
|
63
36
|
port,
|
|
37
|
+
swagger: stringToBoolean(process.env.SWAGGER || "true"),
|
|
64
38
|
swaggerOptions: {
|
|
65
39
|
swaggerDefinition: {
|
|
66
40
|
openApi: "3.0.0",
|
|
@@ -76,23 +50,18 @@ const config: Config = {
|
|
|
76
50
|
],
|
|
77
51
|
},
|
|
78
52
|
apis: [
|
|
79
|
-
"./src/routers/index.ts",
|
|
80
|
-
"./src/routers/**/*.ts",
|
|
81
|
-
"./src/routers/index.js",
|
|
82
|
-
"./src/routers/**/*.js",
|
|
53
|
+
"./src/routers/index.router.ts",
|
|
54
|
+
"./src/routers/**/*.router.ts",
|
|
55
|
+
"./src/routers/index.router.js",
|
|
56
|
+
"./src/routers/**/*.router.js",
|
|
83
57
|
], // Path to the API docs
|
|
84
58
|
},
|
|
59
|
+
timeout: Number(process.env.TIMEOUT) || timeout,
|
|
60
|
+
rateLimitTimeSpan: Number(process.env.RATE_LIMIT) || rateLimitTimeSpan,
|
|
61
|
+
rateLimitAllowedCalls:
|
|
62
|
+
Number(process.env.RATE_LIMIT) || rateLimitAllowedCalls,
|
|
63
|
+
memoizeTime: Number(process.env.MEMOIZE_TIME) || memoizeTime,
|
|
64
|
+
cacheSize: Number(process.env.CACHE_SIZE) || cacheSize,
|
|
85
65
|
};
|
|
86
66
|
|
|
87
|
-
// Other configurations:
|
|
88
|
-
//
|
|
89
|
-
// config.jwt_key = config.devMode ? "" : "";
|
|
90
|
-
// config.jwt_expiration = config.devMode ? 360000 : 360000;
|
|
91
|
-
// config.dbConnectionString = config.devMode ? `mongoDb url` : `mongoDb url`;
|
|
92
|
-
// config.mongoDebug = config.devMode;
|
|
93
|
-
// config.port = config.devMode ? 3000 : 3000;
|
|
94
|
-
// config.host = config.devMode ? "localhost" : "localhost";
|
|
95
|
-
// config.env = config.devMode ? "development" : "production";
|
|
96
|
-
// config.mongoUrl = config.devMode ? "mongodb://localhost:27017/test" : "mongodb://localhost:27017/test";
|
|
97
|
-
|
|
98
67
|
export default config;
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const timeout = 20000;
|
|
2
|
+
export const rateLimitTimeSpan = 60000;
|
|
3
|
+
export const rateLimitAllowedCalls = 300;
|
|
4
|
+
export const memoizeTime = 1000 * 60 * 60;
|
|
5
|
+
export const cacheSize = 1000;
|
|
6
|
+
export const productionEnvironment = "production";
|
|
7
|
+
export const portNumber = 3000;
|
package/src/db.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { Order } from "@/entities/order.entity";
|
|
2
|
+
import { Product } from "@/entities/product.entity";
|
|
3
|
+
import { User } from "@/entities/user.entity";
|
|
4
|
+
|
|
5
|
+
// Array to store users (as a mock database)
|
|
6
|
+
export const users: User[] = [
|
|
7
|
+
{
|
|
8
|
+
id: 1,
|
|
9
|
+
username: "johndoe",
|
|
10
|
+
email: "johndoe@gmail.com",
|
|
11
|
+
password: "S3cur3P@ssw0rd",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 2,
|
|
15
|
+
username: "janesmith",
|
|
16
|
+
email: "janesmith@yahoo.com",
|
|
17
|
+
password: "P@ssw0rd2024",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 3,
|
|
21
|
+
username: "michael89",
|
|
22
|
+
email: "michael89@hotmail.com",
|
|
23
|
+
password: "M1chael!2024",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 4,
|
|
27
|
+
username: "lisa.wong",
|
|
28
|
+
email: "lisa.wong@example.com",
|
|
29
|
+
password: "L1saW0ng!2024",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 5,
|
|
33
|
+
username: "alex_k",
|
|
34
|
+
email: "alex.k@gmail.com",
|
|
35
|
+
password: "A1ex#Key2024",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 6,
|
|
39
|
+
username: "emilyj",
|
|
40
|
+
email: "emilyj@hotmail.com",
|
|
41
|
+
password: "Em!ly0101",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 7,
|
|
45
|
+
username: "davidparker",
|
|
46
|
+
email: "david.parker@yahoo.com",
|
|
47
|
+
password: "D@v!d2024",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 8,
|
|
51
|
+
username: "sophia_m",
|
|
52
|
+
email: "sophia.m@gmail.com",
|
|
53
|
+
password: "Sophi@2024",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 9,
|
|
57
|
+
username: "chrisw",
|
|
58
|
+
email: "chrisw@outlook.com",
|
|
59
|
+
password: "Chri$Wong21",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 10,
|
|
63
|
+
username: "natalie_b",
|
|
64
|
+
email: "natalie_b@gmail.com",
|
|
65
|
+
password: "N@talie#B2024",
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// Array to store products (as a mock database)
|
|
70
|
+
export const products: Product[] = [
|
|
71
|
+
{
|
|
72
|
+
id: 1,
|
|
73
|
+
name: "ASUS ROG Zephyrus G15",
|
|
74
|
+
price: 45000000,
|
|
75
|
+
description: "Gaming laptop with AMD Ryzen 9 5900HS and RTX 3080 GPU",
|
|
76
|
+
category: "Electronics",
|
|
77
|
+
stock: 15,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 2,
|
|
81
|
+
name: "Sony WH-1000XM5 Wireless Headphones",
|
|
82
|
+
price: 12000000,
|
|
83
|
+
description:
|
|
84
|
+
"Premium noise-canceling over-ear headphones with 30hr battery",
|
|
85
|
+
category: "Electronics",
|
|
86
|
+
stock: 8,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 3,
|
|
90
|
+
name: "LG Smart Inverter Microwave",
|
|
91
|
+
price: 25000000,
|
|
92
|
+
description: "1.7 cu.ft countertop microwave with smart sensor cooking",
|
|
93
|
+
category: "Appliances",
|
|
94
|
+
stock: 5,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 4,
|
|
98
|
+
name: "Trek Marlin 5 Mountain Bike",
|
|
99
|
+
price: 18000000,
|
|
100
|
+
description: "Entry-level mountain bike with aluminum frame and 21 speeds",
|
|
101
|
+
category: "Sports",
|
|
102
|
+
stock: 3,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 5,
|
|
106
|
+
name: "DeLonghi Espresso Machine",
|
|
107
|
+
price: 6500000,
|
|
108
|
+
description: "Compact espresso maker with manual milk frother",
|
|
109
|
+
category: "Kitchen",
|
|
110
|
+
stock: 12,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 6,
|
|
114
|
+
name: "Anker Wireless Charger",
|
|
115
|
+
price: 1200000,
|
|
116
|
+
description: "15W fast wireless charger with anti-slip surface",
|
|
117
|
+
category: "Mobile Accessories",
|
|
118
|
+
stock: 30,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 7,
|
|
122
|
+
name: "Logitech MX Master 3 Mouse",
|
|
123
|
+
price: 4500000,
|
|
124
|
+
description: "Ergonomic wireless mouse with Darkfield tracking",
|
|
125
|
+
category: "Computer Accessories",
|
|
126
|
+
stock: 18,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 8,
|
|
130
|
+
name: "Kindle Paperwhite",
|
|
131
|
+
price: 3800000,
|
|
132
|
+
description: 'Waterproof e-reader with 6.8" 300ppi display',
|
|
133
|
+
category: "Electronics",
|
|
134
|
+
stock: 9,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 9,
|
|
138
|
+
name: "Dyson V11 Vacuum Cleaner",
|
|
139
|
+
price: 32000000,
|
|
140
|
+
description: "Cordless stick vacuum with LCD screen and 60min runtime",
|
|
141
|
+
category: "Home Appliances",
|
|
142
|
+
stock: 7,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
// Array to store orders (as a mock database)
|
|
147
|
+
export const orders: Order[] = [
|
|
148
|
+
{
|
|
149
|
+
id: 1,
|
|
150
|
+
userId: 1,
|
|
151
|
+
products: [
|
|
152
|
+
{ productId: 2, quantity: 1 },
|
|
153
|
+
{ productId: 6, quantity: 2 },
|
|
154
|
+
],
|
|
155
|
+
status: "Delivered",
|
|
156
|
+
total: 14400,
|
|
157
|
+
createdAt: new Date("2024-01-15"),
|
|
158
|
+
deliveredAt: new Date("2024-02-10"),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 2,
|
|
162
|
+
userId: 3,
|
|
163
|
+
products: [
|
|
164
|
+
{ productId: 9, quantity: 1 },
|
|
165
|
+
{ productId: 7, quantity: 1 },
|
|
166
|
+
],
|
|
167
|
+
status: "Processing",
|
|
168
|
+
total: 36500,
|
|
169
|
+
createdAt: new Date("2024-03-20"),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 3,
|
|
173
|
+
userId: 2,
|
|
174
|
+
products: [
|
|
175
|
+
{ productId: 1, quantity: 1 },
|
|
176
|
+
{ productId: 4, quantity: 2 },
|
|
177
|
+
],
|
|
178
|
+
status: "Canceled",
|
|
179
|
+
total: 81000,
|
|
180
|
+
createdAt: new Date("2024-05-01"),
|
|
181
|
+
canceledAt: new Date("2024-05-03"),
|
|
182
|
+
},
|
|
183
|
+
];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for the Order entity.
|
|
5
|
+
* This schema validates orders to ensure data integrity before processing.
|
|
6
|
+
*/
|
|
7
|
+
export const ZodOrder = z.object({
|
|
8
|
+
/** Unique identifier for the order (positive integer). */
|
|
9
|
+
id: z.number().min(1).int(),
|
|
10
|
+
|
|
11
|
+
/** ID of the user who placed the order (positive integer). */
|
|
12
|
+
userId: z.number().min(1).int(),
|
|
13
|
+
|
|
14
|
+
/** Array of ordered products with their quantities. */
|
|
15
|
+
products: z.array(
|
|
16
|
+
z.object({
|
|
17
|
+
/** Product identifier referenced in the catalog */
|
|
18
|
+
productId: z.number().min(1).int(),
|
|
19
|
+
|
|
20
|
+
/** Quantity of the product ordered (at least 1) */
|
|
21
|
+
quantity: z.number().min(1).int(),
|
|
22
|
+
})
|
|
23
|
+
),
|
|
24
|
+
|
|
25
|
+
/** Current status of the order */
|
|
26
|
+
status: z.enum(["Processing", "Delivered", "Canceled"]),
|
|
27
|
+
|
|
28
|
+
/** Total price of the order (minimum value constraint is 1000) */
|
|
29
|
+
total: z.number().min(1000),
|
|
30
|
+
|
|
31
|
+
/** Timestamp when the order was created */
|
|
32
|
+
createdAt: z.date(),
|
|
33
|
+
|
|
34
|
+
/** Optional timestamp when the order was canceled */
|
|
35
|
+
canceledAt: z.date().optional(),
|
|
36
|
+
|
|
37
|
+
/** Optional timestamp when the order was delivered */
|
|
38
|
+
deliveredAt: z.date().optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export type Order = {
|
|
42
|
+
/** Unique identifier for the order */
|
|
43
|
+
id: number;
|
|
44
|
+
|
|
45
|
+
/** User ID who placed the order */
|
|
46
|
+
userId: number;
|
|
47
|
+
|
|
48
|
+
/** List of products in the order with quantities */
|
|
49
|
+
products: {
|
|
50
|
+
productId: number;
|
|
51
|
+
quantity: number;
|
|
52
|
+
}[];
|
|
53
|
+
|
|
54
|
+
/** Order status: Processing, Delivered, or Canceled */
|
|
55
|
+
status: "Processing" | "Delivered" | "Canceled";
|
|
56
|
+
|
|
57
|
+
/** Total price of the order */
|
|
58
|
+
total: number;
|
|
59
|
+
|
|
60
|
+
/** Order creation timestamp */
|
|
61
|
+
createdAt: Date;
|
|
62
|
+
|
|
63
|
+
/** Optional cancellation timestamp (if canceled) */
|
|
64
|
+
canceledAt?: Date | undefined;
|
|
65
|
+
|
|
66
|
+
/** Optional delivery timestamp (if delivered) */
|
|
67
|
+
deliveredAt?: Date | undefined;
|
|
68
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for the Product entity.
|
|
5
|
+
* This schema validates product data to ensure consistency
|
|
6
|
+
* before it is stored or processed.
|
|
7
|
+
*/
|
|
8
|
+
export const ZodProduct = z.object({
|
|
9
|
+
/** Unique identifier for the product. Must be a positive integer. */
|
|
10
|
+
id: z.number().min(1).int(),
|
|
11
|
+
|
|
12
|
+
/** Product name. Minimum length of 2 characters. */
|
|
13
|
+
name: z.string().min(2),
|
|
14
|
+
|
|
15
|
+
/** Product price. Minimum value of 1000 (assuming your currency unit). */
|
|
16
|
+
price: z.number().min(1000),
|
|
17
|
+
|
|
18
|
+
/** Optional product description. Minimum length of 10 characters if provided. */
|
|
19
|
+
description: z.string().min(10).optional(),
|
|
20
|
+
|
|
21
|
+
/** Product category from a predefined list. */
|
|
22
|
+
category: z.enum([
|
|
23
|
+
"Electronics",
|
|
24
|
+
"Appliances",
|
|
25
|
+
"Sports",
|
|
26
|
+
"Kitchen",
|
|
27
|
+
"Mobile Accessories",
|
|
28
|
+
"Computer Accessories",
|
|
29
|
+
"Home Appliances",
|
|
30
|
+
"Books",
|
|
31
|
+
]),
|
|
32
|
+
|
|
33
|
+
/** Stock count in inventory. Non-negative integer. */
|
|
34
|
+
stock: z.number().min(0).int(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* TypeScript type for a Product entity.
|
|
39
|
+
* Mirrors the Zod schema with an optional description.
|
|
40
|
+
*/
|
|
41
|
+
export type Product = {
|
|
42
|
+
/** Unique identifier for the product. */
|
|
43
|
+
id: number;
|
|
44
|
+
|
|
45
|
+
/** Display name of the product. */
|
|
46
|
+
name: string;
|
|
47
|
+
|
|
48
|
+
/** Price of the product in the chosen currency. */
|
|
49
|
+
price: number;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Category of the product.
|
|
53
|
+
* Must be one of the predefined categories:
|
|
54
|
+
* - Electronics, Appliances, Sports, Kitchen, Mobile Accessories,
|
|
55
|
+
* Computer Accessories, Home Appliances, Books
|
|
56
|
+
*/
|
|
57
|
+
category:
|
|
58
|
+
| "Electronics"
|
|
59
|
+
| "Appliances"
|
|
60
|
+
| "Sports"
|
|
61
|
+
| "Kitchen"
|
|
62
|
+
| "Mobile Accessories"
|
|
63
|
+
| "Computer Accessories"
|
|
64
|
+
| "Home Appliances"
|
|
65
|
+
| "Books";
|
|
66
|
+
|
|
67
|
+
/** Current stock level in inventory. Non-negative. */
|
|
68
|
+
stock: number;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Optional product description.
|
|
72
|
+
* Includes more details about the product when provided.
|
|
73
|
+
*/
|
|
74
|
+
description?: string | undefined;
|
|
75
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for the User entity.
|
|
5
|
+
* This schema validates user data to ensure it meets basic integrity requirements
|
|
6
|
+
* before being stored or processed.
|
|
7
|
+
*/
|
|
8
|
+
export const ZodUser = z.object({
|
|
9
|
+
/** Unique identifier for the user. Must be a positive integer. */
|
|
10
|
+
id: z.number().min(1).int(),
|
|
11
|
+
|
|
12
|
+
/** Username chosen by the user. Minimum length of 3 characters. */
|
|
13
|
+
username: z.string().min(3),
|
|
14
|
+
|
|
15
|
+
/** Email address of the user. Must be a valid email format. */
|
|
16
|
+
email: z.email(),
|
|
17
|
+
|
|
18
|
+
/** Password for the user. Minimum length of 6 characters. */
|
|
19
|
+
password: z.string().min(6),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* TypeScript type for a User entity.
|
|
24
|
+
* Mirrors the Zod schema.
|
|
25
|
+
*/
|
|
26
|
+
export type User = {
|
|
27
|
+
/** Unique identifier for the user. */
|
|
28
|
+
id: number;
|
|
29
|
+
|
|
30
|
+
/** Username chosen by the user. */
|
|
31
|
+
username: string;
|
|
32
|
+
|
|
33
|
+
/** Email address of the user. */
|
|
34
|
+
email: string;
|
|
35
|
+
|
|
36
|
+
/** Password for the user. */
|
|
37
|
+
password: string;
|
|
38
|
+
};
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import express, { NextFunction, Request, Response } from "express";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import config from "./config";
|
|
5
|
+
import { ResponseError } from "./utilities/error-handling";
|
|
6
|
+
require("./app");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Recursively loads Express routers from directory
|
|
10
|
+
* @param {string} routerPath - Directory path to scan
|
|
11
|
+
* @param {string} [basePath=""] - Base route path
|
|
12
|
+
*/
|
|
13
|
+
function loadRouters(routerPath: string, basePath: string = "") {
|
|
14
|
+
// Read entries with their type info
|
|
15
|
+
const entries = fs.readdirSync(routerPath, { withFileTypes: true });
|
|
16
|
+
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const fullPath = path.join(routerPath, entry.name);
|
|
19
|
+
|
|
20
|
+
if (entry.isDirectory()) {
|
|
21
|
+
// Recurse into subdirectories
|
|
22
|
+
const subRoutePath = path
|
|
23
|
+
.join(basePath, entry.name)
|
|
24
|
+
.replace(/\\/g, "/")
|
|
25
|
+
.replace(/\/?index$/g, "");
|
|
26
|
+
loadRouters(fullPath, subRoutePath);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Only handle router files
|
|
31
|
+
if (
|
|
32
|
+
!entry.name.endsWith(".router.ts") &&
|
|
33
|
+
!entry.name.endsWith(".router.js")
|
|
34
|
+
) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Build route path safely
|
|
39
|
+
const routePath = path
|
|
40
|
+
.join(basePath, entry.name.replace(/\.router\.[tj]s$/, ""))
|
|
41
|
+
.replace(/\\/g, "/")
|
|
42
|
+
.replace(/\/?index$/g, "");
|
|
43
|
+
|
|
44
|
+
// Optional: skip if the target path would be empty (maps to /)
|
|
45
|
+
const mountPath = "/" + (routePath || "");
|
|
46
|
+
|
|
47
|
+
// Import and mount
|
|
48
|
+
const router = require(fullPath);
|
|
49
|
+
app.use(mountPath, router);
|
|
50
|
+
console.log(`Mounted ${entry.name} at ${mountPath}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const app = express();
|
|
55
|
+
app.use(express.json());
|
|
56
|
+
app.use(express.urlencoded({ extended: true }));
|
|
57
|
+
|
|
58
|
+
// Automatically import all routers from the /src/routers directory
|
|
59
|
+
const routersPath = path.join(__dirname, "/routers");
|
|
60
|
+
loadRouters(routersPath);
|
|
61
|
+
|
|
62
|
+
// Swagger setup
|
|
63
|
+
if (config.swagger) {
|
|
64
|
+
const swaggerJsDoc = require("swagger-jsdoc");
|
|
65
|
+
const swaggerUi = require("swagger-ui-express");
|
|
66
|
+
const swaggerDocs = swaggerJsDoc(config.swaggerOptions);
|
|
67
|
+
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// General error handler
|
|
71
|
+
app.use(
|
|
72
|
+
(err: ResponseError, req: Request, res: Response, next: NextFunction) => {
|
|
73
|
+
res.status(err.status ?? 500).json(err);
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Start the server
|
|
78
|
+
app.listen(config.port, () => {
|
|
79
|
+
console.log(`Server is running on http://localhost:${config.port}`);
|
|
80
|
+
if (config.devMode) {
|
|
81
|
+
console.log(
|
|
82
|
+
`Swagger UI is available at http://localhost:${config.port}/api-docs`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ZodOrder } from "@/entities/order.entity";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @property {number} userId - Associated user ID (min 1)
|
|
8
|
-
* @property {{productId: number, quantity: number}[]} products - Array of ordered products
|
|
9
|
-
* @property {"Processing"|"Delivered"|"Canceled"} status - Order status
|
|
10
|
-
* @property {number} total - Total price (min 1000)
|
|
11
|
-
* @property {Date} createdAt - Creation timestamp
|
|
12
|
-
* @property {Date} [canceledAt] - Cancellation timestamp
|
|
13
|
-
* @property {Date} [deliveredAt] - Delivery timestamp
|
|
4
|
+
* DTO for creating an order.
|
|
5
|
+
* This is derived from the full Order schema by omitting fields
|
|
6
|
+
* that are typically generated by the system (id, status, timestamps).
|
|
14
7
|
*/
|
|
15
|
-
export const ZodOrder = z.object({
|
|
16
|
-
id: z.number().min(1).int(),
|
|
17
|
-
userId: z.number().min(1).int(),
|
|
18
|
-
products: z.array(
|
|
19
|
-
z.object({
|
|
20
|
-
productId: z.number().min(1).int(),
|
|
21
|
-
quantity: z.number().min(1).int(),
|
|
22
|
-
})
|
|
23
|
-
),
|
|
24
|
-
status: z.enum(["Processing", "Delivered", "Canceled"]),
|
|
25
|
-
total: z.number().min(1000),
|
|
26
|
-
createdAt: z.date(),
|
|
27
|
-
canceledAt: z.date().optional(),
|
|
28
|
-
deliveredAt: z.date().optional(),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
8
|
export const ZodOrderCreationDto = ZodOrder.omit({
|
|
32
9
|
id: true,
|
|
33
10
|
status: true,
|
|
@@ -36,5 +13,53 @@ export const ZodOrderCreationDto = ZodOrder.omit({
|
|
|
36
13
|
deliveredAt: true,
|
|
37
14
|
});
|
|
38
15
|
|
|
39
|
-
|
|
40
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Data Transfer Object for creating an order.
|
|
18
|
+
* Represents the minimal data required to create an order.
|
|
19
|
+
*/
|
|
20
|
+
export type OrderCreationDto = {
|
|
21
|
+
/** ID of the user placing the order. */
|
|
22
|
+
userId: number;
|
|
23
|
+
|
|
24
|
+
/** List of products included in the order with their quantities. */
|
|
25
|
+
products: {
|
|
26
|
+
productId: number;
|
|
27
|
+
quantity: number;
|
|
28
|
+
}[];
|
|
29
|
+
|
|
30
|
+
/** Total price for the order (validated by the server/persistence layer). */
|
|
31
|
+
total: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Data Transfer Object for a full Order view.
|
|
36
|
+
* Represents the complete order data as returned by the API/service.
|
|
37
|
+
*/
|
|
38
|
+
export type OrderDto = {
|
|
39
|
+
/** Unique identifier for the order. */
|
|
40
|
+
id: number;
|
|
41
|
+
|
|
42
|
+
/** User ID who placed the order. */
|
|
43
|
+
userId: number;
|
|
44
|
+
|
|
45
|
+
/** List of products in the order with their quantities. */
|
|
46
|
+
products: {
|
|
47
|
+
productId: number;
|
|
48
|
+
quantity: number;
|
|
49
|
+
}[];
|
|
50
|
+
|
|
51
|
+
/** Current status of the order: Processing, Delivered, or Canceled. */
|
|
52
|
+
status: "Processing" | "Delivered" | "Canceled";
|
|
53
|
+
|
|
54
|
+
/** Total price for the order. */
|
|
55
|
+
total: number;
|
|
56
|
+
|
|
57
|
+
/** Timestamp when the order was created. */
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
|
|
60
|
+
/** Optional timestamp when the order was canceled (if canceled). */
|
|
61
|
+
canceledAt?: Date | undefined;
|
|
62
|
+
|
|
63
|
+
/** Optional timestamp when the order was delivered (if delivered). */
|
|
64
|
+
deliveredAt?: Date | undefined;
|
|
65
|
+
};
|