express-generator-typescript 2.8.1 → 2.9.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 +6 -7
- package/bin/cli.js +91 -32
- package/lib/express-generator-typescript.js +125 -151
- package/lib/{project-files → template}/.prettierignore +2 -4
- package/lib/template/README.md +50 -0
- package/lib/template/bs-config.js +8 -0
- package/lib/{project-files → template}/config/.env.development +4 -6
- package/lib/{project-files → template}/config/.env.production +4 -5
- package/lib/{project-files → template}/config/.env.test +4 -5
- package/lib/{project-files → template}/eslint.config.ts +4 -0
- package/lib/template/gitignore +10 -0
- package/lib/{project-files → template}/package.json +31 -35
- package/lib/{project-files → template}/src/common/constants/HttpStatusCodes.ts +7 -8
- package/lib/template/src/common/constants/Paths.ts +22 -0
- package/lib/template/src/common/constants/env.ts +28 -0
- package/lib/template/src/common/types/utility-types.ts +5 -0
- package/lib/template/src/common/utils/date-utils.ts +29 -0
- package/lib/template/src/common/utils/route-errors.ts +37 -0
- package/lib/template/src/main.ts +24 -0
- package/lib/template/src/models/User.model.ts +95 -0
- package/lib/template/src/models/common/types.ts +10 -0
- package/lib/template/src/public/scripts/http.js +25 -0
- package/lib/template/src/public/scripts/lib/bootstrap.bundle.min.js +4229 -0
- package/lib/template/src/public/scripts/renderUsers.js +90 -0
- package/lib/template/src/public/scripts/users.js +142 -0
- package/lib/template/src/public/stylesheets/lib/bootstrap.min.css +11798 -0
- package/lib/template/src/repos/MockOrm.ts +71 -0
- package/lib/{project-files → template}/src/repos/UserRepo.ts +21 -35
- package/lib/template/src/repos/common/database.json +28 -0
- package/lib/{project-files → template}/src/routes/UserRoutes.ts +15 -18
- package/lib/template/src/routes/apiRouter.ts +28 -0
- package/lib/template/src/routes/common/express-types.ts +9 -0
- package/lib/template/src/routes/common/parseReq.ts +23 -0
- package/lib/{project-files → template}/src/server.ts +23 -18
- package/lib/template/src/services/UserService.ts +66 -0
- package/lib/{project-files → template}/src/views/users.html +13 -6
- package/lib/{project-files → template}/tests/common/comparators.ts +6 -6
- package/lib/template/tests/common/supertest-types.ts +11 -0
- package/lib/template/tests/frontend.test.ts +33 -0
- package/lib/template/tests/support/agent.ts +23 -0
- package/lib/template/tests/users.test.ts +205 -0
- package/lib/{project-files → template}/tsconfig.json +1 -3
- package/lib/{project-files → template}/tsconfig.prod.json +3 -9
- package/lib/{project-files → template}/vitest.config.mts +2 -0
- package/package.json +69 -52
- package/lib/cli.js +0 -33
- package/lib/project-files/README.md +0 -39
- package/lib/project-files/gitignore +0 -4
- package/lib/project-files/src/common/constants/Paths.ts +0 -15
- package/lib/project-files/src/common/constants/env.ts +0 -28
- package/lib/project-files/src/common/types/structure-utils.ts +0 -2
- package/lib/project-files/src/common/utils/number-utils.ts +0 -10
- package/lib/project-files/src/common/utils/route-errors.ts +0 -35
- package/lib/project-files/src/common/utils/validators.ts +0 -14
- package/lib/project-files/src/main.ts +0 -24
- package/lib/project-files/src/models/User.model.ts +0 -72
- package/lib/project-files/src/models/common/types.ts +0 -4
- package/lib/project-files/src/public/stylesheets/lib/bootstrap.min.css +0 -6
- package/lib/project-files/src/repos/MockOrm.ts +0 -63
- package/lib/project-files/src/repos/common/database.json +0 -1
- package/lib/project-files/src/repos/common/database.test.json +0 -1
- package/lib/project-files/src/routes/apiRouter.ts +0 -28
- package/lib/project-files/src/routes/common/express-types.ts +0 -8
- package/lib/project-files/src/routes/common/parseReq.ts +0 -19
- package/lib/project-files/src/services/UserService.ts +0 -64
- package/lib/project-files/tests/common/error-utils.ts +0 -25
- package/lib/project-files/tests/common/supertest-types.ts +0 -15
- package/lib/project-files/tests/support/agent.ts +0 -23
- package/lib/project-files/tests/users.test.ts +0 -131
- /package/lib/{project-files → template}/.prettierrc.json +0 -0
- /package/lib/{project-files → template}/src/public/stylesheets/users.css +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import jsonfile from 'jsonfile';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
import EnvVars, { NodeEnvs } from '@src/common/constants/env';
|
|
6
|
+
import { IUser } from '@src/models/User.model';
|
|
7
|
+
|
|
8
|
+
// ========================================================================= //
|
|
9
|
+
// CONSTANTS //
|
|
10
|
+
// ========================================================================= //
|
|
11
|
+
|
|
12
|
+
const DATABASE_FILE_PATH = path.join(
|
|
13
|
+
__dirname,
|
|
14
|
+
'common',
|
|
15
|
+
EnvVars.NodeEnv === NodeEnvs.TEST ? 'database.test.json' : 'database.json',
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// ========================================================================= //
|
|
19
|
+
// TYPES //
|
|
20
|
+
// ========================================================================= //
|
|
21
|
+
|
|
22
|
+
type Database = {
|
|
23
|
+
users: IUser[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ========================================================================= //
|
|
27
|
+
// FUNCTIONS //
|
|
28
|
+
// ========================================================================= //
|
|
29
|
+
|
|
30
|
+
// NOTE: Every write is a read-modify-write of one JSON file with no locking,
|
|
31
|
+
// so concurrent requests can overwrite each other. Swap this module for a
|
|
32
|
+
// real database before relying on it.
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fetch the json from the file. A missing file is an empty database.
|
|
36
|
+
*/
|
|
37
|
+
async function openDb(): Promise<Database> {
|
|
38
|
+
let db: Partial<Database>;
|
|
39
|
+
try {
|
|
40
|
+
db = (await jsonfile.readFile(DATABASE_FILE_PATH)) as Partial<Database>;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
|
|
43
|
+
db = {};
|
|
44
|
+
}
|
|
45
|
+
return { users: db.users ?? [] };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Update the file, creating its folder if needed.
|
|
50
|
+
*/
|
|
51
|
+
async function saveDb(db: Database): Promise<void> {
|
|
52
|
+
await fs.mkdir(path.dirname(DATABASE_FILE_PATH), { recursive: true });
|
|
53
|
+
return jsonfile.writeFile(DATABASE_FILE_PATH, db, { spaces: 2 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Empty the database
|
|
58
|
+
*/
|
|
59
|
+
function cleanDb(): Promise<void> {
|
|
60
|
+
return saveDb({ users: [] });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ========================================================================= //
|
|
64
|
+
// EXPORT //
|
|
65
|
+
// ========================================================================= //
|
|
66
|
+
|
|
67
|
+
export default {
|
|
68
|
+
openDb,
|
|
69
|
+
saveDb,
|
|
70
|
+
cleanDb,
|
|
71
|
+
} as const;
|
|
@@ -1,29 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import jetid from 'jet-id';
|
|
2
|
+
|
|
3
|
+
import { getISOString } from '@src/common/utils/date-utils';
|
|
2
4
|
import { IUser } from '@src/models/User.model';
|
|
3
5
|
|
|
4
6
|
import orm from './MockOrm';
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get one user.
|
|
12
|
-
*/
|
|
13
|
-
async function getOne(email: string): Promise<IUser | null> {
|
|
14
|
-
const db = await orm.openDb();
|
|
15
|
-
for (const user of db.users) {
|
|
16
|
-
if (user.email === email) {
|
|
17
|
-
return user;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
8
|
+
// ========================================================================= //
|
|
9
|
+
// FUNCTIONS //
|
|
10
|
+
// ========================================================================= //
|
|
22
11
|
|
|
23
12
|
/**
|
|
24
13
|
* See if a user with the given id exists.
|
|
25
14
|
*/
|
|
26
|
-
async function persists(id:
|
|
15
|
+
async function persists(id: string): Promise<boolean> {
|
|
27
16
|
const db = await orm.openDb();
|
|
28
17
|
for (const user of db.users) {
|
|
29
18
|
if (user.id === id) {
|
|
@@ -46,7 +35,6 @@ async function getAll(): Promise<IUser[]> {
|
|
|
46
35
|
*/
|
|
47
36
|
async function add(user: IUser): Promise<void> {
|
|
48
37
|
const db = await orm.openDb();
|
|
49
|
-
user.id = getRandomInt();
|
|
50
38
|
db.users.push(user);
|
|
51
39
|
return orm.saveDb(db);
|
|
52
40
|
}
|
|
@@ -72,7 +60,7 @@ async function update(user: IUser): Promise<void> {
|
|
|
72
60
|
/**
|
|
73
61
|
* Delete one user.
|
|
74
62
|
*/
|
|
75
|
-
async function delete_(id:
|
|
63
|
+
async function delete_(id: string): Promise<void> {
|
|
76
64
|
const db = await orm.openDb();
|
|
77
65
|
for (let i = 0; i < db.users.length; i++) {
|
|
78
66
|
if (db.users[i].id === id) {
|
|
@@ -82,7 +70,7 @@ async function delete_(id: number): Promise<void> {
|
|
|
82
70
|
}
|
|
83
71
|
}
|
|
84
72
|
|
|
85
|
-
//
|
|
73
|
+
// ============================ Unit-tests Only ============================ //
|
|
86
74
|
|
|
87
75
|
/**
|
|
88
76
|
* @testOnly
|
|
@@ -98,29 +86,27 @@ async function deleteAllUsers(): Promise<void> {
|
|
|
98
86
|
/**
|
|
99
87
|
* @testOnly
|
|
100
88
|
*
|
|
101
|
-
* Insert
|
|
102
|
-
* for now.
|
|
89
|
+
* Insert copies of `users` with fresh ids; the inputs are not modified.
|
|
103
90
|
*/
|
|
104
91
|
async function insertMultiple(
|
|
105
92
|
users: IUser[] | readonly IUser[],
|
|
106
93
|
): Promise<IUser[]> {
|
|
107
|
-
const db = await orm.openDb()
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
db.users = [...db.users, ...
|
|
94
|
+
const db = await orm.openDb();
|
|
95
|
+
const inserted = users.map((user) => ({
|
|
96
|
+
...user,
|
|
97
|
+
id: jetid(),
|
|
98
|
+
created: getISOString(),
|
|
99
|
+
}));
|
|
100
|
+
db.users = [...db.users, ...inserted];
|
|
114
101
|
await orm.saveDb(db);
|
|
115
|
-
return
|
|
102
|
+
return inserted;
|
|
116
103
|
}
|
|
117
104
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
105
|
+
// ========================================================================= //
|
|
106
|
+
// EXPORT //
|
|
107
|
+
// ========================================================================= //
|
|
121
108
|
|
|
122
109
|
export default {
|
|
123
|
-
getOne,
|
|
124
110
|
persists,
|
|
125
111
|
getAll,
|
|
126
112
|
add,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"users": [
|
|
3
|
+
{
|
|
4
|
+
"id": "2HF7W6BZV-23RYN-0GY0E-80X19N",
|
|
5
|
+
"name": "Sean Maxwell",
|
|
6
|
+
"email": "smaxwell@example.com",
|
|
7
|
+
"created": "2024-03-22T05:14:36.252Z"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "56N1CSRF5-SG3XG-M7CEY-TE5G45",
|
|
11
|
+
"name": "John Smith",
|
|
12
|
+
"email": "john.smith@example.com",
|
|
13
|
+
"created": "2024-03-22T05:20:55.079Z"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "Q78BTED9D-HBPJQ-GFEYQ-3EVFJF",
|
|
17
|
+
"name": "Gordan Freeman",
|
|
18
|
+
"email": "nova@prospect.com",
|
|
19
|
+
"created": "2024-03-22T05:42:18.895Z"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "NEQXY1EYZ-66K6S-AMGXW-BWKYAY",
|
|
23
|
+
"name": "asdf",
|
|
24
|
+
"email": "asdf",
|
|
25
|
+
"created": "2026-09-25T22:31:30.844Z"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { isNumber } from 'jet-validators';
|
|
2
|
-
import { transform } from 'jet-validators/utils';
|
|
3
|
-
|
|
4
1
|
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
|
|
5
2
|
import User from '@src/models/User.model';
|
|
6
3
|
import UserService from '@src/services/UserService';
|
|
@@ -8,19 +5,19 @@ import UserService from '@src/services/UserService';
|
|
|
8
5
|
import { Req, Res } from './common/express-types';
|
|
9
6
|
import parseReq from './common/parseReq';
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
// ========================================================================= //
|
|
9
|
+
// EXEC //
|
|
10
|
+
// ========================================================================= //
|
|
14
11
|
|
|
15
12
|
const reqValidators = {
|
|
16
|
-
add: parseReq({ user: User.
|
|
13
|
+
add: parseReq({ user: User.isInput }),
|
|
17
14
|
update: parseReq({ user: User.isComplete }),
|
|
18
|
-
delete: parseReq({ id:
|
|
15
|
+
delete: parseReq({ id: User.isId }),
|
|
19
16
|
} as const;
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
// ========================================================================= //
|
|
19
|
+
// FUNCTIONS //
|
|
20
|
+
// ========================================================================= //
|
|
24
21
|
|
|
25
22
|
/**
|
|
26
23
|
* Get all users.
|
|
@@ -39,8 +36,8 @@ async function getAll(_: Req, res: Res) {
|
|
|
39
36
|
*/
|
|
40
37
|
async function add(req: Req, res: Res) {
|
|
41
38
|
const { user } = reqValidators.add(req.body);
|
|
42
|
-
await UserService.addOne(user);
|
|
43
|
-
res.status(HttpStatusCodes.CREATED).
|
|
39
|
+
const created = await UserService.addOne(user);
|
|
40
|
+
res.status(HttpStatusCodes.CREATED).json({ user: created });
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
/**
|
|
@@ -57,17 +54,17 @@ async function update(req: Req, res: Res) {
|
|
|
57
54
|
/**
|
|
58
55
|
* Delete one user.
|
|
59
56
|
*
|
|
60
|
-
* @route DELETE /api/users/delete
|
|
57
|
+
* @route DELETE /api/users/delete?id=:id
|
|
61
58
|
*/
|
|
62
59
|
async function delete_(req: Req, res: Res) {
|
|
63
|
-
const { id } = reqValidators.delete(req.
|
|
60
|
+
const { id } = reqValidators.delete(req.query);
|
|
64
61
|
await UserService.delete(id);
|
|
65
62
|
res.status(HttpStatusCodes.OK).end();
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
// ========================================================================= //
|
|
66
|
+
// EXPORT //
|
|
67
|
+
// ========================================================================= //
|
|
71
68
|
|
|
72
69
|
export default {
|
|
73
70
|
getAll,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
|
|
3
|
+
import Paths from '@src/common/constants/Paths';
|
|
4
|
+
|
|
5
|
+
import UserRoutes from './UserRoutes';
|
|
6
|
+
|
|
7
|
+
// ========================================================================= //
|
|
8
|
+
// EXEC //
|
|
9
|
+
// ========================================================================= //
|
|
10
|
+
|
|
11
|
+
const apiRouter = Router();
|
|
12
|
+
|
|
13
|
+
// ============================ Add `userRouter` =========================== //
|
|
14
|
+
|
|
15
|
+
const userRouter = Router();
|
|
16
|
+
|
|
17
|
+
userRouter.get(Paths.Users.Get._, UserRoutes.getAll);
|
|
18
|
+
userRouter.post(Paths.Users.Add._, UserRoutes.add);
|
|
19
|
+
userRouter.put(Paths.Users.Update._, UserRoutes.update);
|
|
20
|
+
userRouter.delete(Paths.Users.Delete._, UserRoutes.delete);
|
|
21
|
+
|
|
22
|
+
apiRouter.use(Paths.Users._, userRouter);
|
|
23
|
+
|
|
24
|
+
// ========================================================================= //
|
|
25
|
+
// EXPORT //
|
|
26
|
+
// ========================================================================= //
|
|
27
|
+
|
|
28
|
+
export default apiRouter;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
|
|
3
|
+
// ========================================================================= //
|
|
4
|
+
// TYPES //
|
|
5
|
+
// ========================================================================= //
|
|
6
|
+
|
|
7
|
+
// Body is `unknown` until it has been validated with `parseReq`.
|
|
8
|
+
export type Req = Request<Record<string, string>, unknown, unknown>;
|
|
9
|
+
export type Res = Response;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { parseObject, Schema } from 'jet-validators/utils';
|
|
2
|
+
|
|
3
|
+
import { ValidationError } from '@src/common/utils/route-errors';
|
|
4
|
+
|
|
5
|
+
// ========================================================================= //
|
|
6
|
+
// FUNCTIONS //
|
|
7
|
+
// ========================================================================= //
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Build a request parser for `schema` that throws a `ValidationError`
|
|
11
|
+
* (400) listing every failed field.
|
|
12
|
+
*/
|
|
13
|
+
function parseReq<U extends Schema>(schema: U) {
|
|
14
|
+
return parseObject(schema, (errors) => {
|
|
15
|
+
throw new ValidationError(errors);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ========================================================================= //
|
|
20
|
+
// EXPORT //
|
|
21
|
+
// ========================================================================= //
|
|
22
|
+
|
|
23
|
+
export default parseReq;
|
|
@@ -4,19 +4,20 @@ import logger from 'jet-logger';
|
|
|
4
4
|
import morgan from 'morgan';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
|
|
7
|
+
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
|
|
7
8
|
import Paths from '@src/common/constants/Paths';
|
|
8
9
|
import { RouteError } from '@src/common/utils/route-errors';
|
|
9
10
|
import BaseRouter from '@src/routes/apiRouter';
|
|
10
11
|
|
|
11
12
|
import EnvVars, { NodeEnvs } from './common/constants/env';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// ========================================================================= //
|
|
15
|
+
// EXEC //
|
|
16
|
+
// ========================================================================= //
|
|
16
17
|
|
|
17
18
|
const app = express();
|
|
18
19
|
|
|
19
|
-
//
|
|
20
|
+
// =============================== Middleware ============================== //
|
|
20
21
|
|
|
21
22
|
// Basic middleware
|
|
22
23
|
app.use(express.json());
|
|
@@ -35,22 +36,26 @@ if (EnvVars.NodeEnv === NodeEnvs.PRODUCTION) {
|
|
|
35
36
|
// Add APIs, must be after middleware
|
|
36
37
|
app.use(Paths._, BaseRouter);
|
|
37
38
|
|
|
38
|
-
// Add error handler
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
logger.err(err, true);
|
|
42
|
-
}
|
|
39
|
+
// Add error handler. `_next` must stay: Express only treats 4-arg middleware
|
|
40
|
+
// as an error handler.
|
|
41
|
+
app.use((err: Error, _: Request, res: Response, _next: NextFunction) => {
|
|
43
42
|
if (err instanceof RouteError) {
|
|
44
|
-
res
|
|
43
|
+
return res
|
|
44
|
+
.status(err.status)
|
|
45
|
+
.json({ error: err.message, errors: err.errors });
|
|
46
|
+
}
|
|
47
|
+
if (EnvVars.NodeEnv !== NodeEnvs.TEST) {
|
|
48
|
+
logger.err(err, true);
|
|
45
49
|
}
|
|
46
|
-
return
|
|
50
|
+
return res
|
|
51
|
+
.status(HttpStatusCodes.INTERNAL_SERVER_ERROR)
|
|
52
|
+
.json({ error: 'Internal Server Error' });
|
|
47
53
|
});
|
|
48
54
|
|
|
49
|
-
//
|
|
55
|
+
// =========================== Front-end Content =========================== //
|
|
50
56
|
|
|
51
|
-
//
|
|
57
|
+
// Views directory (html)
|
|
52
58
|
const viewsDir = path.join(__dirname, 'views');
|
|
53
|
-
app.set('views', viewsDir);
|
|
54
59
|
|
|
55
60
|
// Set static directory (js and css).
|
|
56
61
|
const staticDir = path.join(__dirname, 'public');
|
|
@@ -61,13 +66,13 @@ app.get('/', (_: Request, res: Response) => {
|
|
|
61
66
|
return res.redirect('/users');
|
|
62
67
|
});
|
|
63
68
|
|
|
64
|
-
//
|
|
69
|
+
// Users page
|
|
65
70
|
app.get('/users', (_: Request, res: Response) => {
|
|
66
71
|
return res.sendFile('users.html', { root: viewsDir });
|
|
67
72
|
});
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
// ========================================================================= //
|
|
75
|
+
// EXPORT //
|
|
76
|
+
// ========================================================================= //
|
|
72
77
|
|
|
73
78
|
export default app;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
|
|
2
|
+
import { RouteError } from '@src/common/utils/route-errors';
|
|
3
|
+
import User, { IUser, IUserInput } from '@src/models/User.model';
|
|
4
|
+
import UserRepo from '@src/repos/UserRepo';
|
|
5
|
+
|
|
6
|
+
// ========================================================================= //
|
|
7
|
+
// CONSTANTS //
|
|
8
|
+
// ========================================================================= //
|
|
9
|
+
|
|
10
|
+
const Errors = {
|
|
11
|
+
USER_NOT_FOUND: 'User not found',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
// ========================================================================= //
|
|
15
|
+
// FUNCTIONS //
|
|
16
|
+
// ========================================================================= //
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get all users.
|
|
20
|
+
*/
|
|
21
|
+
function getAll(): Promise<IUser[]> {
|
|
22
|
+
return UserRepo.getAll();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a user from client input; the id and created date are set here.
|
|
27
|
+
*/
|
|
28
|
+
async function addOne(input: IUserInput): Promise<IUser> {
|
|
29
|
+
const user = User.new(input);
|
|
30
|
+
await UserRepo.add(user);
|
|
31
|
+
return user;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Update one user.
|
|
36
|
+
*/
|
|
37
|
+
async function updateOne(user: IUser): Promise<void> {
|
|
38
|
+
const persists = await UserRepo.persists(user.id);
|
|
39
|
+
if (!persists) {
|
|
40
|
+
throw new RouteError(HttpStatusCodes.NOT_FOUND, Errors.USER_NOT_FOUND);
|
|
41
|
+
}
|
|
42
|
+
return UserRepo.update(user);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Delete a user by their id.
|
|
47
|
+
*/
|
|
48
|
+
async function deleteOne(id: string): Promise<void> {
|
|
49
|
+
const persists = await UserRepo.persists(id);
|
|
50
|
+
if (!persists) {
|
|
51
|
+
throw new RouteError(HttpStatusCodes.NOT_FOUND, Errors.USER_NOT_FOUND);
|
|
52
|
+
}
|
|
53
|
+
return UserRepo.delete(id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ========================================================================= //
|
|
57
|
+
// EXPORT //
|
|
58
|
+
// ========================================================================= //
|
|
59
|
+
|
|
60
|
+
export default {
|
|
61
|
+
Errors,
|
|
62
|
+
getAll,
|
|
63
|
+
addOne,
|
|
64
|
+
updateOne,
|
|
65
|
+
delete: deleteOne,
|
|
66
|
+
} as const;
|
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
<link rel="stylesheet" type="text/css" href="stylesheets/users.css" />
|
|
8
8
|
|
|
9
9
|
<!-- Bootstrap -->
|
|
10
|
-
<link
|
|
11
|
-
|
|
10
|
+
<link
|
|
11
|
+
href="stylesheets/lib/bootstrap.min.css"
|
|
12
|
+
type="text/css"
|
|
13
|
+
rel="stylesheet"
|
|
14
|
+
/>
|
|
15
|
+
<script
|
|
16
|
+
type="application/javascript"
|
|
17
|
+
src="/scripts/lib/bootstrap.bundle.min.js"
|
|
18
|
+
></script>
|
|
12
19
|
</head>
|
|
13
20
|
|
|
14
21
|
<!-- Body -->
|
|
@@ -42,9 +49,9 @@
|
|
|
42
49
|
<div class="column-header">Users:</div>
|
|
43
50
|
<div id="all-users-anchor"></div>
|
|
44
51
|
</div>
|
|
45
|
-
</body>
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
<script src="/scripts/http.js"></script>
|
|
54
|
+
<script src="/scripts/renderUsers.js"></script>
|
|
55
|
+
<script src="/scripts/users.js"></script>
|
|
56
|
+
</body>
|
|
50
57
|
</html>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { IUser } from '@src/models/User.model';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
// ========================================================================= //
|
|
4
|
+
// TYPES //
|
|
5
|
+
// ========================================================================= //
|
|
6
6
|
|
|
7
7
|
type UserArray = IUser[] | readonly IUser[];
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
// ========================================================================= //
|
|
10
|
+
// FUNCTIONS //
|
|
11
|
+
// ========================================================================= //
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Compare to user arrays. Order does not matter and this assumes the email
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ParseError } from 'jet-validators/utils';
|
|
2
|
+
import { Response } from 'supertest';
|
|
3
|
+
|
|
4
|
+
// ========================================================================= //
|
|
5
|
+
// TYPES //
|
|
6
|
+
// ========================================================================= //
|
|
7
|
+
|
|
8
|
+
// Use generics to add properties to 'body'
|
|
9
|
+
export interface TestRes<T = object> extends Omit<Response, 'body'> {
|
|
10
|
+
body: T & { error?: string; errors?: ParseError[] };
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
|
|
2
|
+
|
|
3
|
+
import { agent } from './support/agent';
|
|
4
|
+
|
|
5
|
+
// ========================================================================= //
|
|
6
|
+
// TESTS //
|
|
7
|
+
// ========================================================================= //
|
|
8
|
+
|
|
9
|
+
describe('Front-end content', () => {
|
|
10
|
+
it('should redirect "/" to the users page.', async () => {
|
|
11
|
+
const res = await agent.get('/');
|
|
12
|
+
expect(res.status).toBe(HttpStatusCodes.FOUND);
|
|
13
|
+
expect(res.headers.location).toBe('/users');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should serve the users page.', async () => {
|
|
17
|
+
const res = await agent.get('/users');
|
|
18
|
+
expect(res.status).toBe(HttpStatusCodes.OK);
|
|
19
|
+
expect(res.headers['content-type']).toMatch(/html/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it.each([
|
|
23
|
+
'/scripts/http.js',
|
|
24
|
+
'/scripts/renderUsers.js',
|
|
25
|
+
'/scripts/users.js',
|
|
26
|
+
'/scripts/lib/bootstrap.bundle.min.js',
|
|
27
|
+
'/stylesheets/users.css',
|
|
28
|
+
'/stylesheets/lib/bootstrap.min.css',
|
|
29
|
+
])('should serve the static asset "%s".', async (asset) => {
|
|
30
|
+
const res = await agent.get(asset);
|
|
31
|
+
expect(res.status).toBe(HttpStatusCodes.OK);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import supertest, { Test } from 'supertest';
|
|
2
|
+
import TestAgent from 'supertest/lib/agent';
|
|
3
|
+
import { beforeAll } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import MockOrm from '@src/repos/MockOrm';
|
|
6
|
+
import app from '@src/server';
|
|
7
|
+
|
|
8
|
+
// ========================================================================= //
|
|
9
|
+
// EXEC //
|
|
10
|
+
// ========================================================================= //
|
|
11
|
+
|
|
12
|
+
let agent: TestAgent<Test>;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
agent = supertest.agent(app);
|
|
16
|
+
await MockOrm.cleanDb();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// ========================================================================= //
|
|
20
|
+
// EXPORT //
|
|
21
|
+
// ========================================================================= //
|
|
22
|
+
|
|
23
|
+
export { agent };
|