express-generator-typescript 2.7.12 → 2.7.15
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/LICENSE +0 -0
- package/README.md +0 -0
- package/lib/cli.js +0 -0
- package/lib/express-generator-typescript.js +0 -0
- package/lib/project-files/.prettierignore +0 -0
- package/lib/project-files/.prettierrc.json +0 -0
- package/lib/project-files/README.md +3 -7
- package/lib/project-files/config/.env.development +0 -0
- package/lib/project-files/config/.env.production +0 -0
- package/lib/project-files/config/.env.test +0 -0
- package/lib/project-files/eslint.config.ts +0 -0
- package/lib/project-files/gitignore +0 -2
- package/lib/project-files/package.json +11 -8
- package/lib/project-files/src/common/constants/HttpStatusCodes.ts +0 -0
- package/lib/project-files/src/common/constants/Paths.ts +0 -0
- package/lib/project-files/src/common/constants/env.ts +0 -0
- package/lib/project-files/src/common/utils/number-utils.ts +0 -0
- package/lib/project-files/src/common/utils/route-errors.ts +0 -0
- package/lib/project-files/src/common/utils/validators.ts +0 -0
- package/lib/project-files/src/main.ts +2 -2
- package/lib/project-files/src/models/{User.ts → UserModel.ts} +19 -16
- package/lib/project-files/src/models/common/types.ts +4 -0
- package/lib/project-files/src/public/stylesheets/users.css +0 -0
- package/lib/project-files/src/repos/MockOrm.ts +14 -13
- package/lib/project-files/src/repos/UserRepo.ts +11 -13
- package/lib/project-files/src/repos/common/database.json +22 -1
- package/lib/project-files/src/repos/common/database.test.json +1 -22
- package/lib/project-files/src/routes/UserRoutes.ts +7 -7
- package/lib/project-files/src/routes/apiRouter.ts +2 -2
- package/lib/project-files/src/routes/common/express-types.ts +0 -0
- package/lib/project-files/src/routes/common/parseReq.ts +0 -0
- package/lib/project-files/src/server.ts +0 -0
- package/lib/project-files/src/services/UserService.ts +6 -6
- package/lib/project-files/src/views/users.html +0 -0
- package/lib/project-files/tests/common/comparators.ts +4 -4
- package/lib/project-files/tests/common/error-utils.ts +3 -3
- package/lib/project-files/tests/common/supertest-types.ts +2 -2
- package/lib/project-files/tests/support/agent.ts +0 -0
- package/lib/project-files/tests/users.test.ts +9 -9
- package/lib/project-files/tsconfig.json +1 -2
- package/lib/project-files/tsconfig.prod.json +2 -2
- package/lib/project-files/vitest.config.mts +4 -1
- package/package.json +1 -1
- package/lib/project-files/config.ts +0 -20
- package/lib/project-files/scripts/build.ts +0 -23
- package/lib/project-files/scripts/common/utils.ts +0 -93
- package/lib/project-files/src/models/common/IModel.ts +0 -4
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/lib/cli.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -23,19 +23,15 @@ app.use(helmet());
|
|
|
23
23
|
|
|
24
24
|
Remove the existing `node_modules/` folder, `package-lock.json`, and reinstall all library modules.
|
|
25
25
|
|
|
26
|
-
### `npm run dev` or `npm run dev:
|
|
26
|
+
### `npm run dev` or `npm run dev:watch` (hot reloading)
|
|
27
27
|
|
|
28
28
|
Run the server in development mode.<br/>
|
|
29
29
|
|
|
30
30
|
**IMPORTANT** development mode uses `swc` for performance reasons which DOES NOT check for typescript errors. Run `npm run type-check` to check for type errors. NOTE: you should use your IDE to prevent most type errors.
|
|
31
31
|
|
|
32
|
-
### `npm test`
|
|
32
|
+
### `npm test`
|
|
33
33
|
|
|
34
|
-
Run
|
|
35
|
-
|
|
36
|
-
### `npm test -- "name of test file" (i.e. users).`
|
|
37
|
-
|
|
38
|
-
Run a single unit-test.
|
|
34
|
+
Run unit-tests with <a href="https://vitest.dev/guide/">vitest</a>.
|
|
39
35
|
|
|
40
36
|
### `npm run lint`
|
|
41
37
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
"name": "express-typescript-example",
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"build": "
|
|
5
|
+
"build": "npm run lint && tsc --project tsconfig.prod.json",
|
|
6
6
|
"clean-install": "rm -rf ./node_modules && rm -r package-lock.json && npm i",
|
|
7
|
-
"dev": "
|
|
8
|
-
"dev:
|
|
7
|
+
"dev": "DOTENV_CONFIG_PATH=./config/.env.development ts-node ./src/main",
|
|
8
|
+
"dev:watch": "nodemon --exec \"npm run dev\" --watch ./src --ext .ts",
|
|
9
9
|
"lint": "eslint .",
|
|
10
10
|
"format": "prettier --write .",
|
|
11
|
-
"start": "
|
|
11
|
+
"start": "DOTENV_CONFIG_PATH=./config/.env.production node -r dotenv/config -r module-alias/register ./dist/main.js",
|
|
12
12
|
"test": "NODE_ENV=test vitest",
|
|
13
13
|
"type-check": "tsc -b --noEmit"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=16.0.0"
|
|
17
17
|
},
|
|
18
|
+
"_moduleAliases": {
|
|
19
|
+
"@src": "dist"
|
|
20
|
+
},
|
|
18
21
|
"dependencies": {
|
|
19
22
|
"cookie-parser": "^1.4.7",
|
|
20
23
|
"dayjs": "^1.11.19",
|
|
@@ -24,7 +27,7 @@
|
|
|
24
27
|
"jet-env": "^1.1.6",
|
|
25
28
|
"jet-logger": "^2.2.3",
|
|
26
29
|
"jet-paths": "^2.0.1",
|
|
27
|
-
"jet-validators": "2.2.5",
|
|
30
|
+
"jet-validators": "^2.2.5",
|
|
28
31
|
"jsonfile": "^6.2.0",
|
|
29
32
|
"module-alias": "^2.2.3",
|
|
30
33
|
"morgan": "^1.10.1"
|
|
@@ -39,7 +42,7 @@
|
|
|
39
42
|
"@types/jsonfile": "^6.1.4",
|
|
40
43
|
"@types/module-alias": "^2.0.4",
|
|
41
44
|
"@types/morgan": "^1.9.10",
|
|
42
|
-
"@types/node": "^25.0.
|
|
45
|
+
"@types/node": "^25.0.9",
|
|
43
46
|
"@types/supertest": "^6.0.3",
|
|
44
47
|
"eslint": "^9.39.2",
|
|
45
48
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -53,7 +56,7 @@
|
|
|
53
56
|
"ts-node": "^10.9.2",
|
|
54
57
|
"tsconfig-paths": "^4.2.0",
|
|
55
58
|
"typescript": "^5.9.3",
|
|
56
|
-
"typescript-eslint": "^8.
|
|
57
|
-
"vitest": "^4.0.
|
|
59
|
+
"typescript-eslint": "^8.53.0",
|
|
60
|
+
"vitest": "^4.0.17"
|
|
58
61
|
}
|
|
59
62
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -7,7 +7,7 @@ import server from './server';
|
|
|
7
7
|
Constants
|
|
8
8
|
******************************************************************************/
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const SERVER_START_MESSAGE =
|
|
11
11
|
'Express server started on port: ' + EnvVars.Port.toString();
|
|
12
12
|
|
|
13
13
|
/******************************************************************************
|
|
@@ -19,6 +19,6 @@ server.listen(EnvVars.Port, (err) => {
|
|
|
19
19
|
if (!!err) {
|
|
20
20
|
logger.err(err.message);
|
|
21
21
|
} else {
|
|
22
|
-
logger.info(
|
|
22
|
+
logger.info(SERVER_START_MESSAGE);
|
|
23
23
|
}
|
|
24
24
|
});
|
|
@@ -3,44 +3,47 @@ import { parseObject, Schema, testObject } from 'jet-validators/utils';
|
|
|
3
3
|
|
|
4
4
|
import { transformIsDate } from '@src/common/utils/validators';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { Entity } from './common/types';
|
|
7
7
|
|
|
8
8
|
/******************************************************************************
|
|
9
9
|
Constants
|
|
10
10
|
******************************************************************************/
|
|
11
11
|
|
|
12
|
-
const GetDefaults = ():
|
|
12
|
+
const GetDefaults = (): User => ({
|
|
13
13
|
id: 0,
|
|
14
14
|
name: '',
|
|
15
15
|
email: '',
|
|
16
16
|
created: new Date(),
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
const schema: Schema<User> = {
|
|
20
|
+
id: isUnsignedInteger,
|
|
21
|
+
name: isString,
|
|
22
|
+
email: isString,
|
|
23
|
+
created: transformIsDate,
|
|
24
|
+
};
|
|
25
|
+
|
|
19
26
|
/******************************************************************************
|
|
20
27
|
Types
|
|
21
28
|
******************************************************************************/
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
/**
|
|
31
|
+
* @entity users
|
|
32
|
+
*/
|
|
33
|
+
export type User = Entity & {
|
|
24
34
|
name: string;
|
|
25
35
|
email: string;
|
|
26
|
-
}
|
|
36
|
+
};
|
|
27
37
|
|
|
28
38
|
/******************************************************************************
|
|
29
39
|
Setup
|
|
30
40
|
******************************************************************************/
|
|
31
41
|
|
|
32
|
-
const schema: Schema<IUser> = {
|
|
33
|
-
id: isUnsignedInteger,
|
|
34
|
-
name: isString,
|
|
35
|
-
email: isString,
|
|
36
|
-
created: transformIsDate,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
42
|
// Set the "parseUser" function
|
|
40
|
-
const parseUser = parseObject<
|
|
43
|
+
const parseUser = parseObject<User>(schema);
|
|
41
44
|
|
|
42
45
|
// For the APIs make sure the right fields are complete
|
|
43
|
-
const
|
|
46
|
+
const isCompleteUser = testObject<User>({
|
|
44
47
|
...schema,
|
|
45
48
|
name: isNonEmptyString,
|
|
46
49
|
email: isNonEmptyString,
|
|
@@ -53,7 +56,7 @@ const testIsCompleteUser = testObject<IUser>({
|
|
|
53
56
|
/**
|
|
54
57
|
* New user object.
|
|
55
58
|
*/
|
|
56
|
-
function
|
|
59
|
+
function newUser(user?: Partial<User>): User {
|
|
57
60
|
return parseUser({ ...GetDefaults(), ...user }, (errors) => {
|
|
58
61
|
throw new Error('Setup new user failed ' + JSON.stringify(errors, null, 2));
|
|
59
62
|
});
|
|
@@ -64,6 +67,6 @@ function __new__(user?: Partial<IUser>): IUser {
|
|
|
64
67
|
******************************************************************************/
|
|
65
68
|
|
|
66
69
|
export default {
|
|
67
|
-
new:
|
|
68
|
-
isComplete:
|
|
70
|
+
new: newUser,
|
|
71
|
+
isComplete: isCompleteUser,
|
|
69
72
|
} as const;
|
|
File without changes
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import jsonfile from 'jsonfile';
|
|
2
2
|
|
|
3
3
|
import EnvVars, { NodeEnvs } from '@src/common/constants/env';
|
|
4
|
-
import {
|
|
4
|
+
import { User } from '@src/models/UserModel';
|
|
5
5
|
|
|
6
6
|
/******************************************************************************
|
|
7
7
|
Constants
|
|
8
8
|
******************************************************************************/
|
|
9
9
|
|
|
10
|
-
const DATABASE_FILE_PATH =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const DATABASE_FILE_PATH =
|
|
11
|
+
__dirname +
|
|
12
|
+
'/common' +
|
|
13
|
+
(EnvVars.NodeEnv === NodeEnvs.TEST
|
|
14
|
+
? '/database.test.json'
|
|
15
|
+
: '/database.json');
|
|
15
16
|
|
|
16
17
|
/******************************************************************************
|
|
17
18
|
Types
|
|
18
19
|
******************************************************************************/
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
users:
|
|
22
|
-
}
|
|
21
|
+
type Database = {
|
|
22
|
+
users: User[];
|
|
23
|
+
};
|
|
23
24
|
|
|
24
25
|
/******************************************************************************
|
|
25
26
|
Functions
|
|
@@ -28,14 +29,14 @@ interface IDatabase {
|
|
|
28
29
|
/**
|
|
29
30
|
* Fetch the json from the file.
|
|
30
31
|
*/
|
|
31
|
-
function openDb(): Promise<
|
|
32
|
-
return jsonfile.readFile(DATABASE_FILE_PATH) as Promise<
|
|
32
|
+
function openDb(): Promise<Database> {
|
|
33
|
+
return jsonfile.readFile(DATABASE_FILE_PATH) as Promise<Database>;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Update the file.
|
|
37
38
|
*/
|
|
38
|
-
function saveDb(db:
|
|
39
|
+
function saveDb(db: Database): Promise<void> {
|
|
39
40
|
return jsonfile.writeFile(DATABASE_FILE_PATH, db);
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -43,7 +44,7 @@ function saveDb(db: IDatabase): Promise<void> {
|
|
|
43
44
|
* Empty the database
|
|
44
45
|
*/
|
|
45
46
|
function cleanDb(): Promise<void> {
|
|
46
|
-
return jsonfile.writeFile(
|
|
47
|
+
return jsonfile.writeFile(DATABASE_FILE_PATH, {});
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
/******************************************************************************
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getRandomInt } from '@src/common/utils/number-utils';
|
|
2
|
-
import {
|
|
2
|
+
import { User } from '@src/models/UserModel';
|
|
3
3
|
|
|
4
4
|
import orm from './MockOrm';
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ import orm from './MockOrm';
|
|
|
10
10
|
/**
|
|
11
11
|
* Get one user.
|
|
12
12
|
*/
|
|
13
|
-
async function getOne(email: string): Promise<
|
|
13
|
+
async function getOne(email: string): Promise<User | null> {
|
|
14
14
|
const db = await orm.openDb();
|
|
15
15
|
for (const user of db.users) {
|
|
16
16
|
if (user.email === email) {
|
|
@@ -36,7 +36,7 @@ async function persists(id: number): Promise<boolean> {
|
|
|
36
36
|
/**
|
|
37
37
|
* Get all users.
|
|
38
38
|
*/
|
|
39
|
-
async function getAll(): Promise<
|
|
39
|
+
async function getAll(): Promise<User[]> {
|
|
40
40
|
const db = await orm.openDb();
|
|
41
41
|
return db.users;
|
|
42
42
|
}
|
|
@@ -44,7 +44,7 @@ async function getAll(): Promise<IUser[]> {
|
|
|
44
44
|
/**
|
|
45
45
|
* Add one user.
|
|
46
46
|
*/
|
|
47
|
-
async function add(user:
|
|
47
|
+
async function add(user: User): Promise<void> {
|
|
48
48
|
const db = await orm.openDb();
|
|
49
49
|
user.id = getRandomInt();
|
|
50
50
|
db.users.push(user);
|
|
@@ -54,7 +54,7 @@ async function add(user: IUser): Promise<void> {
|
|
|
54
54
|
/**
|
|
55
55
|
* Update a user.
|
|
56
56
|
*/
|
|
57
|
-
async function update(user:
|
|
57
|
+
async function update(user: User): Promise<void> {
|
|
58
58
|
const db = await orm.openDb();
|
|
59
59
|
for (let i = 0; i < db.users.length; i++) {
|
|
60
60
|
if (db.users[i].id === user.id) {
|
|
@@ -85,10 +85,9 @@ async function __delete__(id: number): Promise<void> {
|
|
|
85
85
|
// **** Unit-Tests Only **** //
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
88
|
+
* @testOnly
|
|
89
89
|
*
|
|
90
|
-
*
|
|
91
|
-
* Test-only helper. Do not use in production code.
|
|
90
|
+
* Delete every user record.
|
|
92
91
|
*/
|
|
93
92
|
async function deleteAllUsers(): Promise<void> {
|
|
94
93
|
const db = await orm.openDb();
|
|
@@ -97,15 +96,14 @@ async function deleteAllUsers(): Promise<void> {
|
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
/**
|
|
99
|
+
* @testOnly
|
|
100
|
+
*
|
|
100
101
|
* Insert multiple users. Can't do multiple at once cause using a plain file
|
|
101
102
|
* for now.
|
|
102
|
-
*
|
|
103
|
-
* @internal
|
|
104
|
-
* Test-only helper. Do not use in production code.
|
|
105
103
|
*/
|
|
106
104
|
async function insertMultiple(
|
|
107
|
-
users:
|
|
108
|
-
): Promise<
|
|
105
|
+
users: User[] | readonly User[],
|
|
106
|
+
): Promise<User[]> {
|
|
109
107
|
const db = await orm.openDb(),
|
|
110
108
|
usersF = [...users];
|
|
111
109
|
for (const user of usersF) {
|
|
@@ -1 +1,22 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"users": [
|
|
3
|
+
{
|
|
4
|
+
"id": 366115170645,
|
|
5
|
+
"name": "Sean Maxwell",
|
|
6
|
+
"email": "smaxwell@example.com",
|
|
7
|
+
"created": "2024-03-22T05:14:36.252Z"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": 310946254456,
|
|
11
|
+
"name": "John Smith",
|
|
12
|
+
"email": "john.smith@example.com",
|
|
13
|
+
"created": "2024-03-22T05:20:55.079Z"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": 143027113460,
|
|
17
|
+
"name": "Gordan Freeman",
|
|
18
|
+
"email": "nova@prospect.com",
|
|
19
|
+
"created": "2024-03-22T05:42:18.895Z"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -1,22 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"users": [
|
|
3
|
-
{
|
|
4
|
-
"id": 547317739445,
|
|
5
|
-
"name": "Bill",
|
|
6
|
-
"email": "sean.maxwell@gmail.com",
|
|
7
|
-
"created": "2026-01-10T04:06:20.291Z"
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"id": 332906029656,
|
|
11
|
-
"name": "John Smith",
|
|
12
|
-
"email": "john.smith@gmail.com",
|
|
13
|
-
"created": "2026-01-10T04:06:20.291Z"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"id": 421195709596,
|
|
17
|
-
"name": "Gordan Freeman",
|
|
18
|
-
"email": "gordan.freeman@gmail.com",
|
|
19
|
-
"created": "2026-01-10T04:06:20.291Z"
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
1
|
+
{"users":[{"id":146631637670,"name":"Bill","email":"sean.maxwell@gmail.com","created":"2026-01-26T00:52:08.804Z"},{"id":498481378998,"name":"John Smith","email":"john.smith@gmail.com","created":"2026-01-26T00:52:08.804Z"},{"id":878476468362,"name":"Gordan Freeman","email":"gordan.freeman@gmail.com","created":"2026-01-26T00:52:08.804Z"}]}
|
|
@@ -2,7 +2,7 @@ import { isNumber } from 'jet-validators';
|
|
|
2
2
|
import { transform } from 'jet-validators/utils';
|
|
3
3
|
|
|
4
4
|
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
|
|
5
|
-
import
|
|
5
|
+
import UserModel from '@src/models/UserModel';
|
|
6
6
|
import UserService from '@src/services/UserService';
|
|
7
7
|
|
|
8
8
|
import { IReq, IRes } from './common/express-types';
|
|
@@ -12,9 +12,9 @@ import parseReq from './common/parseReq';
|
|
|
12
12
|
Constants
|
|
13
13
|
******************************************************************************/
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
add: parseReq({ user:
|
|
17
|
-
update: parseReq({ user:
|
|
15
|
+
const reqValidators = {
|
|
16
|
+
add: parseReq({ user: UserModel.isComplete }),
|
|
17
|
+
update: parseReq({ user: UserModel.isComplete }),
|
|
18
18
|
delete: parseReq({ id: transform(Number, isNumber) }),
|
|
19
19
|
} as const;
|
|
20
20
|
|
|
@@ -38,7 +38,7 @@ async function getAll(_: IReq, res: IRes) {
|
|
|
38
38
|
* @route POST /api/users/add
|
|
39
39
|
*/
|
|
40
40
|
async function add(req: IReq, res: IRes) {
|
|
41
|
-
const { user } =
|
|
41
|
+
const { user } = reqValidators.add(req.body);
|
|
42
42
|
await UserService.addOne(user);
|
|
43
43
|
res.status(HttpStatusCodes.CREATED).end();
|
|
44
44
|
}
|
|
@@ -49,7 +49,7 @@ async function add(req: IReq, res: IRes) {
|
|
|
49
49
|
* @route PUT /api/users/update
|
|
50
50
|
*/
|
|
51
51
|
async function update(req: IReq, res: IRes) {
|
|
52
|
-
const { user } =
|
|
52
|
+
const { user } = reqValidators.update(req.body);
|
|
53
53
|
await UserService.updateOne(user);
|
|
54
54
|
res.status(HttpStatusCodes.OK).end();
|
|
55
55
|
}
|
|
@@ -60,7 +60,7 @@ async function update(req: IReq, res: IRes) {
|
|
|
60
60
|
* @route DELETE /api/users/delete/:id
|
|
61
61
|
*/
|
|
62
62
|
async function __delete__(req: IReq, res: IRes) {
|
|
63
|
-
const { id } =
|
|
63
|
+
const { id } = reqValidators.delete(req.params);
|
|
64
64
|
await UserService.delete(id);
|
|
65
65
|
res.status(HttpStatusCodes.OK).end();
|
|
66
66
|
}
|
|
@@ -10,7 +10,7 @@ import UserRoutes from './UserRoutes';
|
|
|
10
10
|
|
|
11
11
|
const apiRouter = Router();
|
|
12
12
|
|
|
13
|
-
// ----------------------- Add UserRouter
|
|
13
|
+
// ----------------------- Add UserRouter --------------------------------- //
|
|
14
14
|
|
|
15
15
|
const userRouter = Router();
|
|
16
16
|
|
|
@@ -22,7 +22,7 @@ userRouter.delete(Paths.Users.Delete, UserRoutes.delete);
|
|
|
22
22
|
apiRouter.use(Paths.Users._, userRouter);
|
|
23
23
|
|
|
24
24
|
/******************************************************************************
|
|
25
|
-
Export
|
|
25
|
+
Export
|
|
26
26
|
******************************************************************************/
|
|
27
27
|
|
|
28
28
|
export default apiRouter;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
|
|
2
2
|
import { RouteError } from '@src/common/utils/route-errors';
|
|
3
|
-
import {
|
|
3
|
+
import { User } from '@src/models/UserModel';
|
|
4
4
|
import UserRepo from '@src/repos/UserRepo';
|
|
5
5
|
|
|
6
6
|
/******************************************************************************
|
|
@@ -18,21 +18,21 @@ const Errors = {
|
|
|
18
18
|
/**
|
|
19
19
|
* Get all users.
|
|
20
20
|
*/
|
|
21
|
-
function getAll(): Promise<
|
|
21
|
+
function getAll(): Promise<User[]> {
|
|
22
22
|
return UserRepo.getAll();
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Add one user.
|
|
27
27
|
*/
|
|
28
|
-
function addOne(user:
|
|
28
|
+
function addOne(user: User): Promise<void> {
|
|
29
29
|
return UserRepo.add(user);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Update one user.
|
|
34
34
|
*/
|
|
35
|
-
async function updateOne(user:
|
|
35
|
+
async function updateOne(user: User): Promise<void> {
|
|
36
36
|
const persists = await UserRepo.persists(user.id);
|
|
37
37
|
if (!persists) {
|
|
38
38
|
throw new RouteError(HttpStatusCodes.NOT_FOUND, Errors.USER_NOT_FOUND);
|
|
@@ -43,7 +43,7 @@ async function updateOne(user: IUser): Promise<void> {
|
|
|
43
43
|
/**
|
|
44
44
|
* Delete a user by their id.
|
|
45
45
|
*/
|
|
46
|
-
async function
|
|
46
|
+
async function deleteOne(id: number): Promise<void> {
|
|
47
47
|
const persists = await UserRepo.persists(id);
|
|
48
48
|
if (!persists) {
|
|
49
49
|
throw new RouteError(HttpStatusCodes.NOT_FOUND, Errors.USER_NOT_FOUND);
|
|
@@ -60,5 +60,5 @@ export default {
|
|
|
60
60
|
getAll,
|
|
61
61
|
addOne,
|
|
62
62
|
updateOne,
|
|
63
|
-
delete:
|
|
63
|
+
delete: deleteOne,
|
|
64
64
|
} as const;
|
|
File without changes
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { User } from '@src/models/UserModel';
|
|
2
2
|
|
|
3
3
|
/******************************************************************************
|
|
4
4
|
Types
|
|
5
5
|
******************************************************************************/
|
|
6
6
|
|
|
7
|
-
type
|
|
7
|
+
type UserArray = User[] | readonly User[];
|
|
8
8
|
|
|
9
9
|
/******************************************************************************
|
|
10
10
|
Functions
|
|
@@ -14,7 +14,7 @@ type TUserArray = IUser[] | readonly IUser[];
|
|
|
14
14
|
* Compare to user arrays. Order does not matter and this assumes the email
|
|
15
15
|
* field is unique
|
|
16
16
|
*/
|
|
17
|
-
export function compareUserArrays(a:
|
|
17
|
+
export function compareUserArrays(a: UserArray, b: UserArray): boolean {
|
|
18
18
|
if (a.length !== b.length) return false;
|
|
19
19
|
const aSorted = sortByEmail(a),
|
|
20
20
|
bSorted = sortByEmail(b);
|
|
@@ -31,7 +31,7 @@ export function compareUserArrays(a: TUserArray, b: TUserArray): boolean {
|
|
|
31
31
|
/**
|
|
32
32
|
* Sort user array by email.
|
|
33
33
|
*/
|
|
34
|
-
function sortByEmail(arr:
|
|
34
|
+
function sortByEmail(arr: UserArray): User[] {
|
|
35
35
|
return [...arr].sort((x, y) => {
|
|
36
36
|
return x.email.localeCompare(y.email);
|
|
37
37
|
});
|
|
@@ -5,7 +5,7 @@ import { ParseError, parseJson } from 'jet-validators/utils';
|
|
|
5
5
|
Types
|
|
6
6
|
******************************************************************************/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
type ValidationError = {
|
|
9
9
|
message: string;
|
|
10
10
|
errors: ParseError[];
|
|
11
11
|
}
|
|
@@ -17,9 +17,9 @@ interface IValidationError {
|
|
|
17
17
|
/**
|
|
18
18
|
* JSON parse a validation error.
|
|
19
19
|
*/
|
|
20
|
-
export function parseValidationError(arg: unknown):
|
|
20
|
+
export function parseValidationError(arg: unknown): ValidationError {
|
|
21
21
|
if (!isString(arg)) {
|
|
22
22
|
throw new Error('Not a string');
|
|
23
23
|
}
|
|
24
|
-
return parseJson<
|
|
24
|
+
return parseJson<ValidationError>(arg);
|
|
25
25
|
}
|
|
@@ -6,10 +6,10 @@ import { Response } from 'supertest';
|
|
|
6
6
|
|
|
7
7
|
// Use generics to add properties to 'body'
|
|
8
8
|
export interface IRes<T = object> extends Omit<Response, 'body'> {
|
|
9
|
-
body: T & { error?: string |
|
|
9
|
+
body: T & { error?: string | ErrorObject };
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
interface
|
|
12
|
+
interface ErrorObject {
|
|
13
13
|
message: string;
|
|
14
14
|
[key: string]: unknown;
|
|
15
15
|
}
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
|
|
2
2
|
import { JetPaths as Paths } from '@src/common/constants/Paths';
|
|
3
3
|
import { ValidationError } from '@src/common/utils/route-errors';
|
|
4
|
-
import
|
|
4
|
+
import UserModel, { User } from '@src/models/UserModel';
|
|
5
5
|
import UserRepo from '@src/repos/UserRepo';
|
|
6
6
|
|
|
7
7
|
import { agent } from './support/agent';
|
|
@@ -15,9 +15,9 @@ import { compareUserArrays } from './common/comparators';
|
|
|
15
15
|
******************************************************************************/
|
|
16
16
|
|
|
17
17
|
const DUMMY_USERS = [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
UserModel.new({ name: 'Sean Maxwell', email: 'sean.maxwell@gmail.com' }),
|
|
19
|
+
UserModel.new({ name: 'John Smith', email: 'john.smith@gmail.com' }),
|
|
20
|
+
UserModel.new({ name: 'Gordan Freeman', email: 'gordan.freeman@gmail.com' }),
|
|
21
21
|
] as const;
|
|
22
22
|
|
|
23
23
|
const { BAD_REQUEST, CREATED, OK, NOT_FOUND } = HttpStatusCodes;
|
|
@@ -30,7 +30,7 @@ const { BAD_REQUEST, CREATED, OK, NOT_FOUND } = HttpStatusCodes;
|
|
|
30
30
|
******************************************************************************/
|
|
31
31
|
|
|
32
32
|
describe('UserRouter', () => {
|
|
33
|
-
let dbUsers:
|
|
33
|
+
let dbUsers: User[] = [];
|
|
34
34
|
|
|
35
35
|
beforeEach(async () => {
|
|
36
36
|
await UserRepo.deleteAllUsers();
|
|
@@ -42,7 +42,7 @@ describe('UserRouter', () => {
|
|
|
42
42
|
'should return a JSON object with all the users and a status code of ' +
|
|
43
43
|
`"${OK}" if the request was successful.`,
|
|
44
44
|
async () => {
|
|
45
|
-
const res: IRes<{ users:
|
|
45
|
+
const res: IRes<{ users: User[] }> = await agent.get(Paths.Users.Get);
|
|
46
46
|
expect(res.status).toBe(OK);
|
|
47
47
|
expect(compareUserArrays(res.body.users, DUMMY_USERS)).toBeTruthy();
|
|
48
48
|
},
|
|
@@ -54,7 +54,7 @@ describe('UserRouter', () => {
|
|
|
54
54
|
`should return a status code of "${CREATED}" if the request was ` +
|
|
55
55
|
'successful.',
|
|
56
56
|
async () => {
|
|
57
|
-
const user =
|
|
57
|
+
const user = UserModel.new({ name: 'a', email: 'a@a.com' }),
|
|
58
58
|
res = await agent.post(Paths.Users.Add).send({ user });
|
|
59
59
|
expect(res.status).toBe(CREATED);
|
|
60
60
|
},
|
|
@@ -87,7 +87,7 @@ describe('UserRouter', () => {
|
|
|
87
87
|
'should return a JSON object with an error message and a status code ' +
|
|
88
88
|
`of "${BAD_REQUEST}" if id is the wrong data type`,
|
|
89
89
|
async () => {
|
|
90
|
-
const user =
|
|
90
|
+
const user = UserModel.new();
|
|
91
91
|
user.id = '5' as unknown as number;
|
|
92
92
|
const res: IRes = await agent.put(Paths.Users.Update).send({ user });
|
|
93
93
|
expect(res.status).toBe(BAD_REQUEST);
|
|
@@ -102,7 +102,7 @@ describe('UserRouter', () => {
|
|
|
102
102
|
`"${UserService.Errors.USER_NOT_FOUND}" and a status code of ` +
|
|
103
103
|
`"${NOT_FOUND}" if the id was not found.`,
|
|
104
104
|
async () => {
|
|
105
|
-
const user =
|
|
105
|
+
const user = UserModel.new({ id: 4, name: 'a', email: 'a@a.com' }),
|
|
106
106
|
res: IRes = await agent.put(Paths.Users.Update).send({ user });
|
|
107
107
|
expect(res.status).toBe(NOT_FOUND);
|
|
108
108
|
expect(res.body.error).toBe(UserService.Errors.USER_NOT_FOUND);
|
|
@@ -16,12 +16,11 @@
|
|
|
16
16
|
},
|
|
17
17
|
"ts-node": {
|
|
18
18
|
"swc": true,
|
|
19
|
-
"require": ["tsconfig-paths/register", "
|
|
19
|
+
"require": ["tsconfig-paths/register", "dotenv/config"],
|
|
20
20
|
},
|
|
21
21
|
"include": [
|
|
22
22
|
"src/**/*.ts",
|
|
23
23
|
"tests/**/*.ts",
|
|
24
|
-
"config.ts",
|
|
25
24
|
"scripts",
|
|
26
25
|
"eslint.config.ts",
|
|
27
26
|
"vitest.config.mts"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"lib": ["ES2020"],
|
|
6
6
|
"strict": true,
|
|
7
7
|
"baseUrl": "./",
|
|
8
|
-
"outDir": "
|
|
8
|
+
"outDir": "dist",
|
|
9
9
|
"esModuleInterop": true,
|
|
10
10
|
"skipLibCheck": true,
|
|
11
11
|
"forceConsistentCasingInFileNames": true,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"sourceMap": false,
|
|
17
17
|
"removeComments": true
|
|
18
18
|
},
|
|
19
|
-
"include": ["src/**/*.ts"
|
|
19
|
+
"include": ["src/**/*.ts"],
|
|
20
20
|
"exclude": [
|
|
21
21
|
"tests",
|
|
22
22
|
"src/public/",
|
|
@@ -5,8 +5,11 @@ const config = defineConfig({
|
|
|
5
5
|
test: {
|
|
6
6
|
globals: true,
|
|
7
7
|
environment: 'node',
|
|
8
|
-
setupFiles: ['config
|
|
8
|
+
setupFiles: ['dotenv/config', './tests/support/agent.ts'],
|
|
9
9
|
isolate: true,
|
|
10
|
+
env: {
|
|
11
|
+
DOTENV_CONFIG_PATH: 'config/.env.test',
|
|
12
|
+
},
|
|
10
13
|
},
|
|
11
14
|
resolve: {
|
|
12
15
|
alias: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-generator-typescript",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.15",
|
|
4
4
|
"description": "Generate new Express applications similar to express-generate which but sets it up to use TypeScript instead",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"express-generator-typescript": "node bin/cli.js",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import dotenv from 'dotenv';
|
|
2
|
-
import moduleAlias from 'module-alias';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
// Check the env
|
|
6
|
-
// eslint-disable-next-line no-process-env
|
|
7
|
-
const NODE_ENV = process.env.NODE_ENV ?? 'development';
|
|
8
|
-
|
|
9
|
-
// Configure "dotenv"
|
|
10
|
-
const result2 = dotenv.config({
|
|
11
|
-
path: path.join(__dirname, `./config/.env.${NODE_ENV}`),
|
|
12
|
-
});
|
|
13
|
-
if (result2.error) {
|
|
14
|
-
throw result2.error;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Configure moduleAlias
|
|
18
|
-
if (__filename.endsWith('js')) {
|
|
19
|
-
moduleAlias.addAlias('@src', __dirname + '/dist');
|
|
20
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import logger from 'jet-logger';
|
|
2
|
-
|
|
3
|
-
import { copy, copyFilesRec, exec, remove } from './common/utils';
|
|
4
|
-
|
|
5
|
-
/******************************************************************************
|
|
6
|
-
Run
|
|
7
|
-
******************************************************************************/
|
|
8
|
-
|
|
9
|
-
(async () => {
|
|
10
|
-
// Remove current build
|
|
11
|
-
await remove('./dist/');
|
|
12
|
-
await exec('npm run lint', '../');
|
|
13
|
-
await exec('tsc --project tsconfig.prod.json', '../');
|
|
14
|
-
// Copy
|
|
15
|
-
await copyFilesRec('./src', './dist', ['.ts']);
|
|
16
|
-
await copy('./temp/config.js', './config.js');
|
|
17
|
-
await copy('./temp/src', './dist');
|
|
18
|
-
await remove('./temp/');
|
|
19
|
-
})().catch((err) => {
|
|
20
|
-
logger.err(err);
|
|
21
|
-
process.exit(1);
|
|
22
|
-
})
|
|
23
|
-
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import childProcess from 'child_process';
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import logger from 'jet-logger';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
/******************************************************************************
|
|
7
|
-
Functions
|
|
8
|
-
*******************************************************************************
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Copy file.
|
|
12
|
-
*/
|
|
13
|
-
export function copy(src: string, dest: string): Promise<void> {
|
|
14
|
-
return new Promise((res, rej) => {
|
|
15
|
-
return fs.copy(src, dest, (err) => {
|
|
16
|
-
return !!err ? rej(err) : res();
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Remove file
|
|
23
|
-
*/
|
|
24
|
-
export function remove(loc: string): Promise<void> {
|
|
25
|
-
return new Promise((res, rej) => {
|
|
26
|
-
return fs.remove(loc, (err) => {
|
|
27
|
-
return !!err ? rej(err) : res();
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Run command-line command.
|
|
34
|
-
*/
|
|
35
|
-
export function exec(cmd: string, loc = './'): Promise<string> {
|
|
36
|
-
// Setup directory
|
|
37
|
-
const callingDir = path.dirname(require.main?.filename ?? './'),
|
|
38
|
-
cwd = path.join(callingDir, loc);
|
|
39
|
-
// Wrap in promise
|
|
40
|
-
return new Promise((res, rej) =>
|
|
41
|
-
childProcess.exec(cmd, { cwd }, (err, stdout, stderr) => {
|
|
42
|
-
if (!!stdout) {
|
|
43
|
-
logger.info(stdout);
|
|
44
|
-
}
|
|
45
|
-
if (!!stderr) {
|
|
46
|
-
logger.err(stderr);
|
|
47
|
-
}
|
|
48
|
-
if (!!err) {
|
|
49
|
-
return rej(err);
|
|
50
|
-
} else if (!!stderr) {
|
|
51
|
-
return rej(new Error(stderr));
|
|
52
|
-
} else {
|
|
53
|
-
return res(stdout);
|
|
54
|
-
}
|
|
55
|
-
}),
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Recursively search for a folder for all files and copy them to the new
|
|
61
|
-
* destination keeping their paths relative to the "src" param.
|
|
62
|
-
*
|
|
63
|
-
* Example:
|
|
64
|
-
* If file "./src/foo/bar.txt" exists then:
|
|
65
|
-
* copyFilesRec('./src', './dest') => ["./dest/foo/bar.txt"]
|
|
66
|
-
*
|
|
67
|
-
* @param excludeExt Exlude a list of file extensions.
|
|
68
|
-
*/
|
|
69
|
-
export async function copyFilesRec(
|
|
70
|
-
src: string,
|
|
71
|
-
dest: string,
|
|
72
|
-
excludeExt?: string[],
|
|
73
|
-
): Promise<void> {
|
|
74
|
-
const copyReqs: Promise<void>[] = [];
|
|
75
|
-
try {
|
|
76
|
-
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
77
|
-
for (const entry of entries) {
|
|
78
|
-
const currPath = src + '/' + entry.name,
|
|
79
|
-
currDest = dest + '/' + entry.name,
|
|
80
|
-
ext = path.extname(entry.name);
|
|
81
|
-
if (entry.isFile() && !excludeExt?.includes(ext)) {
|
|
82
|
-
const req = fs.copy(currPath, currDest);
|
|
83
|
-
copyReqs.push(req);
|
|
84
|
-
} else if (entry.isDirectory()) {
|
|
85
|
-
const req = copyFilesRec(currPath, currDest, excludeExt);
|
|
86
|
-
copyReqs.push(req);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
logger.err(`Error searching in ${src}: ` + String(error));
|
|
91
|
-
}
|
|
92
|
-
await Promise.all(copyReqs);
|
|
93
|
-
}
|