express-generator-typescript 2.4.7 → 2.4.8

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.
Files changed (33) hide show
  1. package/lib/express-generator-typescript.js +1 -1
  2. package/lib/project-files/package.json +2 -1
  3. package/lib/project-files/spec/support/apiCb.ts +76 -0
  4. package/lib/project-files/spec/tests/users.spec.ts +98 -135
  5. package/lib/project-files/spec/types/misc.ts +18 -0
  6. package/lib/project-files/src/models/User.ts +13 -27
  7. package/lib/project-files/src/public/scripts/http.js +3 -1
  8. package/lib/project-files/src/public/scripts/users.js +31 -65
  9. package/lib/project-files/src/public/stylesheets/users.css +4 -6
  10. package/lib/project-files/src/repos/UserRepo.ts +6 -1
  11. package/lib/project-files/src/repos/database.json +22 -1
  12. package/lib/project-files/src/routes/types/express/misc.ts +1 -5
  13. package/lib/project-files/src/routes/types/types.ts +0 -8
  14. package/lib/project-files/src/views/users.html +100 -5
  15. package/lib/project-files-auth/package.json +2 -1
  16. package/lib/project-files-auth/spec/support/apiCb.ts +76 -0
  17. package/lib/project-files-auth/spec/support/login.ts +3 -6
  18. package/lib/project-files-auth/spec/tests/auth.spec.ts +50 -67
  19. package/lib/project-files-auth/spec/tests/users.spec.ts +98 -142
  20. package/lib/project-files-auth/spec/types/misc.ts +18 -0
  21. package/lib/project-files-auth/src/models/User.ts +17 -12
  22. package/lib/project-files-auth/src/public/scripts/http.js +3 -1
  23. package/lib/project-files-auth/src/public/scripts/login.js +1 -3
  24. package/lib/project-files-auth/src/public/scripts/users.js +31 -63
  25. package/lib/project-files-auth/src/public/stylesheets/users.css +4 -11
  26. package/lib/project-files-auth/src/repos/UserRepo.ts +6 -1
  27. package/lib/project-files-auth/src/repos/database.json +1 -1
  28. package/lib/project-files-auth/src/services/UserService.ts +2 -8
  29. package/lib/project-files-auth/src/views/login.html +26 -3
  30. package/lib/project-files-auth/src/views/users.html +106 -8
  31. package/package.json +1 -1
  32. package/lib/project-files/spec/support/types.ts +0 -1
  33. package/lib/project-files-auth/spec/support/types.ts +0 -1
@@ -20,7 +20,7 @@ const PROJECT_FOLDER_PATH = './project-files';
20
20
  // Project-folder dependencies
21
21
  const DEPENDENCIES = 'express dotenv morgan cookie-parser jet-logger ' +
22
22
  'module-alias express-async-errors helmet jsonfile inserturlparams ' +
23
- 'jet-validator ts-command-line-args jet-paths',
23
+ 'jet-validator ts-command-line-args jet-paths moment',
24
24
  DEPENDENCIES_AUTH = ' bcrypt jsonwebtoken',
25
25
  DEV_DEPENDENCIES = 'ts-node typescript nodemon find jasmine supertest ' +
26
26
  '@types/node @types/express @types/jasmine @types/find @types/morgan ' +
@@ -35,11 +35,12 @@
35
35
  "helmet": "^6.0.0",
36
36
  "inserturlparams": "^1.0.1",
37
37
  "jet-logger": "^1.3.1",
38
- "jet-validator": "^1.0.3",
39
38
  "jet-paths": "^1.0.5",
39
+ "jet-validator": "^1.0.3",
40
40
  "jsonfile": "^6.1.0",
41
41
  "jsonwebtoken": "^8.5.1",
42
42
  "module-alias": "^2.2.2",
43
+ "moment": "^2.30.1",
43
44
  "morgan": "^1.10.0",
44
45
  "ts-command-line-args": "^2.4.2"
45
46
  },
