@abgov/nx-adsp 12.4.1 → 12.5.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.
Files changed (41) hide show
  1. package/generators.json +6 -0
  2. package/package.json +1 -1
  3. package/src/generators/angular-app/angular-app.js +22 -7
  4. package/src/generators/angular-app/angular-app.js.map +1 -1
  5. package/src/generators/angular-app/files/src/app/app.component.css__tmpl__ +10 -54
  6. package/src/generators/angular-app/files/src/app/app.component.html__tmpl__ +18 -49
  7. package/src/generators/angular-app/files/src/app/app.component.spec.ts__tmpl__ +22 -27
  8. package/src/generators/angular-app/files/src/app/app.component.ts__tmpl__ +60 -20
  9. package/src/generators/angular-app/files/src/app/app.config.ts__tmpl__ +27 -0
  10. package/src/generators/angular-app/files/src/app/app.routes.ts__tmpl__ +10 -31
  11. package/src/generators/angular-app/files/src/app/home/home.component.html__tmpl__ +1 -3
  12. package/src/generators/angular-app/files/src/app/home/home.component.ts__tmpl__ +9 -23
  13. package/src/generators/angular-app/files/src/app/protected/protected.component.html__tmpl__ +2 -2
  14. package/src/generators/angular-app/files/src/app/protected/protected.component.spec.ts__tmpl__ +27 -43
  15. package/src/generators/angular-app/files/src/app/protected/protected.component.ts__tmpl__ +15 -15
  16. package/src/generators/angular-app/files/src/app/services/auth-guard.service.ts__tmpl__ +12 -21
  17. package/src/generators/angular-app/files/src/environments/environment.ts__tmpl__ +2 -11
  18. package/src/generators/angular-app/files/src/index.html__tmpl__ +3 -9
  19. package/src/generators/angular-app/files/src/main.ts__tmpl__ +8 -12
  20. package/src/generators/angular-app/files/src/silent-check-sso.html__tmpl__ +5 -0
  21. package/src/generators/angular-app/files/src/styles.css__tmpl__ +2 -2
  22. package/src/generators/mean/mean.d.ts +3 -0
  23. package/src/generators/mean/mean.js +30 -0
  24. package/src/generators/mean/mean.js.map +1 -0
  25. package/src/generators/mean/mean.spec.ts +38 -0
  26. package/src/generators/mean/schema.d.ts +11 -0
  27. package/src/generators/mean/schema.json +26 -0
  28. package/src/generators/angular-app/files/src/app/app.module.ts__tmpl__ +0 -47
  29. package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.css__tmpl__ +0 -13
  30. package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.html__tmpl__ +0 -12
  31. package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.spec.ts__tmpl__ +0 -33
  32. package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.ts__tmpl__ +0 -48
  33. package/src/generators/angular-app/files/src/app/auth.interceptor.ts__tmpl__ +0 -24
  34. package/src/generators/angular-app/files/src/app/logout/logout.component.html__tmpl__ +0 -1
  35. package/src/generators/angular-app/files/src/app/logout/logout.component.ts__tmpl__ +0 -9
  36. package/src/generators/angular-app/files/src/app/services/auth.service.ts__tmpl__ +0 -57
  37. package/src/generators/angular-app/files/src/app/tenant.service.ts__tmpl__ +0 -19
  38. package/src/generators/angular-app/files/src/environments/config.ts__tmpl__ +0 -21
  39. /package/src/generators/angular-app/files/{src → public}/assets/banner.jpg +0 -0
  40. /package/src/generators/angular-app/files/{src → public}/assets/github-1.svg +0 -0
  41. /package/src/generators/angular-app/files/{src → public}/favicon.ico +0 -0
package/generators.json CHANGED
@@ -43,6 +43,12 @@
43
43
  "factory": "./src/generators/angular-app/angular-app",
44
44
  "schema": "./src/generators/angular-app/schema.json",
45
45
  "description": "Generator that creates a Angular based frontend application."
46
+ },
47
+ "mean": {
48
+ "factory": "./src/generators/mean/mean",
49
+ "schema": "./src/generators/mean/schema.json",
50
+ "description": "Generator that creates a MEAN fullstack solution.",
51
+ "hidden": true
46
52
  }
47
53
  }
48
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "12.4.1",
3
+ "version": "12.5.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -56,6 +56,7 @@ function removeFiles(host, options) {
56
56
  }
