express-generator-typescript 2.4.6 → 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 +4 -3
  3. package/lib/project-files/spec/support/apiCb.ts +76 -0
  4. package/lib/project-files/spec/tests/users.spec.ts +102 -127
  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 +4 -3
  16. package/lib/project-files-auth/spec/support/apiCb.ts +76 -0
  17. package/lib/project-files-auth/spec/support/login.ts +6 -8
  18. package/lib/project-files-auth/spec/tests/auth.spec.ts +52 -59
  19. package/lib/project-files-auth/spec/tests/users.spec.ts +101 -133
  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
  },
@@ -54,7 +55,7 @@
54
55
  "@types/jsonwebtoken": "^8.5.9",
55
56
  "@types/morgan": "^1.9.3",
56
57
  "@types/node": "^18.7.14",
57
- "@types/supertest": "^2.0.12",
58
+ "@types/supertest": "^6.0.2",
58
59
  "@typescript-eslint/eslint-plugin": "^5.36.1",
59
60
  "@typescript-eslint/parser": "^5.36.1",
60
61
  "eslint": "^8.23.0",
@@ -63,7 +64,7 @@
63
64
  "fs-extra": "^10.1.0",
64
65
  "jasmine": "^4.3.0",
65
66
  "nodemon": "^2.0.19",
66
- "supertest": "^6.2.4",
67
+ "supertest": "^6.3.4",
67
68
  "ts-node": "^10.9.1",
68
69
  "tsconfig-paths": "^4.1.0",
69
70
  "typescript": "^4.8.2"
@@ -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,211 +1,186 @@
1
- import supertest, { SuperTest, Test, Response } from 'supertest';
1
+ import supertest, { Test } from 'supertest';
2
+ import TestAgent from 'supertest/lib/agent';
2
3
  import { defaultErrMsg as ValidatorErr } from 'jet-validator';
3
4
  import insertUrlParams from 'inserturlparams';
4
5
 
5
6
  import app from '@src/server';
6
7
 
7
8
  import UserRepo from '@src/repos/UserRepo';
8
- import User from '@src/models/User';
9
+ import User, { IUser } from '@src/models/User';
9
10
  import HttpStatusCodes from '@src/constants/HttpStatusCodes';
10
11
  import { USER_NOT_FOUND_ERR } from '@src/services/UserService';
11
12
 
12
13
  import Paths from 'spec/support/Paths';
13
- import { TReqBody } from 'spec/support/types';
14
+ import apiCb from 'spec/support/apiCb';
15
+ import { TApiCb } from 'spec/types/misc';
14
16
 
15
17
 
16
- // **** Variables **** //
17
-
18
- // StatusCodes
19
- const {
20
- OK,
21
- CREATED,
22
- NOT_FOUND,
23
- BAD_REQUEST,
24
- } = HttpStatusCodes;
25
-
26
18
  // Dummy users for GET req
27
- const DummyGetAllUsers = [
28
- User.new('Sean Maxwell', 'sean.maxwell@gmail.com'),
29
- User.new('John Smith', 'john.smith@gmail.com'),
30
- User.new('Gordan Freeman', 'gordan.freeman@gmail.com'),
31
- ] as const;
32
-
33
- // Dummy update user
34
- const DummyUserData = {
35
- user: User.new('Gordan Freeman', 'gordan.freeman@gmail.com'),
36
- } as const;
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
+ ];
25
+ };
37
26
 
38
27
 