@@ -0,0 +1,76 @@
1
+ import { CallbackHandler } from 'supertest';
2
+ import moment from 'moment';
3
+ import logger from 'jet-logger';
4
+
5
+ import { TApiCb, TRes } from 'spec/types/misc';
6
+
7
+
8
+ /**
9
+ * API callback function.
10
+ */
11
+ function apiCb(
12
+ cb: TApiCb,
13
+ dateParam = 'created',
14
+ printErr?: boolean,
15
+ ): CallbackHandler {
16
+ return (err: Error, res: TRes) => {
17
+ if (printErr) {
18
+ logger.err(err);
19
+ }
20
+ _strToDate(res.body, dateParam);
21
+ return cb(res);
22
+ };
23
+ }
24
+
25
+ /**
26
+ * When date objects get sent through supertest they are converted to
27
+ * iso-strings. This will cause "toEqual()"" tests to fail, so we need to
28
+ * convert them back to Date objects.
29
+ */
30
+ function _strToDate(
31
+ param: unknown,
32
+ prop: string,
33
+ ): void {
34
+ return _iterate(param, prop);
35
+ }
36
+
37
+ /**
38
+ * Interate object recursively and convert string-dates to "Date" objects.
39
+ */
40
+ function _iterate(param: unknown, prop: string): void {
41
+ if (!param || typeof param !== 'object') {
42
+ return;
43
+ }
44
+ const paramF = param as Record<string, unknown>;
45
+ // For Arrays
46
+ if (Array.isArray(paramF)) {
47
+ for (const item of paramF) {
48
+ _iterate(item, prop);
49
+ }
50
+ return;
51
+ }
52
+ // Check valid string or Date object. If undefined just skip
53
+ const val = paramF[prop];
54
+ if (
55
+ (typeof val !== 'undefined') &&
56
+ !((typeof val === 'string') || (val instanceof Date)) &&
57
+ !moment(val as string | Date).isValid()
58
+ ) {
59
+ throw new Error('Property must be a valid date-string or Date() object');
60
+ }
61
+ // Convert and iterate
62
+ if (typeof val !== 'undefined') {
63
+ paramF[prop] = new Date(val as string | Date);
64
+ }
65
+ for (const key in paramF) {
66
+ const oval = paramF[key];
67
+ if (typeof oval === 'object' && key !== prop) {
68
+ _iterate(oval, prop);
69
+ }
70
+ }
71
+ }
72
+
73
+
74
+ // **** Export default **** //
75
+
76
+ export default apiCb;
@@ -1,4 +1,4 @@
1
- import supertest, { Test, Response } from 'supertest';
1
+ import supertest, { Test } from 'supertest';
2
2
  import TestAgent from 'supertest/lib/agent';
3
3
  import { defaultErrMsg as ValidatorErr } from 'jet-validator';
4
4
  import insertUrlParams from 'inserturlparams';
@@ -11,213 +11,176 @@ import HttpStatusCodes from '@src/constants/HttpStatusCodes';
11
11
  import { USER_NOT_FOUND_ERR } from '@src/services/UserService';
12
12
 
13
13
  import Paths from 'spec/support/Paths';
14
- import { TReqBody } from 'spec/support/types';
14
+ import apiCb from 'spec/support/apiCb';
15
+ import { TApiCb } from 'spec/types/misc';
15
16
 
16
17
 
17
- // **** Variables **** //
18
-
19
- // StatusCodes
20
- const {
21
- OK,
22
- CREATED,
23
- NOT_FOUND,
24
- BAD_REQUEST,
25
- } = HttpStatusCodes;
26
-
27
18
  // Dummy users for GET req
