express-generator-typescript 3.0.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/bin/cli.js +1 -20
- package/lib/template/README.md +4 -3
- package/lib/template/package.json +8 -6
- package/lib/template/src/common/classes/route-errors.ts +4 -8
- package/lib/template/src/common/constants/HttpStatusCodes.ts +2 -7
- package/lib/template/src/common/constants/{Paths.ts → Paths/Paths.ts} +16 -9
- package/lib/template/src/common/constants/Paths/helpers.ts +29 -0
- package/lib/template/src/common/constants/Paths/index.ts +1 -0
- package/lib/template/src/common/constants/env.constants.ts +42 -0
- package/lib/template/src/common/utils/{date-utils.ts → date.utils.ts} +10 -12
- package/lib/template/src/entities/User/module.ts +77 -28
- package/lib/template/src/entities/User/types.ts +1 -1
- package/lib/template/src/entities/common/{types.ts → entity-base-types.ts} +1 -1
- package/lib/template/src/main.ts +3 -4
- package/lib/template/src/public/scripts/lib/bootstrap.bundle.min.js +0 -1
- package/lib/template/src/public/scripts/users.js +20 -11
- package/lib/template/src/public/stylesheets/lib/bootstrap.min.css +0 -1
- package/lib/template/src/public/stylesheets/users.css +24 -1
- package/lib/template/src/repos/MockOrm.ts +2 -2
- package/lib/template/src/repos/UserRepo.ts +20 -27
- package/lib/template/src/repos/common/database.json +3 -3
- package/lib/template/src/{routes/apiRouter.ts → routers/api.router.ts} +3 -11
- package/lib/template/src/{routes/UserRoutes.ts → routers/user.router.ts} +38 -47
- package/lib/template/src/server.ts +22 -11
- package/lib/template/src/services/UserService.ts +9 -14
- package/lib/template/src/views/users.html +8 -0
- package/lib/template/tests/support/utils.ts +23 -0
- package/lib/template/tests/users.test.ts +182 -140
- package/package.json +6 -5
- package/lib/template/src/common/constants/env-inv.ts +0 -32
- package/lib/template/src/entities/User/_internal/validators.ts +0 -47
- package/lib/template/src/routes/common/express-types.ts +0 -9
- package/lib/template/src/routes/common/parseReq.ts +0 -23
- package/lib/template/tests/common/comparators.ts +0 -38
- package/lib/template/tests/common/supertest-types.ts +0 -11
- /package/lib/template/src/common/{types/utility-types.ts → utils/validators.ts} +0 -0
|
@@ -1,29 +1,56 @@
|
|
|
1
|
-
import jetid from 'jet-id';
|
|
2
|
-
|
|
3
1
|
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
|
|
4
2
|
import Paths from '@src/common/constants/Paths';
|
|
5
|
-
import { ValidationError } from '@src/common/classes/route-errors';
|
|
6
3
|
import User, { type UserEntity } from '@src/entities/User';
|
|
7
|
-
import UserRepo from '@src/repos/UserRepo';
|
|
4
|
+
import UserRepo, { UserRepoTestOnly } from '@src/repos/UserRepo';
|
|
8
5
|
import UserService from '@src/services/UserService';
|
|
9
6
|
|
|
10
|
-
import { compareUserArrays } from './common/comparators';
|
|
11
|
-
import type { TestRes } from './common/supertest-types';
|
|
12
7
|
import { agent } from './support/agent';
|
|
8
|
+
import {
|
|
9
|
+
extractErrMsg,
|
|
10
|
+
type TypedErrRes,
|
|
11
|
+
type TypedRes,
|
|
12
|
+
} from './support/utils';
|
|
13
13
|
|
|
14
14
|
// ========================================================================= //
|
|
15
15
|
// CONSTANTS //
|
|
16
16
|
// ========================================================================= //
|
|
17
17
|
|
|
18
|
+
// Shorthand the `Paths`
|
|
19
|
+
const P = {
|
|
20
|
+
Get: {
|
|
21
|
+
CALL: Paths.Users.Get.FULL_PATH,
|
|
22
|
+
DISPLAY: Paths.Users.Get.DISPLAY,
|
|
23
|
+
},
|
|
24
|
+
Add: {
|
|
25
|
+
CALL: Paths.Users.Add.FULL_PATH,
|
|
26
|
+
DISPLAY: Paths.Users.Add.DISPLAY,
|
|
27
|
+
},
|
|
28
|
+
Update: {
|
|
29
|
+
CALL: Paths.Users.Update.FULL_PATH,
|
|
30
|
+
DISPLAY: Paths.Users.Update.DISPLAY,
|
|
31
|
+
},
|
|
32
|
+
Delete: {
|
|
33
|
+
CALL: Paths.Users.Delete.format,
|
|
34
|
+
DISPLAY: Paths.Users.Delete.DISPLAY,
|
|
35
|
+
FULL_PATH: Paths.Users.Delete.FULL_PATH,
|
|
36
|
+
},
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
// Errors
|
|
40
|
+
const Errors = {
|
|
41
|
+
...UserService.Errors,
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
// Status codes
|
|
45
|
+
const { BAD_REQUEST, CREATED, INTERNAL_SERVER_ERROR, OK, NOT_FOUND } =
|
|
46
|
+
HttpStatusCodes;
|
|
47
|
+
|
|
18
48
|
const DUMMY_USERS = [
|
|
19
49
|
User.of('Sean Maxwell', 'sean.maxwell@gmail.com'),
|
|
20
50
|
User.of('John Smith', 'john.smith@gmail.com'),
|
|
21
51
|
User.of('Gordan Freeman', 'gordan.freeman@gmail.com'),
|
|
22
52
|
] as const;
|
|
23
53
|
|
|
24
|
-
const { BAD_REQUEST, CREATED, INTERNAL_SERVER_ERROR, OK, NOT_FOUND } =
|
|
25
|
-
HttpStatusCodes;
|
|
26
|
-
|
|
27
54
|
// ========================================================================= //
|
|
28
55
|
// TESTS //
|
|
29
56
|
// ========================================================================= //
|
|
@@ -35,171 +62,186 @@ describe('UserRouter', () => {
|
|
|
35
62
|
let dbUsers: UserEntity[] = [];
|
|
36
63
|
|
|
37
64
|
beforeEach(async () => {
|
|
38
|
-
await
|
|
39
|
-
dbUsers = await
|
|
65
|
+
await UserRepoTestOnly.deleteAllUsers();
|
|
66
|
+
dbUsers = await UserRepoTestOnly.insertMultiple(DUMMY_USERS);
|
|
40
67
|
});
|
|
41
68
|
|
|
42
69
|
afterEach(() => {
|
|
43
70
|
vi.restoreAllMocks();
|
|
44
71
|
});
|
|
45
72
|
|
|
46
|
-
//
|
|
47
|
-
describe(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const res: TestRes<{ users: UserEntity[] }> = await agent.get(
|
|
53
|
-
Paths.Users.Get(),
|
|
73
|
+
// --- `Get` ---
|
|
74
|
+
describe(P.Get.DISPLAY, () => {
|
|
75
|
+
describe('on success', () => {
|
|
76
|
+
it(`returns all users as JSON with status "${OK}"`, async () => {
|
|
77
|
+
const res: TypedRes<{ users: UserEntity[] }> = await agent.get(
|
|
78
|
+
P.Get.CALL,
|
|
54
79
|
);
|
|
55
80
|
expect(res.status).toBe(OK);
|
|
56
|
-
expect(
|
|
57
|
-
}
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
67
|
-
const res: TestRes = await agent.get(Paths.Users.Get());
|
|
81
|
+
expect(res.body.users).toStrictEqual(DUMMY_USERS);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('when an unexpected error is thrown', () => {
|
|
86
|
+
it(`returns status "${INTERNAL_SERVER_ERROR}"`, async () => {
|
|
87
|
+
const error = new Error('boom');
|
|
88
|
+
const getAllSpy = vi.spyOn(UserService, 'getAll');
|
|
89
|
+
getAllSpy.mockRejectedValueOnce(error);
|
|
90
|
+
const res: TypedErrRes = await agent.get(P.Get.CALL);
|
|
68
91
|
expect(res.status).toBe(INTERNAL_SERVER_ERROR);
|
|
69
92
|
expect(res.body.error).toBe('Internal Server Error');
|
|
70
|
-
}
|
|
71
|
-
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
72
95
|
});
|
|
73
96
|
|
|
74
|
-
//
|
|
75
|
-
describe(
|
|
76
|
-
|
|
77
|
-
`
|
|
78
|
-
'with a server-generated id if the request was successful.',
|
|
79
|
-
async () => {
|
|
97
|
+
// --- `Add` ---
|
|
98
|
+
describe(P.Add.DISPLAY, () => {
|
|
99
|
+
describe('on success', () => {
|
|
100
|
+
it(`persists the user and returns status "${CREATED}"`, async () => {
|
|
80
101
|
// Same payload shape the front-end sends
|
|
81
102
|
const input = { name: 'a', email: 'a@a.com' };
|
|
82
|
-
const res:
|
|
83
|
-
.post(
|
|
103
|
+
const res: TypedRes<{ user: UserEntity }> = await agent
|
|
104
|
+
.post(P.Add.CALL)
|
|
84
105
|
.send({ user: input });
|
|
85
106
|
expect(res.status).toBe(CREATED);
|
|
86
|
-
|
|
107
|
+
const isId = User.is.id(res.body.user.id);
|
|
108
|
+
expect(isId).toBe(true);
|
|
87
109
|
const all = await UserRepo.getAll();
|
|
88
110
|
expect(all).toHaveLength(DUMMY_USERS.length + 1);
|
|
89
111
|
expect(all).toContainEqual(res.body.user);
|
|
90
|
-
}
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
'
|
|
95
|
-
|
|
96
|
-
async () => {
|
|
97
|
-
const res: TestRes = await agent
|
|
98
|
-
.post(Paths.Users.Add())
|
|
99
|
-
.send({ user: null });
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('when the user param is missing', () => {
|
|
116
|
+
it('returns a validation error', async () => {
|
|
117
|
+
const res = await agent.post(P.Add.CALL).send({ user: null });
|
|
100
118
|
expect(res.status).toBe(BAD_REQUEST);
|
|
101
|
-
|
|
102
|
-
expect(
|
|
103
|
-
}
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
`
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const res: TestRes = await agent
|
|
111
|
-
.post(Paths.Users.Add())
|
|
119
|
+
const errMsg = extractErrMsg(res);
|
|
120
|
+
expect(errMsg).toBe('Invalid input: expected object, received null');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('when the name is empty', () => {
|
|
125
|
+
it(`returns status "${BAD_REQUEST}"`, async () => {
|
|
126
|
+
const res = await agent
|
|
127
|
+
.post(P.Add.CALL)
|
|
112
128
|
.send({ user: { name: '', email: 'a@a.com' } });
|
|
113
129
|
expect(res.status).toBe(BAD_REQUEST);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
130
|
+
// Verify it didn't persist in the database
|
|
131
|
+
const allUsers = await UserRepo.getAll();
|
|
132
|
+
expect(allUsers).toHaveLength(DUMMY_USERS.length);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('when the request body cannot be parsed', () => {
|
|
137
|
+
it('returns 400 for malformed JSON without persisting a user', async () => {
|
|
138
|
+
const res: TypedErrRes = await agent
|
|
139
|
+
.post(P.Add.CALL)
|
|
140
|
+
.set('Content-Type', 'application/json')
|
|
141
|
+
.send('{"user":');
|
|
142
|
+
expect(res.status).toBe(BAD_REQUEST);
|
|
143
|
+
expect(res.body).toStrictEqual({ error: 'Invalid JSON' });
|
|
144
|
+
expect(await UserRepo.getAll()).toStrictEqual(DUMMY_USERS);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it.each([
|
|
148
|
+
[
|
|
149
|
+
'application/json',
|
|
150
|
+
JSON.stringify({ user: { name: 'a'.repeat(110000) } }),
|
|
151
|
+
],
|
|
152
|
+
[
|
|
153
|
+
'application/x-www-form-urlencoded',
|
|
154
|
+
`user[name]=${'a'.repeat(110000)}`,
|
|
155
|
+
],
|
|
156
|
+
])('returns 413 for an oversized %s body', async (contentType, body) => {
|
|
157
|
+
const res: TypedErrRes = await agent
|
|
158
|
+
.post(P.Add.CALL)
|
|
159
|
+
.set('Content-Type', contentType)
|
|
160
|
+
.send(body);
|
|
161
|
+
expect(res.status).toBe(HttpStatusCodes.PAYLOAD_TOO_LARGE);
|
|
162
|
+
expect(res.body).toStrictEqual({ error: 'Request body too large' });
|
|
163
|
+
expect(await UserRepo.getAll()).toStrictEqual(DUMMY_USERS);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
117
166
|
});
|
|
118
167
|
|
|
119
|
-
//
|
|
120
|
-
describe(
|
|
121
|
-
|
|
122
|
-
`
|
|
123
|
-
'request was successful.',
|
|
124
|
-
async () => {
|
|
168
|
+
// --- `Update` ---
|
|
169
|
+
describe(P.Update.DISPLAY, () => {
|
|
170
|
+
describe('on success', () => {
|
|
171
|
+
it(`saves the change and returns status "${OK}"`, async () => {
|
|
125
172
|
const user = { ...dbUsers[0], name: 'Bill' };
|
|
126
|
-
const res = await agent.put(
|
|
173
|
+
const res = await agent.put(P.Update.CALL).send({ user });
|
|
127
174
|
expect(res.status).toBe(OK);
|
|
128
|
-
const
|
|
175
|
+
const allUsers = await UserRepo.getAll();
|
|
176
|
+
const saved = allUsers.find((u) => u.id === user.id);
|
|
129
177
|
expect(saved?.name).toBe('Bill');
|
|
130
|
-
}
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
'
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
.
|
|
140
|
-
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('when the id is invalid', () => {
|
|
182
|
+
it('returns a validation error', async () => {
|
|
183
|
+
const newUser = User.create();
|
|
184
|
+
const user = {
|
|
185
|
+
...newUser,
|
|
186
|
+
name: 'a',
|
|
187
|
+
email: 'a@a.com',
|
|
188
|
+
id: '5',
|
|
189
|
+
};
|
|
190
|
+
const res = await agent.put(P.Update.CALL).send({ user });
|
|
141
191
|
expect(res.status).toBe(BAD_REQUEST);
|
|
142
|
-
|
|
143
|
-
expect(
|
|
144
|
-
}
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
async () => {
|
|
152
|
-
const user = User.create({ id: jetid(), name: 'a', email: 'a@a.com' }),
|
|
153
|
-
res: TestRes = await agent.put(Paths.Users.Update()).send({ user });
|
|
192
|
+
const errMsg = extractErrMsg(res);
|
|
193
|
+
expect(errMsg).toBe(errMsg);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('when the user is not found', () => {
|
|
198
|
+
it(`returns status "${NOT_FOUND}"`, async () => {
|
|
199
|
+
const user = User.create({ name: 'a', email: 'a@a.com' });
|
|
200
|
+
const res: TypedErrRes = await agent.put(P.Update.CALL).send({ user });
|
|
154
201
|
expect(res.status).toBe(NOT_FOUND);
|
|
155
|
-
expect(res.body.error).toBe(
|
|
156
|
-
}
|
|
157
|
-
);
|
|
202
|
+
expect(res.body.error).toBe(Errors.USER_NOT_FOUND);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
158
205
|
});
|
|
159
206
|
|
|
160
|
-
//
|
|
161
|
-
describe(
|
|
162
|
-
|
|
163
|
-
`
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
res = await agent.delete(Paths.Users.Delete({ id }));
|
|
207
|
+
// --- `Delete` ---
|
|
208
|
+
describe(P.Delete.DISPLAY, () => {
|
|
209
|
+
describe('on success', () => {
|
|
210
|
+
it(`removes the user and returns status "${OK}"`, async () => {
|
|
211
|
+
const id = dbUsers[0].id;
|
|
212
|
+
const path = P.Delete.CALL(id);
|
|
213
|
+
const res = await agent.delete(path);
|
|
168
214
|
expect(res.status).toBe(OK);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const res:
|
|
179
|
-
Paths.Users.Delete({ id: jetid() }),
|
|
180
|
-
);
|
|
215
|
+
const persists = await UserRepo.persists(id);
|
|
216
|
+
expect(persists).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('when the user is not found', () => {
|
|
221
|
+
it(`returns status "${NOT_FOUND}"`, async () => {
|
|
222
|
+
const id = User.genId();
|
|
223
|
+
const path = P.Delete.CALL(id);
|
|
224
|
+
const res: TypedErrRes = await agent.delete(path);
|
|
181
225
|
expect(res.status).toBe(NOT_FOUND);
|
|
182
|
-
expect(res.body.error).toBe(
|
|
183
|
-
}
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
`
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const res = await agent.delete(Paths.Users.Delete({ id: -1 }));
|
|
226
|
+
expect(res.body.error).toBe(Errors.USER_NOT_FOUND);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('when the id is invalid', () => {
|
|
231
|
+
it(`returns status "${BAD_REQUEST}"`, async () => {
|
|
232
|
+
const path = P.Delete.CALL('asdf');
|
|
233
|
+
const res = await agent.delete(path);
|
|
191
234
|
expect(res.status).toBe(BAD_REQUEST);
|
|
192
|
-
}
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
`
|
|
197
|
-
|
|
198
|
-
async () => {
|
|
199
|
-
const res: TestRes = await agent.delete(Paths.Users.Delete());
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('when the id path param is missing', () => {
|
|
239
|
+
it(`returns status "${BAD_REQUEST}"`, async () => {
|
|
240
|
+
const res = await agent.delete(P.Delete.FULL_PATH);
|
|
200
241
|
expect(res.status).toBe(BAD_REQUEST);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
242
|
+
const errMsg = extractErrMsg(res);
|
|
243
|
+
expect(errMsg).toStrictEqual('Not a valid uuid7 id');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
204
246
|
});
|
|
205
247
|
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-generator-typescript",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Generate new Express applications similar to express-generate which but sets it up to use TypeScript instead",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"commit": "git add -A && git commit -m \"dev commit\"",
|
|
7
|
-
"commit:push": "npm run commit && git push",
|
|
8
6
|
"install:clean": "rm -rf node_modules && rm -rf package-lock.json && npm i",
|
|
9
7
|
"test": "node --test \"tests/**/*.test.js\"",
|
|
10
|
-
"test:e2e": "node scripts/e2e.js"
|
|
8
|
+
"test:e2e": "node scripts/e2e.js",
|
|
9
|
+
"_local:commit": "tsx --tsconfig ../my-tools/tsconfig.json ../my-tools/src/workflows/commit/run.ts",
|
|
10
|
+
"_local:commit:push": "npm run _local:commit -- -p",
|
|
11
|
+
"_local:squash-history": "tsx --tsconfig ../my-tools/tsconfig.json ../my-tools/src/workflows/squash-history/run.ts"
|
|
11
12
|
},
|
|
12
13
|
"main": "lib/express-generator-typescript.js",
|
|
13
14
|
"bin": {
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
"!lib/template/src/repos/common/database.test.json"
|
|
26
27
|
],
|
|
27
28
|
"engines": {
|
|
28
|
-
"node": "
|
|
29
|
+
"node": "^22.13.0 || ^24.0.0 || >=26.0.0"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import jetEnv, { num } from 'jet-env';
|
|
2
|
-
|
|
3
|
-
import type { ValueOf } from '../types/utility-types';
|
|
4
|
-
|
|
5
|
-
// ========================================================================= //
|
|
6
|
-
// CONSTANTS //
|
|
7
|
-
// ========================================================================= //
|
|
8
|
-
|
|
9
|
-
// NOTE: These need to match the names of your ".env" files
|
|
10
|
-
export const NodeEnvs = {
|
|
11
|
-
DEV: 'development',
|
|
12
|
-
TEST: 'test',
|
|
13
|
-
PRODUCTION: 'production',
|
|
14
|
-
} as const;
|
|
15
|
-
export type NodeEnvs = ValueOf<typeof NodeEnvs>;
|
|
16
|
-
|
|
17
|
-
// ========================================================================= //
|
|
18
|
-
// EXEC //
|
|
19
|
-
// ========================================================================= //
|
|
20
|
-
|
|
21
|
-
// Setup the is `NodeEnvs` validator
|
|
22
|
-
const isNodeEnv = (() => {
|
|
23
|
-
const vals = Object.values(NodeEnvs);
|
|
24
|
-
const valsFin = vals.map((item) => item.toLowerCase());
|
|
25
|
-
const set = new Set(valsFin);
|
|
26
|
-
return (val: unknown): val is NodeEnvs => set.has(val as NodeEnvs);
|
|
27
|
-
})();
|
|
28
|
-
|
|
29
|
-
export const EnvVars = jetEnv({
|
|
30
|
-
NodeEnv: isNodeEnv,
|
|
31
|
-
Port: num,
|
|
32
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import jetid from 'jet-id';
|
|
2
|
-
import { isNonEmptyString, isString } from 'jet-validators';
|
|
3
|
-
import { parseObject, type Schema, testObject } from 'jet-validators/utils';
|
|
4
|
-
|
|
5
|
-
import { isISOString } from '@src/common/utils/date-utils';
|
|
6
|
-
|
|
7
|
-
import type { UserEntity, UserInput } from '../types';
|
|
8
|
-
|
|
9
|
-
// ========================================================================= //
|
|
10
|
-
// FUNCTIONS //
|
|
11
|
-
// ========================================================================= //
|
|
12
|
-
|
|
13
|
-
const schema: Schema<UserEntity> = {
|
|
14
|
-
id: isUserId,
|
|
15
|
-
name: isString,
|
|
16
|
-
email: isString,
|
|
17
|
-
created: isISOString,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Validate the `User` schema.
|
|
22
|
-
*/
|
|
23
|
-
export const parseUser = parseObject<UserEntity>(schema);
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* For the APIs make sure the right fields are complete
|
|
27
|
-
*/
|
|
28
|
-
export const isCompleteUser = testObject<UserEntity>({
|
|
29
|
-
...schema,
|
|
30
|
-
name: isNonEmptyString,
|
|
31
|
-
email: isNonEmptyString,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Validate the fields a client sends to create a user.
|
|
36
|
-
*/
|
|
37
|
-
export const isUserInput = testObject<UserInput>({
|
|
38
|
-
name: isNonEmptyString,
|
|
39
|
-
email: isNonEmptyString,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Test if an id is a valid user id.
|
|
44
|
-
*/
|
|
45
|
-
export function isUserId(val: unknown): val is string {
|
|
46
|
-
return jetid.test(val);
|
|
47
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { 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;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { parseObject, type Schema } from 'jet-validators/utils';
|
|
2
|
-
|
|
3
|
-
import { ValidationError } from '@src/common/classes/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;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { UserEntity } from '@src/entities/User';
|
|
2
|
-
|
|
3
|
-
// ========================================================================= //
|
|
4
|
-
// TYPES //
|
|
5
|
-
// ========================================================================= //
|
|
6
|
-
|
|
7
|
-
type UserArray = UserEntity[] | readonly UserEntity[];
|
|
8
|
-
|
|
9
|
-
// ========================================================================= //
|
|
10
|
-
// FUNCTIONS //
|
|
11
|
-
// ========================================================================= //
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Compare to user arrays. Order does not matter and this assumes the email
|
|
15
|
-
* field is unique
|
|
16
|
-
*/
|
|
17
|
-
export function compareUserArrays(a: UserArray, b: UserArray): boolean {
|
|
18
|
-
if (a.length !== b.length) return false;
|
|
19
|
-
const aSorted = sortByEmail(a),
|
|
20
|
-
bSorted = sortByEmail(b);
|
|
21
|
-
for (let i = 0; i < aSorted.length; i++) {
|
|
22
|
-
const a = aSorted[i],
|
|
23
|
-
b = bSorted[i];
|
|
24
|
-
if (a.email !== b.email || a.name !== b.name) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Sort user array by email.
|
|
33
|
-
*/
|
|
34
|
-
function sortByEmail(arr: UserArray): UserEntity[] {
|
|
35
|
-
return [...arr].sort((x, y) => {
|
|
36
|
-
return x.email.localeCompare(y.email);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { ParseError } from 'jet-validators/utils';
|
|
2
|
-
import type { 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
|
-
}
|
|
File without changes
|