39
- // **** Tests **** //
40
-
28
+ // Tests
41
29
  describe('UserRouter', () => {
42
30
 
43
- let agent: SuperTest<Test>;
31
+ let agent: TestAgent<Test>;
44
32
 
45
33
  // Run before all tests
46
- beforeAll((done) => {
34
+ beforeAll(done => {
47
35
  agent = supertest.agent(app);
48
36
  done();
49
37
  });
50
38
 
51
- // ** Get all users ** //
39
+ // Get all users
52
40
  describe(`"GET:${Paths.Users.Get}"`, () => {
53
41
 
54
- 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));
55
47
 
56
48
  // Success
57
49
  it('should return a JSON object with all the users and a status code ' +
58
- `of "${OK}" if the request was successful.`, (done) => {
50
+ `of "${HttpStatusCodes.OK}" if the request was successful.`, (done) => {
59
51
  // Add spy
60
- spyOn(UserRepo, 'getAll').and.resolveTo([...DummyGetAllUsers]);
52
+ const data = getDummyUsers();
53
+ spyOn(UserRepo, 'getAll').and.resolveTo(data);
61
54
  // Call API
62
- callApi()
63
- .end((_: Error, res: Response) => {
64
- expect(res.status).toBe(OK);
65
- for (let i = 0; i < res.body.users.length; i++) {
66
- const user = res.body.users[i];
67
- expect(user).toEqual(DummyGetAllUsers[i]);
68
- }
69
- done();
70
- });
55
+ api(res => {
56
+ expect(res.status).toBe(HttpStatusCodes.OK);
57
+ expect(res.body).toEqual({ users: data });
58
+ done();
59
+ });
71
60
  });
72
61
  });
73
62
 
74
63
  // Test add user
75
64
  describe(`"POST:${Paths.Users.Add}"`, () => {
76
65
 
77
- const ERROR_MSG = `${ValidatorErr}"user".`;
66
+ const ERROR_MSG = `${ValidatorErr}"user".`,
67
+ DUMMY_USER = getDummyUsers()[0];
78
68
 
79
- const callApi = (reqBody: TReqBody) =>
69
+ // Setup API
70
+ const callApi = (user: IUser | null, cb: TApiCb) =>
80
71
  agent
81
72
  .post(Paths.Users.Add)
82
- .type('form').send(reqBody);
73
+ .send({ user })
74
+ .end(apiCb(cb));
83
75
 
84
76
  // Test add user success
85
- it(`should return a status code of "${CREATED}" if the request was ` +
86
- 'successful.', (done) => {
77
+ it(`should return a status code of "${HttpStatusCodes.CREATED}" if the ` +
78
+ 'request was successful.', (done) => {
87
79
  // Spy
88
80
  spyOn(UserRepo, 'add').and.resolveTo();
89
81
  // Call api
90
- callApi(DummyUserData)
91
- .end((_: Error, res: Response) => {
92
- expect(res.status).toBe(CREATED);
93
- expect(res.body.error).toBeUndefined();
94
- done();
95
- });
82
+ callApi(DUMMY_USER, res => {
83
+ expect(res.status).toBe(HttpStatusCodes.CREATED);
84
+ done();
85
+ });
96
86
  });
97
87
 
98
88
  // Missing param
99
- it('should return a JSON object with an error message of ' +
100
- `"${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 ` +
101
91
  'param was missing.', (done) => {
102
92
  // Call api
103
- callApi({})
104
- .end((_: Error, res: Response) => {
105
- expect(res.status).toBe(BAD_REQUEST);
106
- expect(res.body.error).toBe(ERROR_MSG);
107
- done();
108
- });
93
+ callApi(null, res => {
94
+ expect(res.status).toBe(HttpStatusCodes.BAD_REQUEST);
95
+ expect(res.body.error).toBe(ERROR_MSG);
96
+ done();
97
+ });
109
98
  });
110
99
  });
111
100
 
112
- // ** Update users ** //
101
+ // Update users
113
102
  describe(`"PUT:${Paths.Users.Update}"`, () => {
114
103
 
115
- const ERROR_MSG = `${ValidatorErr}"user".`;
104
+ const ERROR_MSG = `${ValidatorErr}"user".`,
105
+ DUMMY_USER = getDummyUsers()[0];
116
106
 
117
- const callApi = (reqBody: TReqBody) =>
107
+ // Setup API
108
+ const callApi = (user: IUser | null, cb: TApiCb) =>
118
109
  agent
119
110
  .put(Paths.Users.Update)
120
- .type('form').send(reqBody);
111
+ .send({ user })
112
+ .end(apiCb(cb));
121
113
 
122
114
  // Success
123
- it(`should return a status code of "${OK}" if the request was successful.`,
124
- (done) => {
125
- // Setup spies
126
- spyOn(UserRepo, 'update').and.resolveTo();
127
- spyOn(UserRepo, 'persists').and.resolveTo(true);
128
- // Call api
129
- callApi(DummyUserData)
130
- .end((_: Error, res: Response) => {
131
- expect(res.status).toBe(OK);
132
- expect(res.body.error).toBeUndefined();
133
- done();
134
- });
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();
135
124
  });
125
+ });
136
126
 
137
127
  // Param missing
138
- it('should return a JSON object with an error message of ' +
139
- `"${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 ` +
140
130
  'param was missing.', (done) => {
141
131
  // Call api
142
- callApi({})
143
- .end((_: Error, res: Response) => {
144
- expect(res.status).toBe(BAD_REQUEST);
145
- expect(res.body.error).toBe(ERROR_MSG);
146
- done();
147
- });
132
+ callApi(null, res => {
133
+ expect(res.status).toBe(HttpStatusCodes.BAD_REQUEST);
134
+ expect(res.body.error).toBe(ERROR_MSG);
135
+ done();
136
+ });
148
137
  });
149
138
 
150
139
  // User not found
151
140
  it('should return a JSON object with the error message of ' +
152
- `"${USER_NOT_FOUND_ERR}" and a status code of "${NOT_FOUND}" if the id ` +
153
- '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) => {
154
143
  // Call api
155
- callApi(DummyUserData)
156
- .end((_: Error, res: Response) => {
157
- expect(res.status).toBe(NOT_FOUND);
158
- expect(res.body.error).toBe(USER_NOT_FOUND_ERR);
159
- done();
160
- });
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
+ });
161
149
  });
162
150
  });