57
57
  function default_1(host, options) {
58
58
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
59
+ var _a, _b;
59
60
  const normalizedOptions = yield normalizeOptions(host, options);
60
61
  const { applicationGenerator: initAngular } = yield Promise.resolve().then(() => require('@nx/angular/generators'));
61
62
  yield initAngular(host, {
@@ -64,17 +65,31 @@ function default_1(host, options) {
64
65
  linter: 'none',
65
66
  directory: `apps/${options.name}`
66
67
  });
67
- (0, devkit_1.addDependenciesToPackageJson)(host, {}, {
68
- '@abgov/angular-components': '^3.0.0',
69
- '@abgov/web-components': '^1.19.0',
70
- 'oidc-client': '~1.11.5',
71
- });
68
+ (0, devkit_1.addDependenciesToPackageJson)(host, {
69
+ '@abgov/angular-components': '5.2.1',
70
+ '@abgov/design-tokens': '1.8.0',
71
+ '@abgov/ui-components-common': '^2.0.0',
72
+ '@abgov/web-components': '1.39.3',
73
+ 'keycloak-angular': '^19.0.2',
74
+ 'keycloak-js': '^23.0.7',
75
+ 'zone.js': '~0.15.0',
76
+ }, {});
72
77
  const addedProxy = addFiles(host, normalizedOptions);
73
- removeFiles(host, normalizedOptions);
78
+ // @nx/angular generates app.ts/html/css/spec.ts (new naming) and nx-welcome.ts;
79
+ // our templates use app.component.* and don't use nx-welcome.
80
+ for (const file of ['app.ts', 'app.html', 'app.css', 'app.spec.ts', 'nx-welcome.ts']) {
81
+ host.delete(`${normalizedOptions.projectRoot}/src/app/${file}`);
82
+ }
74
83
  const layout = (0, devkit_1.getWorkspaceLayout)(host);
75
84
  const config = (0, devkit_1.readProjectConfiguration)(host, options.name);
76
- config.targets.build.options = Object.assign(Object.assign({}, config.targets.build.options), { assets: [
85
+ // Remove the generated fileReplacements for production — single environment.ts
86
+ // is pre-populated from tenant config at generation time.
87
+ if ((_b = (_a = config.targets.build.configurations) === null || _a === void 0 ? void 0 : _a.production) === null || _b === void 0 ? void 0 : _b.fileReplacements) {
88
+ delete config.targets.build.configurations.production.fileReplacements;
89
+ }
90
+ config.targets.build.options = Object.assign(Object.assign({}, config.targets.build.options), { polyfills: ['zone.js'], assets: [
77
91
  ...config.targets.build.options.assets,
92
+ `${normalizedOptions.projectRoot}/src/silent-check-sso.html`,
78
93
  {
79
94
  glob: 'nginx.conf',
80
95
  input: `${layout.appsDir}/${options.name}`,
@@ -1 +1 @@
1
- {"version":3,"file":"angular-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/angular-app/angular-app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"angular-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/angular-app/angular-app.ts"],"names":[],"mappings":";;AAkGA,4BAgFC;;AAlLD,wCAAyE;AACzE,uCAYoB;AACpB,6BAA6B;AAG7B,SAAe,gBAAgB,CAC7B,IAAU,EACV,OAAkC;;QAElC,MAAM,WAAW,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;QACjD,MAAM,WAAW,GAAG,GAAG,IAAA,2BAAkB,EAAC,IAAI,CAAC,CAAC,OAAO,IAAI,WAAW,EAAE,CAAC;QACzE,MAAM,kBAAkB,GAAG,cAAc,WAAW,EAAE,CAAC;QAEvD,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEvD,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;YAC/C,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;YACpB,CAAC,CAAC,OAAO,CAAC,KAAK;gBACf,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;gBACjB,CAAC,CAAC,EAAE,CAAC;QAEP,uCACK,OAAO,KACV,WAAW;YACX,WAAW;YACX,kBAAkB;YAClB,IAAI;YACJ,YAAY,IACZ;IACJ,CAAC;CAAA;AAED,SAAS,QAAQ,CAAC,IAAU,EAAE,OAAyB;IACrD,MAAM,eAAe,+DAChB,OAAO,GACP,OAAO,CAAC,IAAI,GACZ,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,KACtB,cAAc,EAAE,IAAA,uBAAc,EAAC,OAAO,CAAC,WAAW,CAAC,EACnD,IAAI,EAAE,EAAE,GACT,CAAC;IACF,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,WAAW,EACnB,eAAe,CAChB,CAAC;IACF,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IACrD,IAAI,YAAY,EAAE,CAAC;QACjB,mDAAmD;QACnD,6CAA6C;QAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,CAC9C,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE;YACxB,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAElD,MAAM,KAAK,GAAG;gBACZ,MAAM,EAAE,GAAG,WAAW,CAAC,QAAQ,cAC7B,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAC9C,EAAE;gBACF,MAAM,EAAE,WAAW,CAAC,QAAQ,KAAK,QAAQ;gBACzC,YAAY,EAAE,KAAK;gBACnB,WAAW,EAAE,EAAE;aAChB,CAAC;YAEF,8DAA8D;YAC9D,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpC,KAAK,CAAC,WAAW,GAAG;oBAClB,CAAC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE,WAAW,CAAC,QAAQ;iBAClD,CAAC;YACJ,CAAC;YAED,uCACK,SAAS,KACZ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAK,IAC5B;QACJ,CAAC,EACD,EAAE,CACH,CAAC;QACF,IAAA,kBAAS,EAAC,IAAI,EAAE,GAAG,OAAO,CAAC,WAAW,kBAAkB,EAAE,YAAY,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,WAAW,CAAC,IAAU,EAAE,OAAyB;IACxD,IAAI,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,mBAAmB,CAAC,CAAC;IACvD,IAAI,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,mBAAmB,CAAC,CAAC;AACzD,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAkC;;;QAC3E,MAAM,iBAAiB,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEhE,MAAM,EAAE,oBAAoB,EAAE,WAAW,EAAE,GAAG,2CAC5C,wBAAwB,EACzB,CAAC;QACF,MAAM,WAAW,CAAC,IAAI,EAAE;YACtB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,iBAAiB,CAAC,WAAW;YACrC,MAAM,EAAE,MAAM;YACd,SAAS,EAAE,QAAQ,OAAO,CAAC,IAAI,EAAE;SAClC,CAAC,CAAC;QAEH,IAAA,qCAA4B,EAC1B,IAAI,EACJ;YACE,2BAA2B,EAAE,OAAO;YACpC,sBAAsB,EAAE,OAAO;YAC/B,6BAA6B,EAAE,QAAQ;YACvC,uBAAuB,EAAE,QAAQ;YACjC,kBAAkB,EAAE,SAAS;YAC7B,aAAa,EAAE,SAAS;YACxB,SAAS,EAAE,SAAS;SACrB,EACD,EAAE,CACH,CAAC;QAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAErD,gFAAgF;QAChF,8DAA8D;QAC9D,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,CAAC,EAAE,CAAC;YACrF,IAAI,CAAC,MAAM,CAAC,GAAG,iBAAiB,CAAC,WAAW,YAAY,IAAI,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,MAAM,MAAM,GAAG,IAAA,2BAAkB,EAAC,IAAI,CAAC,CAAC;QAExC,MAAM,MAAM,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAE5D,+EAA+E;QAC/E,0DAA0D;QAC1D,IAAI,MAAA,MAAA,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,0CAAE,UAAU,0CAAE,gBAAgB,EAAE,CAAC;YACtE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,gBAAgB,CAAC;QACzE,CAAC;QAED,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,mCACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,KAC/B,SAAS,EAAE,CAAC,SAAS,CAAC,EACtB,MAAM,EAAE;gBACN,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM;gBACtC,GAAG,iBAAiB,CAAC,WAAW,4BAA4B;gBAC5D;oBACE,IAAI,EAAE,YAAY;oBAClB,KAAK,EAAE,GAAG,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE;oBAC1C,MAAM,EAAE,IAAI;iBACb;aACF,GACF,CAAC;QAEF,IAAI,UAAU,EAAE,CAAC;YACf,oEAAoE;YACpE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,mCACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,KAC/B,WAAW,EAAE,GAAG,iBAAiB,CAAC,WAAW,kBAAkB,GAChE,CAAC;QACJ,CAAC;QAED,IAAA,mCAA0B,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAEvD,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;QAExB,MAAM,IAAA,2BAAmB,EAAC,IAAI,kCACzB,iBAAiB,KACpB,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,iBAAiB,CAAC,WAAW,IACtC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,IAAA,4BAAmB,EAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;CAAA"}
@@ -1,36 +1,16 @@
1
- .container {
2
- width: 100%;
3
- margin: 0 auto;
4
- padding: 0;
1
+ .app {
2
+ margin: 0;
5
3
  }
6
4
 
7
- @media (min-width: 640px) {
8
- .container {
9
- margin: 0 auto;
10
- padding: 3rem 1rem;
11
- width: 640px;
12
- }
5
+ main {
6
+ display: grid;
7
+ grid-template-columns: repeat(6, auto);
13
8
  }
14
- @media (min-width: 768px) {
15
- .container {
16
- margin: 0 auto;
17
- padding: 3rem 1rem;
18
- width: 768px;
19
- }
20
- }
21
- @media (min-width: 1024px) {
22
- .container {
23
- padding: 3rem 1rem;
24
- margin: 0 auto;
25
- width: 1024px;
26
- }
27
- }
28
- @media (min-width: 1280px) {
29
- .container {
30
- padding: 3rem 1rem;
31
- margin: 0 auto;
32
- width: 1280px;
33
- }
9
+
10
+ section {
11
+ grid-column: 2 / span 2;
12
+ margin-top: 40px;
13
+ margin-bottom: 40px;
34
14
  }
35
15
 
36
16
  .nextSteps {
@@ -42,27 +22,3 @@
42
22
  margin-bottom: 0;
43
23
  line-height: 50px;
44
24
  }
45
-
46
- .nextSteps li button {
47
- margin: 20px 0 0 10px;
48
- }
49
-
50
-
51
- .footer {
52
- background-color: #f1f1f1;
53
- padding-top: 56px;
54
- padding-bottom: 56px;
55
- padding-left: 150px;
56
- padding-right: 150px;
57
- display: flex;
58
- flex-direction: row;
59
- flex-wrap: wrap;
60
- border-bottom: 16px solid #0081a2;
61
- }
62
-
63
- @media (max-width: 767px) {
64
- /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */
65
- .body-content {
66
- padding-top: 50px;
67
- }
68
- }
@@ -1,58 +1,27 @@
1
1
  <div class="app">
2
- <goa-microsite-header type="alpha"></goa-microsite-header>
3
- <goa-app-header heading="Digital Service Example" url="/"></goa-app-header>
4
- <goa-hero-banner
5
- backgroundUrl="../assets/banner.jpg"
6
- heading="Quick start of a digital service"
7
- >
8
- </goa-hero-banner>
9
- <main class="container">
2
+ <goab-microsite-header type="alpha"></goab-microsite-header>
3
+ <goab-app-header url="/" heading="<%= projectName %>">
4
+ <goab-button-group alignment="end">
5
+ @if (userName) { <span>{{ userName }}</span> }
6
+ @if (!isLoggedIn) {
7
+ <goab-button type="tertiary" (onClick)="login()">Sign in</goab-button>
8
+ } @else {
9
+ <goab-button type="tertiary" (onClick)="logout()">Sign out</goab-button>
10
+ }
11
+ </goab-button-group>
12
+ </goab-app-header>
13
+ <goab-hero-banner #heroBanner heading="<%= projectName %>"></goab-hero-banner>
14
+ <main>
10
15
  <section>
11
16
  <h2>Welcome to {{ title }}!</h2>
12
- <p>
13
- Don't panic. Start editing the project to build your digital service.
14
- </p>
17
+ <p>Don't panic. Start editing the project to build your digital service.</p>
15
18
  <h3>A few things you might want to do next:</h3>
16
19
  <ul class="nextSteps">
17
- <li>Create the 'my-app' client in your realm to let users sign in</li>
18
- <li>
19
- Make requests to the backend API by either updating nginx.conf or
20
- enabling CORS on the API.
21
- </li>
22
- <li>Add requests to public API resources:</li>
23
- <li>Add requests to private API resources:</li>
20
+ <li>Register the '<%= projectName %>' client in your realm and set CLIENT_SECRET.</li>
21
+ <li>Add requests to public API resources: {{ publicResource }}</li>
22
+ <li>Add requests to private API resources: {{ privateResource }}</li>
24
23
  </ul>
25
24
  </section>
26
- <div class="container-fluid">
27
- <div class="row">
28
- <h3><a routerLink="/">Home</a> | <a routerLink="/protected">Admin Area</a></h3>
29
- <div *ngIf="isLoggedIn(); else elseBlock">
30
- <goa-button (click)="logout()">
31
- Sign Out
32
- </goa-button>
33
- </div>
34
- <ng-template #elseBlock>
35
- <goa-button (click)="login()">
36
- Sign In
37
- </goa-button>
38
- </ng-template>
39
- <div class="col-sm-12 body-content">
40
- <router-outlet></router-outlet>
41
- </div>
42
- </div>
43
- </div>
25
+ <router-outlet></router-outlet>
44
26
  </main>
45
- <footer class="footer">
46
- <div class="goa-socialconnect">
47
- <div class="goa-title">Connect with us on</div>
48
- <ul>
49
- <div>
50
- <img src="./assets/github-1.svg" height="15px" />
51
- <a href="https://github.com/abgov" rel="noreferrer" target="_blank">
52
- GitHub
53
- </a>
54
- </div>
55
- </ul>
56
- </div>
57
- </footer>
58
27
  </div>
@@ -1,44 +1,39 @@
1
- import { TestBed, getTestBed } from '@angular/core/testing';
1
+ import { ElementRef } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { provideRouter } from '@angular/router';
4
+ import { GoabAppHeader, GoabButton, GoabButtonGroup, GoabHeroBanner, GoabMicrositeHeader } from '@abgov/angular-components';
5
+ import Keycloak from 'keycloak-js';
2
6
  import { AppComponent } from './app.component';
3
- import { AngularComponentsModule } from '@abgov/angular-components';
4
- import { RouterTestingModule } from '@angular/router/testing';
5
- import {
6
- BrowserDynamicTestingModule,
7
- platformBrowserDynamicTesting
8
- } from "@angular/platform-browser-dynamic/testing";
9
-
10
7
 
11
8
  describe('AppComponent', () => {
12
- beforeAll( ()=> {
13
- TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
14
- });
15
- beforeEach(async () => {
16
- let envData = {"production":false,"access":{"url":"https://testurl.com","realm":"123","client_id":"urn:ads:platform:tenant-admin-app"},"tenantApi":{"host":"http://localhost:3333","endpoints":{"tenantNameByRealm":"/api/tenant/v1/realm"}}}
9
+ const keycloakMock = {
10
+ authenticated: false,
11
+ tokenParsed: null,
12
+ login: jasmine.createSpy('login'),
13
+ logout: jasmine.createSpy('logout'),
14
+ };
17
15
 
18
- localStorage.setItem('envData', JSON.stringify(envData));
16
+ beforeEach(async () => {
19
17
  await TestBed.configureTestingModule({
20
- declarations: [AppComponent],
21
- imports: [AngularComponentsModule, RouterTestingModule],
18
+ imports: [AppComponent, GoabAppHeader, GoabButton, GoabButtonGroup, GoabHeroBanner, GoabMicrositeHeader],
19
+ providers: [
20
+ provideRouter([]),
21
+ { provide: Keycloak, useValue: keycloakMock },
22
+ { provide: ElementRef, useValue: { nativeElement: { querySelector: () => null } } },
23
+ ],
22
24
  }).compileComponents();
23
25
  });
24
26
 
25
27
  it('should create the app', () => {
26
28
  const fixture = TestBed.createComponent(AppComponent);
27
- const app = fixture.componentInstance;
28
- expect(app).toBeTruthy();
29
- });
30
-
31
- it(`should have as title '<%= projectName %>'`, () => {
32
- const fixture = TestBed.createComponent(AppComponent);
33
- const app = fixture.componentInstance;
34
- expect(app.title).toEqual('<%= projectName %>');
29
+ expect(fixture.componentInstance).toBeTruthy();
35
30
  });
36
31
 
37
- it('should render title', () => {
32
+ it('should have welcome heading', () => {
38
33
  const fixture = TestBed.createComponent(AppComponent);
39
34
  fixture.detectChanges();
40
- const compiled = fixture.nativeElement;
41
- expect(compiled.querySelector('h2').textContent).toContain(
35
+ const compiled = fixture.nativeElement as HTMLElement;
36
+ expect(compiled.querySelector('h2')?.textContent).toContain(
42
37
  'Welcome to <%= projectName %>!'
43
38
  );
44
39
  });
@@ -1,36 +1,76 @@
1
- import { Component, OnDestroy, OnInit } from '@angular/core';
2
- import { AuthService } from './services/auth.service';
3
- import { environment } from '../environments/environment'
4
-
1
+ import { AfterViewInit, Component, ElementRef, inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import { RouterOutlet } from '@angular/router';
4
+ import {
5
+ GoabAppHeader,
6
+ GoabButton,
7
+ GoabButtonGroup,
8
+ GoabHeroBanner,
9
+ GoabMicrositeHeader,
10
+ } from '@abgov/angular-components';
11
+ import Keycloak from 'keycloak-js';
12
+ import { getAccessToken } from './user.slice';
5
13
 
6
14
  @Component({
7
- selector: '<%= projectName %>-app-root',
15
+ selector: '<%= projectName %>-root',
16
+ standalone: true,
17
+ imports: [RouterOutlet, GoabAppHeader, GoabButton, GoabButtonGroup, GoabHeroBanner, GoabMicrositeHeader],
8
18
  templateUrl: './app.component.html',
9
- styleUrls: ['./app.component.css'],
19
+ styleUrl: './app.component.css',
10
20
  })
21
+ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
22
+ title = '<%= projectName %>';
11
23
 
12
- export class AppComponent implements OnInit, OnDestroy {
13
- title = "<%= projectName %>";
24
+ @ViewChild('heroBanner', { read: ElementRef, static: false }) heroBannerRef!: ElementRef;
14
25
 
15
- constructor(private authService: AuthService) {}
26
+ private http = inject(HttpClient);
27
+ private keycloak = inject(Keycloak);
28
+ private heroObserver?: MutationObserver;
16
29
 
17
- isLoggedIn(): boolean {
18
- return this.authService.isLoggedIn()
19
- }
30
+ publicResource = 'Not retrieved';
31
+ privateResource = 'Not retrieved';
32
+
33
+ get isLoggedIn(): boolean { return this.keycloak.authenticated ?? false; }
34
+ get userName(): string { return (this.keycloak.tokenParsed?.['name'] as string) ?? ''; }
20
35
 
21
- logout() {
22
- if (this.authService.isLoggedIn()) {
23
- this.authService.logout();
36
+ ngOnInit() {
37
+ this.http.get<{ message: string }>('/api/v1/public').subscribe({
38
+ next: (data) => (this.publicResource = data.message),
39
+ error: () => (this.publicResource = 'Error loading data'),
40
+ });
41
+ if (this.isLoggedIn) {
42
+ getAccessToken().then((token) => {
43
+ this.http.get<{ message: string }>('/api/v1/private', {
44
+ headers: { Authorization: `Bearer ${token}` },
45
+ }).subscribe({
46
+ next: (data) => (this.privateResource = data.message),
47
+ error: () => (this.privateResource = 'Error loading data'),
48
+ });
49
+ });
24
50
  }
25
51
  }
26
52
 
27
- login() {
28
- if (!this.authService.isLoggedIn()) {
29
- this.authService.startAuthentication();
53
+ ngAfterViewInit() {
54
+ const host = this.heroBannerRef?.nativeElement;
55
+ if (!host) return;
56
+ const setBackground = (inner: Element) => {
57
+ (inner as any).backgroundurl = '/assets/banner.jpg';
58
+ this.heroObserver?.disconnect();
59
+ };
60
+ const existing = host.querySelector('goa-hero-banner');
61
+ if (existing) {
62
+ setBackground(existing);
63
+ } else {
64
+ this.heroObserver = new MutationObserver(() => {
65
+ const inner = host.querySelector('goa-hero-banner');
66
+ if (inner) setBackground(inner);
67
+ });
68
+ this.heroObserver.observe(host, { childList: true, subtree: true });
30
69
  }
31
70
  }
32
71
 
33
- ngOnInit() {}
72
+ ngOnDestroy() { this.heroObserver?.disconnect(); }
34
73
 
35
- ngOnDestroy(): void {}
74
+ login() { this.keycloak.login(); }
75
+ logout() { this.keycloak.logout({ redirectUri: window.location.origin }); }
36
76
  }
@@ -0,0 +1,27 @@
1
+ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2
+ import { provideRouter } from '@angular/router';
3
+ import { provideHttpClient, withInterceptors } from '@angular/common/http';
4
+ import { includeBearerTokenInterceptor, provideKeycloak } from 'keycloak-angular';
5
+
6
+ import { routes } from './app.routes';
7
+ import { environment } from '../environments/environment';
8
+
9
+ export const appConfig: ApplicationConfig = {
10
+ providers: [
11
+ provideZoneChangeDetection({ eventCoalescing: true }),
12
+ provideRouter(routes),
13
+ provideHttpClient(withInterceptors([includeBearerTokenInterceptor])),
14
+ provideKeycloak({
15
+ config: {
16
+ url: environment.access.url,
17
+ realm: environment.access.realm,
18
+ clientId: environment.access.client_id,
19
+ },
20
+ initOptions: {
21
+ onLoad: 'check-sso',
22
+ pkceMethod: 'S256',
23
+ silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
24
+ },
25
+ }),
26
+ ],
27
+ };
@@ -1,34 +1,13 @@
1
- import { LogoutComponent } from './logout/logout.component';
2
- import { NgModule } from '@angular/core';
3
- import { Routes, RouterModule } from '@angular/router';
4
- import { HomeComponent } from './home/home.component';
1
+ import { Routes } from '@angular/router';
2
+ import { createAuthGuard } from 'keycloak-angular';
5
3
  import { ProtectedComponent } from './protected/protected.component';
6
- import { AuthGuardService } from './services/auth-guard.service';
7
- import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
8
4
 
9
- const routes: Routes = [
10
- {
11
- path: '',
12
- component: HomeComponent,
13
- children: [],
14
- },
15
- {
16
- path: 'protected',
17
- component: ProtectedComponent,
18
- canActivate: [AuthGuardService],
19
- },
20
- {
21
- path: 'auth-callback',
22
- component: AuthCallbackComponent,
23
- },
24
- {
25
- path: 'signout/callback',
26
- component: LogoutComponent,
27
- },
28
- ];
5
+ const authGuard = createAuthGuard(async (_route, _state, { authenticated, keycloak }) => {
6
+ if (authenticated) return true;
7
+ await keycloak.login({ redirectUri: window.location.href });
8
+ return false;
9
+ });
29
10
 
30
- @NgModule({
31
- imports: [RouterModule.forRoot(routes)],
32
- exports: [RouterModule],
33
- })
34
- export class AppRoutingModule {}
11
+ export const routes: Routes = [
12
+ { path: 'protected', component: ProtectedComponent, canActivate: [authGuard] },
13
+ ];
@@ -1,5 +1,3 @@
1
1
  <hr />
2
2
  <h1>Public Area</h1>
3
- <div>Making a Public API call to uptime endpoint</div>
4
- <div>Uptime of API in seconds: {{uptime}}</div>
5
-
3
+ <p>Public API response: {{ publicMessage() }}</p>
@@ -1,33 +1,19 @@
1
- import { Component, OnInit } from '@angular/core';
1
+ import { Component, inject, OnInit, signal } from '@angular/core';
2
2
  import { HttpClient } from '@angular/common/http';
3
3
 
4
4
  @Component({
5
- selector: '<%= projectName %>-app-logout',
5
+ selector: '<%= projectName %>-home',
6
+ standalone: true,
6
7
  templateUrl: 'home.component.html',
7
8
  })
8
9
  export class HomeComponent implements OnInit {
9
- constructor(private http: HttpClient) {}
10
- public uptime = 0;
11
-
12
- private getHealthUrl = `${this.configData().tenantApi.host}/health`;
13
-
14
- getHealth() {
15
- const uptimePromise = this.http.get(this.getHealthUrl);
16
-
17
- uptimePromise.subscribe(
18
- (data: any) => {
19
- this.uptime = data.uptime;
20
- },
21
- (err) => console.error(err),
22
- () => console.log('done loading uptime')
23
- );
24
- }
25
-
26
- configData () {
27
- return JSON.parse(localStorage.getItem('envData') || "\"\"");
28
- }
10
+ private http = inject(HttpClient);
11
+ publicMessage = signal('Not retrieved');
29
12
 
30
13
  ngOnInit() {
31
- this.getHealth();
14
+ this.http.get<{ message: string }>('/api/v1/public').subscribe({
15
+ next: (data) => this.publicMessage.set(data.message),
16
+ error: () => this.publicMessage.set('Error loading data'),
17
+ });
32
18
  }
33
19
  }
@@ -1,4 +1,4 @@
1
1
  <hr />
2
2
  <h1>Admin Area</h1>
3
- <div>Making a Protected API call to tenant endpoint using access token</div>
4
- <div>Tenant Name: {{tenant.name}}</div>
3
+ <p>Welcome, {{ userName }}.</p>
4
+ <p>Private API response: {{ privateMessage() }}</p>
@@ -1,52 +1,36 @@
1
- import { ComponentFixture, TestBed, waitForAsync, inject } from '@angular/core/testing';
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { provideHttpClient } from '@angular/common/http';
3
+ import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
4
+ import { Keycloak } from 'keycloak-angular';
2
5
  import { ProtectedComponent } from './protected.component';
3
- import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
4
- import TenantService from '../tenant.service';
5
- import {
6
- BrowserDynamicTestingModule,
7
- platformBrowserDynamicTesting
8
- } from "@angular/platform-browser-dynamic/testing";
9
6
 
10
7
  describe('ProtectedComponent', () => {
11
- let fixture: ComponentFixture<ProtectedComponent>;
12
- let httpTestingController: HttpTestingController;
13
- let envData = {"production":false,"access":{"url":"https://testurl.com","realm":"123","client_id":"urn:ads:platform:tenant-admin-app"},"tenantApi":{"host":"http://localhost:3333","endpoints":{"tenantNameByRealm":"/api/tenant/v1/realm"}}}
14
-
15
- localStorage.setItem('envData', JSON.stringify(envData));
16
- beforeAll( ()=> {
17
- TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
8
+ const keycloakMock = {
9
+ authenticated: true,
10
+ tokenParsed: { name: 'Test User' },
11
+ };
12
+
13
+ let httpTesting: HttpTestingController;
14
+
15
+ beforeEach(async () => {
16
+ await TestBed.configureTestingModule({
17
+ imports: [ProtectedComponent],
18
+ providers: [
19
+ provideHttpClient(),
20
+ provideHttpClientTesting(),
21
+ { provide: Keycloak, useValue: keycloakMock },
22
+ ],
23
+ }).compileComponents();
24
+
25
+ httpTesting = TestBed.inject(HttpTestingController);
18
26
  });
19
- beforeEach(
20
- waitForAsync(() => {
21
- TestBed.configureTestingModule({
22
- declarations: [ProtectedComponent],
23
- providers: [ProtectedComponent, TenantService],
24
- imports: [HttpClientTestingModule],
25
- }).compileComponents();
26
27
 
27
- httpTestingController = TestBed.inject(HttpTestingController);
28
- })
29
- );
28
+ afterEach(() => httpTesting.verify());
30
29
 
31
- beforeEach(() => {
32
- fixture = TestBed.createComponent(ProtectedComponent);
30
+ it('should create', () => {
31
+ const fixture = TestBed.createComponent(ProtectedComponent);
33
32
  fixture.detectChanges();
33
+ httpTesting.expectOne('/api/v1/private').flush({ message: 'Hello' });
34
+ expect(fixture.componentInstance).toBeTruthy();
34
35
  });
35
-
36
- afterEach(() => {
37
- httpTestingController.verify();
38
- });
39
-
40
- it('should create', (inject([TenantService, HttpTestingController],
41
- () => {
42
- const mockTenant = { status: 200, statusText: 'OK'}
43
- const data = { name: 'Child Services' }
44
- const req = httpTestingController.expectOne(
45
- 'http://localhost:3333/api/tenant/v1/realm/123'
46
- )
47
-
48
- expect(req.request.method).toEqual('GET');
49
-
50
- req.flush(data, mockTenant);
51
- })));
52
36
  });
@@ -1,27 +1,27 @@
1
- import { Component, OnInit } from '@angular/core';
2
- import TenantService from '../tenant.service';
1
+ import { Component, inject, OnInit, signal } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import Keycloak from 'keycloak-js';
3
4
 
4
5
  @Component({
5
- selector: 'app-protected',
6
+ selector: '<%= projectName %>-protected',
7
+ standalone: true,
6
8
  templateUrl: './protected.component.html',
7
- styleUrls: ['./protected.component.css'],
9
+ styleUrl: './protected.component.css',
8
10
  })
9
11
  export class ProtectedComponent implements OnInit {
10
- constructor(private _tenantService: TenantService) {}
12
+ private http = inject(HttpClient);
13
+ private keycloak = inject(Keycloak);
11
14
 
12
- public tenant = { name: '' };
15
+ privateMessage = signal('Not retrieved');
13
16
 
14
- getTenant() {
15
- this._tenantService.getTenant().subscribe(
16
- (data: any) => {
17
- this.tenant = data.tenant;
18
- },
19
- (err) => console.error(err),
20
- () => console.log('done loading tenant')
21
- );
17
+ get userName(): string {
18
+ return (this.keycloak.tokenParsed?.['name'] as string) ?? '';
22
19
  }
23
20
 
24
21
  ngOnInit() {
25
- this.getTenant();
22
+ this.http.get<{ message: string }>('/api/v1/private').subscribe({
23
+ next: (data) => this.privateMessage.set(data.message),
24
+ error: () => this.privateMessage.set('Error loading data'),
25
+ });
26
26
  }
27
27
  }
@@ -1,21 +1,12 @@
1
- import { Injectable } from '@angular/core';
2
- import { CanActivate } from '@angular/router';
3
- import { AuthService } from './auth.service';
4
-
5
- @Injectable({
6
- providedIn: 'root'
7
- })
8
- export class AuthGuardService implements CanActivate {
9
-
10
- constructor(private authService: AuthService) { }
11
-
12
- canActivate(): boolean {
13
- if (this.authService.isLoggedIn()) {
14
- return true;
15
- }
16
-
17
- console.log('start authentication...');
18
- this.authService.startAuthentication();
19
- return false;
20
- }
21
- }
1
+ // Auth guard is configured in app.routes.ts using keycloak-angular's createAuthGuard.
2
+ // Add this file if you need a reusable, injectable guard:
3
+ //
4
+ // import { Injectable } from '@angular/core';
5
+ // import { Router } from '@angular/router';
6
+ // import { createAuthGuard } from 'keycloak-angular';
7
+ //
8
+ // export const authGuard = createAuthGuard(async ({ keycloak }) => {
9
+ // if (keycloak.authenticated) return true;
10
+ // await keycloak.login({ redirectUri: window.location.href });
11
+ // return false;
12
+ // });
@@ -1,17 +1,8 @@
1
- // This file can be replaced during build by using the `fileReplacements` array.
2
- // When building for production, this file is replaced with `environment.prod.ts`.
3
-
4
1
  export const environment = {
5
2
  production: false,
6
3
  access: {
7
- url: '<%= accessServiceUrl %>',
4
+ url: '<%= accessServiceUrl %>/auth',
8
5
  realm: '<%= tenantRealm %>',
9
- client_id: 'urn:ads:<%= tenant %>:<%= projectName %>'
6
+ client_id: 'urn:ads:<%= tenant %>:<%= projectName %>',
10
7
  },
11
- tenantApi: {
12
- host: "http://localhost:3333",
13
- endpoints: {
14
- tenantNameByRealm: "/api/tenant/v1/realm",
15
- }
16
- }
17
8
  };
@@ -6,16 +6,10 @@
6
6
  <base href="/" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1" />
8
8
  <link rel="icon" type="image/x-icon" href="favicon.ico" />
9
- <script
10
- type="module"
11
- src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"
12
- ></script>
13
- <script
14
- nomodule
15
- src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"
16
- ></script>
9
+ <script type="module" src="https://cdn.jsdelivr.net/npm/ionicons@latest/dist/ionicons/ionicons.esm.js"></script>
10
+ <script nomodule src="https://cdn.jsdelivr.net/npm/ionicons@latest/dist/ionicons/ionicons.js"></script>
17
11
  </head>
18
12
  <body>
19
- <<%= projectName %>-app-root></<%= projectName %>-app-root>
13
+ <<%= projectName %>-root></<%= projectName %>-root>
20
14
  </body>
21
15
  </html>
@@ -1,13 +1,9 @@
1
- import { enableProdMode } from '@angular/core';
2
- import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
1
+ import 'zone.js';
2
+ import '@abgov/web-components';
3
+ import { bootstrapApplication } from '@angular/platform-browser';
4
+ import { AppComponent } from './app/app.component';
5
+ import { appConfig } from './app/app.config';
3
6
 
4
- import { AppModule } from './app/app.module';
5
- import { environment } from './environments/environment';
6
-
7
- if (environment.production) {
8
- enableProdMode();
9
- }
10
-
11
- platformBrowserDynamic()
12
- .bootstrapModule(AppModule)
13
- .catch((err) => console.error(err));
7
+ bootstrapApplication(AppComponent, appConfig).catch((err) =>
8
+ console.error(err)
9
+ );
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <body>
3
+ <script>parent.postMessage(location.href, location.origin);</script>
4
+ </body>
5
+ </html>
@@ -1,6 +1,6 @@
1
- /* You can add global styles to this file, and also import other style files */
1
+ @import "@abgov/design-tokens/dist/tokens.css";
2
2
  @import "@abgov/web-components/index.css";
3
3
 
4
4
  body {
5
- margin: 0px;
5
+ margin: 0;
6
6
  }
@@ -0,0 +1,3 @@
1
+ import { Tree } from '@nx/devkit';
2
+ import { Schema } from './schema';
3
+ export default function (host: Tree, options: Schema): Promise<() => void>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const tslib_1 = require("tslib");
5
+ const devkit_1 = require("@nx/devkit");
6
+ const nx_oc_1 = require("@abgov/nx-oc");
7
+ const angular_app_1 = require("../angular-app/angular-app");
8
+ const express_service_1 = require("../express-service/express-service");
9
+ function normalizeOptions(host, options) {
10
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
11
+ const adsp = yield (0, nx_oc_1.getAdspConfiguration)(host, options);
12
+ return Object.assign(Object.assign({}, options), { adsp });
13
+ });
14
+ }
15
+ function default_1(host, options) {
16
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
17
+ const normalizedOptions = yield normalizeOptions(host, options);
18
+ const projectName = (0, devkit_1.names)(options.name).fileName;
19
+ yield (0, express_service_1.default)(host, Object.assign(Object.assign({}, normalizedOptions), { name: `${projectName}-service` }));
20
+ yield (0, angular_app_1.default)(host, Object.assign(Object.assign({}, normalizedOptions), { name: `${projectName}-app`, proxy: {
21
+ location: '/api/',
22
+ proxyPass: `http://${projectName}-service:3333/${projectName}-service/`,
23
+ } }));
24
+ yield (0, devkit_1.formatFiles)(host);
25
+ return () => {
26
+ (0, devkit_1.installPackagesTask)(host);
27
+ };
28
+ });
29
+ }
30
+ //# sourceMappingURL=mean.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mean.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/mean/mean.ts"],"names":[],"mappings":";;AAcA,4BAuBC;;AArCD,uCAA2E;AAC3E,wCAAoD;AACpD,4DAAwD;AACxD,wEAAoE;AAGpE,SAAe,gBAAgB,CAC7B,IAAU,EACV,OAAe;;QAEf,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvD,uCAAY,OAAO,KAAE,IAAI,IAAG;IAC9B,CAAC;CAAA;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChE,MAAM,WAAW,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;QAEjD,MAAM,IAAA,yBAAkB,EAAC,IAAI,kCACxB,iBAAiB,KACpB,IAAI,EAAE,GAAG,WAAW,UAAU,IAC9B,CAAC;QAEH,MAAM,IAAA,qBAAc,EAAC,IAAI,kCACpB,iBAAiB,KACpB,IAAI,EAAE,GAAG,WAAW,MAAM,EAC1B,KAAK,EAAE;gBACL,QAAQ,EAAE,OAAO;gBACjB,SAAS,EAAE,UAAU,WAAW,iBAAiB,WAAW,WAAW;aACxE,IACD,CAAC;QAEH,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;QAExB,OAAO,GAAG,EAAE;YACV,IAAA,4BAAmB,EAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;CAAA"}
@@ -0,0 +1,38 @@
1
+ import { readProjectConfiguration } from '@nx/devkit';
2
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
3
+ import * as utils from '@abgov/nx-oc';
4
+ import { environments } from '@abgov/nx-oc';
5
+ import { Schema } from './schema';
6
+ import generator from './mean';
7
+
8
+ jest.mock('@abgov/nx-oc');
9
+ const utilsMock = utils as jest.Mocked<typeof utils>;
10
+ utilsMock.getAdspConfiguration.mockResolvedValue({
11
+ tenant: 'test',
12
+ tenantRealm: 'test',
13
+ accessServiceUrl: environments.test.accessServiceUrl,
14
+ directoryServiceUrl: environments.test.directoryServiceUrl,
15
+ });
16
+ utilsMock.deploymentGenerator.mockResolvedValue(undefined);
17
+
18
+ describe('MEAN Generator', () => {
19
+ const options: Schema = {
20
+ name: 'test',
21
+ env: 'dev',
22
+ };
23
+
24
+ it('can run', async () => {
25
+ const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
26
+ await generator(host, options);
27
+
28
+ const appConfig = readProjectConfiguration(host, 'test-app');
29
+ expect(appConfig.root).toBe('apps/test-app');
30
+
31
+ const serviceConfig = readProjectConfiguration(host, 'test-service');
32
+ expect(serviceConfig.root).toBe('apps/test-service');
33
+
34
+ expect(host.exists('apps/test-app/nginx.conf')).toBeTruthy();
35
+ const nginxConf = host.read('apps/test-app/nginx.conf').toString();
36
+ expect(nginxConf).toContain('http://test-service:3333/');
37
+ }, 30000);
38
+ });
@@ -0,0 +1,11 @@
1
+ import { AdspConfiguration, EnvironmentName } from '@abgov/nx-oc';
2
+
3
+ export interface Schema {
4
+ name: string;
5
+ env: EnvironmentName;
6
+ accessToken?: string;
7
+ }
8
+
9
+ export interface NormalizedSchema extends Schema {
10
+ adsp: AdspConfiguration;
11
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "$id": "mean",
5
+ "title": "MEAN fullstack generator",
6
+ "type": "object",
7
+ "properties": {
8
+ "name": {
9
+ "type": "string",
10
+ "description": "Name of the project.",
11
+ "$default": { "$source": "argv", "index": 0 },
12
+ "x-prompt": "What name would you like to use for the project?"
13
+ },
14
+ "env": {
15
+ "type": "string",
16
+ "description": "ADSP environment to initialize the project for.",
17
+ "enum": ["dev", "test", "prod"],
18
+ "default": "prod"
19
+ },
20
+ "accessToken": {
21
+ "type": "string",
22
+ "description": "Access token for ADSP tenant configuration (skips interactive login)."
23
+ }
24
+ },
25
+ "required": ["name"]
26
+ }
@@ -1,47 +0,0 @@
1
- import { NgModule, APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2
- import { BrowserModule } from '@angular/platform-browser';
3
- import "@abgov/web-components";
4
- import { AngularComponentsModule } from '@abgov/angular-components';
5
-
6
- import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
7
- import { AuthInterceptor } from './auth.interceptor';
8
- import { FormsModule } from '@angular/forms';
9
- import { AppRoutingModule } from './app.routes';
10
- import { ProtectedComponent } from './protected/protected.component';
11
- import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
12
- import { AuthGuardService } from './services/auth-guard.service';
13
- import { AuthService } from './services/auth.service';
14
- import { AppComponent } from './app.component';
15
- import { Config } from '../environments/config';
16
-
17
- @NgModule({
18
- imports: [
19
- AngularComponentsModule,
20
- BrowserModule,
21
- HttpClientModule,
22
- FormsModule,
23
- AppRoutingModule,
24
- ],
25
- providers: [
26
- AuthGuardService,
27
- AuthService,
28
- Config,
29
- {
30
- provide: APP_INITIALIZER,
31
- useFactory: initializeApp,
32
- deps: [Config],
33
- multi: true,
34
- },
35
- { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
36
- ],
37
- declarations: [AppComponent, AuthCallbackComponent, ProtectedComponent],
38
- bootstrap: [AppComponent],
39
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
40
- })
41
- export class AppModule {}
42
-
43
- export function initializeApp(config: Config) {
44
- return (): Promise<any> => {
45
- return config.Init();
46
- };
47
- }
@@ -1,13 +0,0 @@
1
- .info {
2
- display: flex;
3
- flex-direction: column;
4
- justify-content: center;
5
- }
6
-
7
- .token {
8
- font-family: monospace;
9
- background-color: beige;
10
- width: 600px;
11
- text-align: left;
12
- word-break: break-all;
13
- }
@@ -1,12 +0,0 @@
1
- <h3>Redirected to auth-callback route!</h3>
2
- <p>Now you are logged in and allowed to access the "protected" route...</p>
3
- <div class="info">
4
- <div>
5
- <div>Token Type:</div>
6
- <div class="token">{{ tokenType }}</div>
7
- </div>
8
- <div>
9
- <div>Token:</div>
10
- <div class="token">{{ accessToken }}</div>
11
- </div>
12
- </div>
@@ -1,33 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { AuthCallbackComponent } from './auth-callback.component';
3
- import {
4
- BrowserDynamicTestingModule,
5
- platformBrowserDynamicTesting
6
- } from "@angular/platform-browser-dynamic/testing";
7
-
8
- describe('AuthCallbackComponent', () => {
9
- let component: AuthCallbackComponent;
10
- let fixture: ComponentFixture<AuthCallbackComponent>;
11
- let envData = {"production":false,"access":{"url":"https://testurl.com","realm":"123","client_id":"urn:ads:platform:tenant-admin-app"},"tenantApi":{"host":"http://localhost:3333","endpoints":{"tenantNameByRealm":"/api/tenant/v1/realm"}}}
12
-
13
- localStorage.setItem('envData', JSON.stringify(envData));
14
-
15
- beforeEach((() => {
16
- TestBed.initTestEnvironment(
17
- BrowserDynamicTestingModule,
18
- platformBrowserDynamicTesting()
19
- );
20
-
21
- TestBed.configureTestingModule({
22
- declarations: [AuthCallbackComponent],
23
- }).compileComponents();
24
-
25
- fixture = TestBed.createComponent(AuthCallbackComponent);
26
- component = fixture.componentInstance;
27
- fixture.detectChanges();
28
- }));
29
-
30
- it('should create', () => {
31
- expect(component).toBeTruthy();
32
- });
33
- });
@@ -1,48 +0,0 @@
1
- import { Injectable, Component, OnInit } from '@angular/core';
2
- import { AuthService } from '../services/auth.service';
3
- import { User } from 'oidc-client';
4
-
5
- @Component({
6
- selector: '<%= projectName %>-app-auth-callback',
7
- templateUrl: './auth-callback.component.html',
8
- styleUrls: ['./auth-callback.component.css'],
9
- })
10
- @Injectable({
11
- providedIn: 'root',
12
- })
13
- export class AuthCallbackComponent implements OnInit {
14
- public tokenType = '';
15
- public accessToken = '';
16
-
17
- constructor(private authService: AuthService) {}
18
-
19
- ngOnInit() {
20
- this.getAuth();
21
- }
22
-
23
- getAuth() {
24
- if (!this.authService.isLoggedIn()) {
25
- this.authService
26
- .completeAuthentication()
27
- .catch((error) => {
28
- console.error(`could not complete authentication: ${error}`);
29
- })
30
- .then(() => {
31
- this.setTokens();
32
- });
33
- }
34
- }
35
-
36
- setTokens() {
37
- if (this.authService.isLoggedIn()) {
38
- const user: User | null = this.authService.getUser();
39
- if (user) {
40
- this.accessToken = user.access_token;
41
- this.tokenType = user.token_type;
42
- }
43
- return true;
44
- } else {
45
- return false;
46
- }
47
- }
48
- }
@@ -1,24 +0,0 @@
1
- import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
2
- import { Injectable } from '@angular/core';
3
- import { Observable } from 'rxjs';
4
- import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
5
-
6
- @Injectable({ providedIn: 'root' })
7
- export class AuthInterceptor implements HttpInterceptor {
8
-
9
- constructor(private authCallBackComponent: AuthCallbackComponent) { }
10
-
11
- intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
12
- if (this.authCallBackComponent.setTokens()) {
13
- req = req.clone({
14
- setHeaders: {
15
- 'Content-Type' : 'application/json; charset=utf-8',
16
- 'Accept' : 'application/json',
17
- 'Authorization': `Bearer ${this.authCallBackComponent.accessToken}`,
18
- },
19
- });
20
- }
21
-
22
- return next.handle(req);
23
- }
24
- }
@@ -1,9 +0,0 @@
1
- import { Component } from '@angular/core';
2
-
3
- @Component({
4
- selector: '<%= projectName %>-app-logout',
5
- templateUrl: 'logout.component.html',
6
- })
7
- export class LogoutComponent {
8
- isAuthenticated = false;
9
- }
@@ -1,57 +0,0 @@
1
- import { Injectable } from '@angular/core';
2
- import { UserManager, UserManagerSettings, User } from 'oidc-client';
3
-
4
- @Injectable({
5
- providedIn: 'root',
6
- })
7
- export class AuthService {
8
- private manager = new UserManager(getClientSettings());
9
- private user: User | null = null;
10
-
11
- constructor() {
12
- this.manager.getUser().then((user) => {
13
- this.user = user;
14
- });
15
- }
16
-
17
- isLoggedIn(): boolean {
18
- return this.user != null && !this.user.expired;
19
- }
20
-
21
- getUser(): User | null {
22
- return this.user;
23
- }
24
-
25
- startAuthentication(): Promise<void> {
26
- return this.manager.signinRedirect();
27
- }
28
-
29
- completeAuthentication(): Promise<void> {
30
- return this.manager.signinRedirectCallback().then((user) => {
31
- this.user = user;
32
- console.log(JSON.stringify(this.user));
33
- });
34
- }
35
-
36
- logout() {
37
- return this.manager.signoutRedirect();
38
- }
39
- }
40
-
41
- export function getClientSettings(): UserManagerSettings {
42
- const data = JSON.parse(localStorage.getItem('envData') || "\"\"");
43
- const appUrl = `${window.location.protocol}//${window.location.hostname}${
44
- window.location.port ? `:${window.location.port}` : ''
45
- }`;
46
-
47
- const settings = {
48
- client_id: data.access.client_id,
49
- redirect_uri: `${appUrl}/auth-callback`,
50
- post_logout_redirect_uri: `${appUrl}/signout/callback`,
51
- silent_redirect_uri: `${appUrl}/auth-callback`,
52
- response_type: 'code',
53
- authority: `${data.access.url}/auth/realms/${data.access.realm}`,
54
- automaticSilentRenew: true,
55
- };
56
- return settings;
57
- }
@@ -1,19 +0,0 @@
1
- import { Injectable } from '@angular/core';
2
- import { HttpClient } from '@angular/common/http';
3
-
4
- @Injectable({ providedIn: 'root' })
5
- export default class TenantService {
6
-
7
- private getTenantNameUrl = `${this.configData().tenantApi.host}${this.configData().tenantApi.endpoints.tenantNameByRealm}/${this.configData().access.realm}`;
8
-
9
- constructor(private http: HttpClient) {}
10
-
11
- /** GET tenants from the server */
12
- getTenant() {
13
- return this.http.get(this.getTenantNameUrl);
14
- }
15
-
16
- configData () {
17
- return JSON.parse(localStorage.getItem('envData') || "\"\"");
18
- }
19
- }
@@ -1,21 +0,0 @@
1
- import { environment } from './environment';
2
- import { Injectable } from '@angular/core';
3
-
4
- @Injectable()
5
- export class Config {
6
- constructor() {}
7
-
8
- Init() {
9
- return new Promise<void>((resolve, reject) => {
10
- console.log("AppInitService.init() called");
11
-
12
- fetch('/config/config.json')
13
- .then((res) => {
14
- return (res.ok ? res.json() : environment)
15
- }).then(( envData ) => {
16
- localStorage.setItem('envData', JSON.stringify(envData));
17
- resolve();
18
- })
19
- });
20
- }
21
- }