@devstroupe/devkit-cli 1.2.0-beta.3 → 1.2.0-beta.5

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.
@@ -4,6 +4,7 @@ import { provideHttpClient, withInterceptors } from '@angular/common/http';
4
4
  import { provideSpartanHlm } from '@spartan-ng/helm/utils';
5
5
  import { routes } from './app.routes';
6
6
  import { tenantInterceptor } from './shared/interceptors/tenant.interceptor';
7
+ import { authInterceptor } from './shared/interceptors/auth.interceptor';
7
8
  import { DEVKIT_AUTH_SERVICE } from '@devstroupe/devkit-angular';
8
9
  import { AuthService } from './core/services/auth.service';
9
10
 
@@ -36,7 +37,7 @@ export const appConfig: ApplicationConfig = {
36
37
  provideRouter(routes),
37
38
  { provide: DEVKIT_AUTH_SERVICE, useExisting: AuthService },
38
39
  provideHttpClient(
39
- withInterceptors([tenantInterceptor])
40
+ withInterceptors([tenantInterceptor, authInterceptor])
40
41
  ),
41
42
  provideIcons({
42
43
  lucideMenu,
@@ -0,0 +1,36 @@
1
+ import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
2
+ import { inject } from '@angular/core';
3
+ import { Router } from '@angular/router';
4
+ import { catchError, throwError } from 'rxjs';
5
+ import { AuthService } from '../../core/services/auth.service';
6
+
7
+ const PUBLIC_AUTH_ENDPOINTS = [
8
+ '/api/core/auth/login',
9
+ '/api/core/auth/register',
10
+ ];
11
+
12
+ function isPublicAuthRequest(url: string): boolean {
13
+ const path = url.split('?')[0];
14
+ return PUBLIC_AUTH_ENDPOINTS.some((endpoint) => path.endsWith(endpoint));
15
+ }
16
+
17
+ export const authInterceptor: HttpInterceptorFn = (req, next) => {
18
+ const authService = inject(AuthService);
19
+ const router = inject(Router);
20
+
21
+ return next(req).pipe(
22
+ catchError((error: unknown) => {
23
+ if (
24
+ error instanceof HttpErrorResponse
25
+ && error.status === 401
26
+ && authService.isAuthenticated
27
+ && !isPublicAuthRequest(req.url)
28
+ ) {
29
+ authService.logout();
30
+ void router.navigate(['/login']);
31
+ }
32
+
33
+ return throwError(() => error);
34
+ }),
35
+ );
36
+ };
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = __importDefault(require("node:path"));
7
9
  const node_test_1 = require("node:test");
8
10
  const nest_1 = require("./nest");
9
11
  const functional_module_templates_1 = require("./nest/functional-module.templates");
@@ -45,4 +47,15 @@ const playground_template_1 = require("./ui/playground.template");
45
47
  strict_1.default.match(playground.html, /<hlm-dialog-content \*hlmDialogPortal>/);
46
48
  strict_1.default.match(playground.html, /<hlm-alert-dialog-content \*hlmAlertDialogPortal>/);
47
49
  });
50
+ (0, node_test_1.it)('generates global unauthorized-session handling', () => {
51
+ const appRoot = node_path_1.default.resolve(__dirname, '../boilerplates/angular-template/src/app');
52
+ const appConfig = (0, node_fs_1.readFileSync)(node_path_1.default.join(appRoot, 'app.config.ts'), 'utf8');
53
+ const interceptor = (0, node_fs_1.readFileSync)(node_path_1.default.join(appRoot, 'shared/interceptors/auth.interceptor.ts'), 'utf8');
54
+ strict_1.default.match(appConfig, /withInterceptors\(\[tenantInterceptor, authInterceptor\]\)/);
55
+ strict_1.default.match(interceptor, /error\.status === 401/);
56
+ strict_1.default.match(interceptor, /authService\.isAuthenticated/);
57
+ strict_1.default.match(interceptor, /authService\.logout\(\)/);
58
+ strict_1.default.match(interceptor, /router\.navigate\(\['\/login'\]\)/);
59
+ strict_1.default.match(interceptor, /PUBLIC_AUTH_ENDPOINTS/);
60
+ });
48
61
  });