163
151
 
164
- // ** Delete user ** //
152
+ // Delete User
165
153
  describe(`"DELETE:${Paths.Users.Delete}"`, () => {
166
154
 
167
- const VALIDATOR_ERR = `${ValidatorErr}"id".`;
168
-
169
- const callApi = (id: number) =>
155
+ // Call API
156
+ const callApi = (id: number, cb: TApiCb) =>
170
157
  agent
171
- .delete(insertUrlParams(Paths.Users.Delete, { id }));
158
+ .delete(insertUrlParams(Paths.Users.Delete, { id }))
159
+ .end(apiCb(cb));
172
160
 
173
161
  // Success
174
- it(`should return a status code of "${OK}" if the request was successful.`,
175
- (done) => {
176
- // Setup spies
177
- spyOn(UserRepo, 'delete').and.resolveTo();
178
- spyOn(UserRepo, 'persists').and.resolveTo(true);
179
- // Call api
180
- callApi(5)
181
- .end((_: Error, res: Response) => {
182
- expect(res.status).toBe(OK);
183
- expect(res.body.error).toBeUndefined();
184
- done();
185
- });
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();
186
171
  });
172
+ });
187
173
 
188
174
  // User not found
189
175
  it('should return a JSON object with the error message of ' +
190
- `"${USER_NOT_FOUND_ERR}" and a status code of "${NOT_FOUND}" if the id ` +
191
- 'was not found.', (done) => {
192
- callApi(-1)
193
- .end((_: Error, res: Response) => {
194
- expect(res.status).toBe(NOT_FOUND);
195
- expect(res.body.error).toBe(USER_NOT_FOUND_ERR);
196
- done();
197
- });
198
- });
199
-
200
- // Invalid param
201
- it(`should return a status code of "${BAD_REQUEST}" and return an error ` +
202
- `message of "${VALIDATOR_ERR}" if the id was not a valid number`, (done) => {
203
- callApi('horse' as unknown as number)
204
- .end((_: Error, res: Response) => {
205
- expect(res.status).toBe(BAD_REQUEST);
206
- expect(res.body.error).toBe(VALIDATOR_ERR);
207
- done();
208
- });
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
+ });
209
184
  });
210
185
  });
211
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')),