28
- const DummyGetAllUsers = [
29
- User.new('Sean Maxwell', 'sean.maxwell@gmail.com'),
30
- User.new('John Smith', 'john.smith@gmail.com'),
31
- User.new('Gordan Freeman', 'gordan.freeman@gmail.com'),
32
- ] as const;
33
-
34
- // Dummy update user
35
- const DummyUserData = {
36
- user: User.new('Gordan Freeman', 'gordan.freeman@gmail.com'),
37
- } as const;
38
-
39
-
40
- // **** Types **** //
41
-
42
- type TRes = Omit<Response, 'body'> & {
43
- body: {
44
- users: IUser[];
45
- error: string;
46
- }
19
+ const getDummyUsers = () => {
20
+ return [
21
+ User.new('Sean Maxwell', 'sean.maxwell@gmail.com'),
22
+ User.new('John Smith', 'john.smith@gmail.com'),
23
+ User.new('Gordan Freeman', 'gordan.freeman@gmail.com'),
24
+ ];
47
25
  };
48
26
 
49
27
 
50
- // **** Tests **** //
51
-
28
+ // Tests
52
29
  describe('UserRouter', () => {
53
30
 
54
31
  let agent: TestAgent<Test>;
55
32
 
56
33
  // Run before all tests
57
- beforeAll((done) => {
34
+ beforeAll(done => {
58
35
  agent = supertest.agent(app);
59
36
  done();
60
37
  });
61
38
 
62
- // ** Get all users ** //
39
+ // Get all users
63
40
  describe(`"GET:${Paths.Users.Get}"`, () => {
64
41
 
65
- const callApi = () => agent.get(Paths.Users.Get);
42
+ // Setup API
43
+ const api = (cb: TApiCb) =>
44
+ agent
45
+ .get(Paths.Users.Get)
46
+ .end(apiCb(cb));
66
47
 
67
48
  // Success
68
49
  it('should return a JSON object with all the users and a status code ' +
69
- `of "${OK}" if the request was successful.`, (done) => {
50
+ `of "${HttpStatusCodes.OK}" if the request was successful.`, (done) => {
70
51
  // Add spy
71
- spyOn(UserRepo, 'getAll').and.resolveTo([...DummyGetAllUsers]);
52
+ const data = getDummyUsers();
53
+ spyOn(UserRepo, 'getAll').and.resolveTo(data);
72
54
  // Call API
73
- callApi()
74
- .end((_: Error, res: TRes) => {
75
- expect(res.status).toBe(OK);
76
- for (let i = 0; i < res.body.users.length; i++) {
77
- const user = res.body.users[i];
78
- expect(user).toEqual(DummyGetAllUsers[i]);
79
- }
80
- done();
81
- });
55
+ api(res => {
56
+ expect(res.status).toBe(HttpStatusCodes.OK);
57
+ expect(res.body).toEqual({ users: data });
58
+ done();
59
+ });
82
60
  });
83
61
  });
84
62
 
85
63
  // Test add user
86
64
  describe(`"POST:${Paths.Users.Add}"`, () => {
87
65
 
88
- const ERROR_MSG = `${ValidatorErr}"user".`;
66
+ const ERROR_MSG = `${ValidatorErr}"user".`,
67
+ DUMMY_USER = getDummyUsers()[0];
89
68
 
90
- const callApi = (reqBody: TReqBody) =>
69
+ // Setup API
70
+ const callApi = (user: IUser | null, cb: TApiCb) =>
91
71
  agent
92
72
  .post(Paths.Users.Add)
93
- .type('form').send(reqBody);
73
+ .send({ user })
74
+ .end(apiCb(cb));
94
75
 
95
76
  // Test add user success
96
- it(`should return a status code of "${CREATED}" if the request was ` +
97
- 'successful.', (done) => {
77
+ it(`should return a status code of "${HttpStatusCodes.CREATED}" if the ` +
78
+ 'request was successful.', (done) => {
98
79
  // Spy
99
80
  spyOn(UserRepo, 'add').and.resolveTo();
100
81
  // Call api
101
- callApi(DummyUserData)
102
- .end((_: Error, res: TRes) => {
103
- expect(res.status).toBe(CREATED);
104
- expect(res.body.error).toBeUndefined();
105
- done();
106
- });
82
+ callApi(DUMMY_USER, res => {
83
+ expect(res.status).toBe(HttpStatusCodes.CREATED);
84
+ done();
85
+ });
107
86
  });
108
87
 
109
88
  // Missing param
110
- it('should return a JSON object with an error message of ' +
111
- `"${ERROR_MSG}" and a status code of "${BAD_REQUEST}" if the user ` +
89
+ it(`should return a JSON object with an error message of "${ERROR_MSG}" ` +
90
+ `and a status code of "${HttpStatusCodes.BAD_REQUEST}" if the user ` +
112
91
  'param was missing.', (done) => {
113
92
  // Call api
114
- callApi({})
115
- .end((_: Error, res: TRes) => {
116
- expect(res.status).toBe(BAD_REQUEST);
117
- expect(res.body.error).toBe(ERROR_MSG);
118
- done();
119
- });
93
+ callApi(null, res => {
94
+ expect(res.status).toBe(HttpStatusCodes.BAD_REQUEST);
95
+ expect(res.body.error).toBe(ERROR_MSG);
96
+ done();
97
+ });
120
98
  });
121
99
  });
122
100
 
123
- // ** Update users ** //
101
+ // Update users
124
102
  describe(`"PUT:${Paths.Users.Update}"`, () => {
125
103
 
126
- const ERROR_MSG = `${ValidatorErr}"user".`;
104
+ const ERROR_MSG = `${ValidatorErr}"user".`,
105
+ DUMMY_USER = getDummyUsers()[0];
127
106
 
128
- const callApi = (reqBody: TReqBody) =>
107
+ // Setup API
108
+ const callApi = (user: IUser | null, cb: TApiCb) =>
129
109
  agent
130
110
  .put(Paths.Users.Update)
131
- .type('form').send(reqBody);
111
+ .send({ user })
112
+ .end(apiCb(cb));
132
113
 
133
114
  // Success
134
- it(`should return a status code of "${OK}" if the request was successful.`,
135
- (done) => {
136
- // Setup spies
137
- spyOn(UserRepo, 'update').and.resolveTo();
138
- spyOn(UserRepo, 'persists').and.resolveTo(true);
139
- // Call api
140
- callApi(DummyUserData)
141
- .end((_: Error, res: TRes) => {
142
- expect(res.status).toBe(OK);
143
- expect(res.body.error).toBeUndefined();
144
- done();
145
- });
115
+ it(`should return a status code of "${HttpStatusCodes.OK}" if the ` +
116
+ 'request was successful.', (done) => {
117
+ // Setup spies
118
+ spyOn(UserRepo, 'update').and.resolveTo();
119
+ spyOn(UserRepo, 'persists').and.resolveTo(true);
120
+ // Call api
121
+ callApi(DUMMY_USER, res => {
122
+ expect(res.status).toBe(HttpStatusCodes.OK);
123
+ done();
146
124
  });
125
+ });
147
126
 
148
127
  // Param missing
149
- it('should return a JSON object with an error message of ' +
150
- `"${ERROR_MSG}" and a status code of "${BAD_REQUEST}" if the user ` +
128
+ it(`should return a JSON object with an error message of "${ERROR_MSG}" ` +
129
+ `and a status code of "${HttpStatusCodes.BAD_REQUEST}" if the user ` +
151
130
  'param was missing.', (done) => {
152
131
  // Call api
153
- callApi({})
154
- .end((_: Error, res: TRes) => {
155
- expect(res.status).toBe(BAD_REQUEST);
156
- expect(res.body.error).toBe(ERROR_MSG);
157
- done();
158
- });
132
+ callApi(null, res => {
133
+ expect(res.status).toBe(HttpStatusCodes.BAD_REQUEST);
134
+ expect(res.body.error).toBe(ERROR_MSG);
135
+ done();
136
+ });
159
137
  });
160
138
 
161
139
  // User not found
162
140
  it('should return a JSON object with the error message of ' +
163
- `"${USER_NOT_FOUND_ERR}" and a status code of "${NOT_FOUND}" if the id ` +
164
- 'was not found.', (done) => {
141
+ `"${USER_NOT_FOUND_ERR}" and a status code of ` +
142
+ `"${HttpStatusCodes.NOT_FOUND}" if the id was not found.`, (done) => {
165
143
  // Call api
166
- callApi(DummyUserData)
167
- .end((_: Error, res: TRes) => {
168
- expect(res.status).toBe(NOT_FOUND);
169
- expect(res.body.error).toBe(USER_NOT_FOUND_ERR);
170
- done();
171
- });
144
+ callApi(DUMMY_USER, res => {
145
+ expect(res.status).toBe(HttpStatusCodes.NOT_FOUND);
146
+ expect(res.body.error).toBe(USER_NOT_FOUND_ERR);
147
+ done();
148
+ });
172
149
  });
173
150
  });
174
151
 
175
- // ** Delete user ** //
152
+ // Delete User
176
153
  describe(`"DELETE:${Paths.Users.Delete}"`, () => {
177
154
 
178
- const VALIDATOR_ERR = `${ValidatorErr}"id".`;
179
-
180
- const callApi = (id: number) =>
155
+ // Call API
156
+ const callApi = (id: number, cb: TApiCb) =>
181
157
  agent
182
- .delete(insertUrlParams(Paths.Users.Delete, { id }));
158
+ .delete(insertUrlParams(Paths.Users.Delete, { id }))
159
+ .end(apiCb(cb));
183
160
 
184
161
  // Success
185
- it(`should return a status code of "${OK}" if the request was successful.`,
186
- (done) => {
187
- // Setup spies
188
- spyOn(UserRepo, 'delete').and.resolveTo();
189
- spyOn(UserRepo, 'persists').and.resolveTo(true);
190
- // Call api
191
- callApi(5)
192
- .end((_: Error, res: TRes) => {
193
- expect(res.status).toBe(OK);
194
- expect(res.body.error).toBeUndefined();
195
- done();
196
- });
162
+ it(`should return a status code of "${HttpStatusCodes.OK}" if the ` +
163
+ 'request was successful.', (done) => {
164
+ // Setup spies
165
+ spyOn(UserRepo, 'delete').and.resolveTo();
166
+ spyOn(UserRepo, 'persists').and.resolveTo(true);
167
+ // Call api
168
+ callApi(5, res => {
169
+ expect(res.status).toBe(HttpStatusCodes.OK);
170
+ done();
197
171
  });
172
+ });
198
173
 
199
174
  // User not found
200
175
  it('should return a JSON object with the error message of ' +
201
- `"${USER_NOT_FOUND_ERR}" and a status code of "${NOT_FOUND}" if the id ` +
202
- 'was not found.', (done) => {
203
- callApi(-1)
204
- .end((_: Error, res: TRes) => {
205
- expect(res.status).toBe(NOT_FOUND);
206
- expect(res.body.error).toBe(USER_NOT_FOUND_ERR);
207
- done();
208
- });
209
- });
210
-
211
- // Invalid param
212
- it(`should return a status code of "${BAD_REQUEST}" and return an error ` +
213
- `message of "${VALIDATOR_ERR}" if the id was not a valid number`,
214
- (done) => {
215
- callApi('horse' as unknown as number)
216
- .end((_: Error, res: TRes) => {
217
- expect(res.status).toBe(BAD_REQUEST);
218
- expect(res.body.error).toBe(VALIDATOR_ERR);
219
- done();
220
- });
176
+ `"${USER_NOT_FOUND_ERR}" and a status code of ` +
177
+ `"${HttpStatusCodes.NOT_FOUND}" if the id was not found.`, done => {
178
+ // Setup spies
179
+ callApi(-1, res => {
180
+ expect(res.status).toBe(HttpStatusCodes.NOT_FOUND);
181
+ expect(res.body.error).toBe(USER_NOT_FOUND_ERR);
182
+ done();
183
+ });
221
184
  });
222
185
  });
223
186
  });
@@ -0,0 +1,18 @@
1
+ import { Response } from 'supertest';
2
+
3
+ import { IUser } from '@src/models/User';
4
+
5
+
6
+ // Misc
7
+ export type TReqBody = Record<string, unknown>;
8
+ export type TRes = Omit<Response, 'body'> & { body: TBody };
9
+ export type TApiCb = (res: TRes) => void;
10
+
11
+
12
+ // Response Body: Add things to the body here over time to prevent
13
+ // typescript errors.
14
+ type TBody = {
15
+ [key: string]: unknown;
16
+ user?: IUser;
17
+ users?: IUser[];
18
+ }
@@ -1,12 +1,10 @@
1
- // **** Variables **** //
1
+ import moment from 'moment';
2
2
 
3
- const INVALID_CONSTRUCTOR_PARAM = 'nameOrObj arg must a string or an ' +
4
- 'object with the appropriate user keys.';
5
3
 
6
- export enum UserRoles {
7
- Standard,
8
- Admin,
9
- }
4
+ // **** Variables **** //
5
+
6
+ const INVALID_CONSTRUCTOR_PARAM = 'nameOrObj arg must a string or an object ' +
7
+ 'with the appropriate user keys.';
10
8
 
11
9
 
12
10
  // **** Types **** //
@@ -15,15 +13,7 @@ export interface IUser {
15
13
  id: number;
16
14
  name: string;
17
15
  email: string;
18
- pwdHash?: string;
19
- role?: UserRoles;
20
- }
21
-
22
- export interface ISessionUser {
23
- id: number;
24
- email: string;
25
- name: string;
26
- role: IUser['role'];
16
+ created: Date;
27
17
  }
28
18
 
29
19
 
@@ -35,16 +25,14 @@ export interface ISessionUser {
35
25
  function new_(
36
26
  name?: string,
37
27
  email?: string,
38
- role?: UserRoles,
39
- pwdHash?: string,
28
+ created?: Date,
40
29
  id?: number, // id last cause usually set by db
41
30
  ): IUser {
42
31
  return {
43
32
  id: (id ?? -1),
44
33
  name: (name ?? ''),
45
34
  email: (email ?? ''),
46
- role: (role ?? UserRoles.Standard),
47
- pwdHash: (pwdHash ?? ''),
35
+ created: (created ? new Date(created) : new Date()),
48
36
  };
49
37
  }
50
38
 
@@ -52,13 +40,11 @@ function new_(
52
40
  * Get user instance from object.
53
41
  */
54
42
  function from(param: object): IUser {
55
- // Check is user
56
43
  if (!isUser(param)) {
57
44
  throw new Error(INVALID_CONSTRUCTOR_PARAM);
58
45
  }
59
- // Get user instance
60
46
  const p = param as IUser;
61
- return new_(p.name, p.email, p.role, p.pwdHash, p.id);
47
+ return new_(p.name, p.email, p.created, p.id);
62
48
  }
63
49
 
64
50
  /**
@@ -68,10 +54,10 @@ function isUser(arg: unknown): boolean {
68
54
  return (
69
55
  !!arg &&
70
56
  typeof arg === 'object' &&
71
- 'id' in arg &&
72
- 'email' in arg &&
73
- 'name' in arg &&
74
- 'role' in arg
57
+ 'id' in arg && typeof arg.id === 'number' &&
58
+ 'email' in arg && typeof arg.email === 'string' &&
59
+ 'name' in arg && typeof arg.name === 'string' &&
60
+ 'created' in arg && moment(arg.created as string | Date).isValid()
75
61
  );
76
62
  }
77
63
 
@@ -1,4 +1,5 @@
1
1
  var Http = (() => {
2
+
2
3
  // Setup request for json
3
4
  var getOptions = (verb, data) => {
4
5
  var options = {
@@ -9,11 +10,12 @@ var Http = (() => {
9
10
  'Content-Type': 'application/json',
10
11
  },
11
12
  };
12
- if (data) {
13
+ if (!!data) {
13
14
  options.body = JSON.stringify(data);
14
15
  }
15
16
  return options;
16
17
  };
18
+
17
19
  // Set Http methods
18
20
  return {
19
21
  get: (path) => fetch(path, getOptions('GET')),