@@ -93,6 +93,9 @@ function assertValidTypeScript(source) {
93
93
  const select = (0, select_template_1.devkitSelectTemplate)();
94
94
  strict_1.default.match(select.ts, /searchChange = new EventEmitter<string>/);
95
95
  strict_1.default.match(select.ts, /setTimeout\(\(\) => this\.searchChange\.emit/);
96
+ strict_1.default.match(select.ts, /if \(this\.valuesEqual\(this\.value, val\)\)/);
97
+ strict_1.default.match(select.ts, /trackByValue/);
98
+ strict_1.default.match(select.html, /trackBy: trackByValue/);
96
99
  strict_1.default.match(select.html, /Carregar mais/);
97
100
  strict_1.default.match(select.html, /loadMore\.emit\(\)/);
98
101
  assertValidTypeScript(select.ts);
@@ -67,11 +67,19 @@ export class DevkitSelectComponent implements ControlValueAccessor, OnDestroy {
67
67
  }
68
68
 
69
69
  onValueChange(val: any): void {
70
+ if (this.valuesEqual(this.value, val)) {
71
+ this.onTouched();
72
+ return;
73
+ }
70
74
  this.value = val;
71
75
  this.onChange(val);
72
76
  this.onTouched();
73
77
  }
74
78
 
79
+ trackByValue(_index: number, option: DevkitSelectOption): any {
80
+ return option.value;
81
+ }
82
+
75
83
  onSearchInput(event: Event): void {
76
84
  this.searchTerm = (event.target as HTMLInputElement).value;
77
85
  if (this.searchTimer) clearTimeout(this.searchTimer);
@@ -93,6 +101,14 @@ export class DevkitSelectComponent implements ControlValueAccessor, OnDestroy {
93
101
  const selected = this.options.find(opt => opt.value === value);
94
102
  return selected ? selected.label : this.placeholder;
95
103
  }
104
+
105
+ private valuesEqual(current: any, next: any): boolean {
106
+ if (Array.isArray(current) && Array.isArray(next)) {
107
+ return current.length === next.length
108
+ && current.every((value, index) => Object.is(value, next[index]));
109
+ }
110
+ return Object.is(current, next);
111
+ }
96
112
  }
97
113
  `,
98
114
  html: `<div class="flex w-full flex-col space-y-1.5">
@@ -115,7 +131,7 @@ export class DevkitSelectComponent implements ControlValueAccessor, OnDestroy {
115
131
  <ng-container *ngTemplateOutlet="lookupSearch"></ng-container>
116
132
  <hlm-select-group>
117
133
  <hlm-select-item *ngIf="placeholder" [value]="null">{{ placeholder }}</hlm-select-item>
118
- <hlm-select-item *ngFor="let opt of options" [value]="opt.value">{{ opt.label }}</hlm-select-item>
134
+ <hlm-select-item *ngFor="let opt of options; trackBy: trackByValue" [value]="opt.value">{{ opt.label }}</hlm-select-item>
119
135
  </hlm-select-group>
120
136
  <ng-container *ngTemplateOutlet="lookupFooter"></ng-container>
121
137
  </hlm-select-content>
@@ -135,7 +151,7 @@ export class DevkitSelectComponent implements ControlValueAccessor, OnDestroy {
135
151
  <hlm-select-content *hlmSelectPortal>
136
152
  <ng-container *ngTemplateOutlet="lookupSearch"></ng-container>
137
153
  <hlm-select-group>
138
- <hlm-select-item *ngFor="let opt of options" [value]="opt.value">{{ opt.label }}</hlm-select-item>
154
+ <hlm-select-item *ngFor="let opt of options; trackBy: trackByValue" [value]="opt.value">{{ opt.label }}</hlm-select-item>
139
155
  </hlm-select-group>
140
156
  <ng-container *ngTemplateOutlet="lookupFooter"></ng-container>
141
157
  </hlm-select-content>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devstroupe/devkit-cli",
3
- "version": "1.2.0-beta.3",
3
+ "version": "1.2.0-beta.5",
4
4
  "description": "DevsTroupe Development Kit CLI — scaffold NestJS+Angular projects, inject Spartan UI, generate CRUDs and audit architectural governance",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "commander": "^12.0.0",
38
38
  "fs-extra": "^11.2.0",
39
- "@devstroupe/devkit-core": "1.2.0-beta.3"
39
+ "@devstroupe/devkit-core": "1.2.0-beta.5"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/fs-extra": "^11.0.4",