@abgov/nx-adsp 12.4.1 → 12.6.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/generators.json +6 -0
- package/package.json +1 -1
- package/src/generators/angular-app/angular-app.js +22 -7
- package/src/generators/angular-app/angular-app.js.map +1 -1
- package/src/generators/angular-app/files/AGENTS.md__tmpl__ +96 -0
- package/src/generators/angular-app/files/src/app/app.component.css__tmpl__ +10 -54
- package/src/generators/angular-app/files/src/app/app.component.html__tmpl__ +18 -49
- package/src/generators/angular-app/files/src/app/app.component.spec.ts__tmpl__ +22 -27
- package/src/generators/angular-app/files/src/app/app.component.ts__tmpl__ +60 -20
- package/src/generators/angular-app/files/src/app/app.config.ts__tmpl__ +27 -0
- package/src/generators/angular-app/files/src/app/app.routes.ts__tmpl__ +10 -31
- package/src/generators/angular-app/files/src/app/home/home.component.html__tmpl__ +1 -3
- package/src/generators/angular-app/files/src/app/home/home.component.ts__tmpl__ +9 -23
- package/src/generators/angular-app/files/src/app/protected/protected.component.html__tmpl__ +2 -2
- package/src/generators/angular-app/files/src/app/protected/protected.component.spec.ts__tmpl__ +27 -43
- package/src/generators/angular-app/files/src/app/protected/protected.component.ts__tmpl__ +15 -15
- package/src/generators/angular-app/files/src/app/services/auth-guard.service.ts__tmpl__ +12 -21
- package/src/generators/angular-app/files/src/environments/environment.ts__tmpl__ +2 -11
- package/src/generators/angular-app/files/src/index.html__tmpl__ +3 -9
- package/src/generators/angular-app/files/src/main.ts__tmpl__ +8 -12
- package/src/generators/angular-app/files/src/silent-check-sso.html__tmpl__ +5 -0
- package/src/generators/angular-app/files/src/styles.css__tmpl__ +2 -2
- package/src/generators/express-service/files/AGENTS.md__tmpl__ +76 -0
- package/src/generators/mean/mean.d.ts +3 -0
- package/src/generators/mean/mean.js +30 -0
- package/src/generators/mean/mean.js.map +1 -0
- package/src/generators/mean/mean.spec.ts +38 -0
- package/src/generators/mean/schema.d.ts +11 -0
- package/src/generators/mean/schema.json +26 -0
- package/src/generators/react-app/files/AGENTS.md__tmpl__ +76 -0
- package/src/generators/angular-app/files/src/app/app.module.ts__tmpl__ +0 -47
- package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.css__tmpl__ +0 -13
- package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.html__tmpl__ +0 -12
- package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.spec.ts__tmpl__ +0 -33
- package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.ts__tmpl__ +0 -48
- package/src/generators/angular-app/files/src/app/auth.interceptor.ts__tmpl__ +0 -24
- package/src/generators/angular-app/files/src/app/logout/logout.component.html__tmpl__ +0 -1
- package/src/generators/angular-app/files/src/app/logout/logout.component.ts__tmpl__ +0 -9
- package/src/generators/angular-app/files/src/app/services/auth.service.ts__tmpl__ +0 -57
- package/src/generators/angular-app/files/src/app/tenant.service.ts__tmpl__ +0 -19
- package/src/generators/angular-app/files/src/environments/config.ts__tmpl__ +0 -21
- /package/src/generators/angular-app/files/{src → public}/assets/banner.jpg +0 -0
- /package/src/generators/angular-app/files/{src → public}/assets/github-1.svg +0 -0
- /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
|
@@ -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': '
|
|
69
|
-
'@abgov/
|
|
70
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
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":";;AAkGA,
|
|
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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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# AGENTS.md — <%= projectName %>
|
|
2
|
+
|
|
3
|
+
Angular 19 frontend for the Alberta Digital Service Platform (ADSP).
|
|
4
|
+
Generated by `nx g @abgov/nx-adsp:angular-app`.
|
|
5
|
+
|
|
6
|
+
## Stack
|
|
7
|
+
|
|
8
|
+
- **UI**: Angular 19 standalone + GoA design system (`@abgov/angular-components` — `Goab*` components)
|
|
9
|
+
- **Auth**: `keycloak-angular` with `keycloak-js`
|
|
10
|
+
- **Router**: Angular Router with `createAuthGuard` from `keycloak-angular`
|
|
11
|
+
|
|
12
|
+
## Key files
|
|
13
|
+
|
|
14
|
+
| File | Purpose |
|
|
15
|
+
|------|---------|
|
|
16
|
+
| `src/main.ts` | Entry — bootstraps `AppComponent` with `appConfig`, imports `zone.js` |
|
|
17
|
+
| `src/app/app.config.ts` | `provideKeycloak` (PKCE, silent SSO), `provideRouter`, `provideHttpClient` |
|
|
18
|
+
| `src/app/app.component.ts` | Shell — auth state, hero banner background workaround, public API call |
|
|
19
|
+
| `src/app/app.routes.ts` | Routes — `/protected` with `createAuthGuard` |
|
|
20
|
+
| `src/app/protected/protected.component.ts` | Protected route — shows authenticated user info |
|
|
21
|
+
| `src/environments/environment.ts` | Access URL, realm, client ID — pre-set from ADSP tenant |
|
|
22
|
+
|
|
23
|
+
## Auth pattern
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import Keycloak from 'keycloak-js';
|
|
27
|
+
import { inject } from '@angular/core';
|
|
28
|
+
|
|
29
|
+
private keycloak = inject(Keycloak); // provided by provideKeycloak in app.config.ts
|
|
30
|
+
|
|
31
|
+
this.keycloak.authenticated // boolean
|
|
32
|
+
this.keycloak.tokenParsed?.['name'] // user display name
|
|
33
|
+
this.keycloak.login()
|
|
34
|
+
this.keycloak.logout({ redirectUri: window.location.origin })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Route guard using `createAuthGuard` from `keycloak-angular`:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const authGuard = createAuthGuard(async (_route, _state, { authenticated, keycloak }) => {
|
|
41
|
+
if (authenticated) return true;
|
|
42
|
+
await keycloak.login({ redirectUri: window.location.href });
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## GoA design system
|
|
48
|
+
|
|
49
|
+
Components are imported from `@abgov/angular-components` with the `Goab` prefix
|
|
50
|
+
and added to each standalone component's `imports` array:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { GoabButton, GoabAppHeader } from '@abgov/angular-components';
|
|
54
|
+
|
|
55
|
+
@Component({ imports: [GoabButton, GoabAppHeader], ... })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Known limitation:** `GoabHeroBanner.backgroundUrl` does not bind reliably via
|
|
59
|
+
Angular's template binding due to an `@if(isReady)` timing issue inside the
|
|
60
|
+
component wrapper. Use `MutationObserver` to set the property directly after
|
|
61
|
+
the inner `<goa-hero-banner>` element appears — see `app.component.ts` for the
|
|
62
|
+
established pattern.
|
|
63
|
+
|
|
64
|
+
## Backend API calls (mean / proxy setup)
|
|
65
|
+
|
|
66
|
+
If `proxy.conf.json` exists at the project root, API calls are proxied to the
|
|
67
|
+
backend service in development. Use relative `/api/` paths — do not hardcode
|
|
68
|
+
the service URL:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// ✓ correct — works through proxy in dev, nginx in production
|
|
72
|
+
this.http.get('/api/v1/my-resource')
|
|
73
|
+
|
|
74
|
+
// ✗ wrong — bypasses proxy, won't work in production
|
|
75
|
+
this.http.get('http://localhost:3333/my-service/v1/my-resource')
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`includeBearerTokenInterceptor` (configured in `app.config.ts`) automatically
|
|
79
|
+
attaches the Keycloak access token to all outgoing HTTP requests.
|
|
80
|
+
In production, `nginx.conf` contains the same proxy rule routing `/api/` to
|
|
81
|
+
the backend service hostname.
|
|
82
|
+
|
|
83
|
+
## Adding a new route
|
|
84
|
+
|
|
85
|
+
1. Create `src/app/my-feature/my-feature.component.ts` as a standalone component
|
|
86
|
+
2. Add the route to `src/app/app.routes.ts`
|
|
87
|
+
3. Add required `Goab*` components to the new component's `imports` array
|
|
88
|
+
|
|
89
|
+
## What NOT to change
|
|
90
|
+
|
|
91
|
+
- `app.config.ts` — `provideKeycloak` initialises keycloak-js once; do not
|
|
92
|
+
create a second Keycloak instance elsewhere
|
|
93
|
+
- `silent-check-sso.html` — served from `public/`; required for keycloak-js
|
|
94
|
+
silent SSO check on page load
|
|
95
|
+
- `environments/environment.ts` — access URL and realm are pre-configured for
|
|
96
|
+
the ADSP tenant; override at runtime via environment variables
|
|
@@ -1,36 +1,16 @@
|
|
|
1
|
-
.
|
|
2
|
-
|
|
3
|
-
margin: 0 auto;
|
|
4
|
-
padding: 0;
|
|
1
|
+
.app {
|
|
2
|
+
margin: 0;
|
|
5
3
|
}
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
padding: 3rem 1rem;
|
|
11
|
-
width: 640px;
|
|
12
|
-
}
|
|
5
|
+
main {
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-columns: repeat(6, auto);
|
|
13
8
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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>
|
|
18
|
-
<li>
|
|
19
|
-
|
|
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
|
-
<
|
|
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 {
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const keycloakMock = {
|
|
10
|
+
authenticated: false,
|
|
11
|
+
tokenParsed: null,
|
|
12
|
+
login: jasmine.createSpy('login'),
|
|
13
|
+
logout: jasmine.createSpy('logout'),
|
|
14
|
+
};
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
beforeEach(async () => {
|
|
19
17
|
await TestBed.configureTestingModule({
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
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')
|
|
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 {
|
|
3
|
-
import {
|
|
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 %>-
|
|
15
|
+
selector: '<%= projectName %>-root',
|
|
16
|
+
standalone: true,
|
|
17
|
+
imports: [RouterOutlet, GoabAppHeader, GoabButton, GoabButtonGroup, GoabHeroBanner, GoabMicrositeHeader],
|
|
8
18
|
templateUrl: './app.component.html',
|
|
9
|
-
|
|
19
|
+
styleUrl: './app.component.css',
|
|
10
20
|
})
|
|
21
|
+
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
22
|
+
title = '<%= projectName %>';
|
|
11
23
|
|
|
12
|
-
|
|
13
|
-
title = "<%= projectName %>";
|
|
24
|
+
@ViewChild('heroBanner', { read: ElementRef, static: false }) heroBannerRef!: ElementRef;
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
private http = inject(HttpClient);
|
|
27
|
+
private keycloak = inject(Keycloak);
|
|
28
|
+
private heroObserver?: MutationObserver;
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
72
|
+
ngOnDestroy() { this.heroObserver?.disconnect(); }
|
|
34
73
|
|
|
35
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
})
|
|
34
|
-
export class AppRoutingModule {}
|
|
11
|
+
export const routes: Routes = [
|
|
12
|
+
{ path: 'protected', component: ProtectedComponent, canActivate: [authGuard] },
|
|
13
|
+
];
|
|
@@ -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 %>-
|
|
5
|
+
selector: '<%= projectName %>-home',
|
|
6
|
+
standalone: true,
|
|
6
7
|
templateUrl: 'home.component.html',
|
|
7
8
|
})
|
|
8
9
|
export class HomeComponent implements OnInit {
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
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
|
}
|