newportsite 1.1.3

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 (131) hide show
  1. package/newportsite-1.1.3.tgz +0 -0
  2. package/ng-package.json +7 -0
  3. package/obfuscate.js +70 -0
  4. package/package.json +15 -0
  5. package/src/lib/app.component.ts +47 -0
  6. package/src/lib/app.routing.ts +38 -0
  7. package/src/lib/auth/alert.component.html +5 -0
  8. package/src/lib/auth/alert.component.ts +24 -0
  9. package/src/lib/auth/auth.component.html +1 -0
  10. package/src/lib/auth/auth.component.ts +10 -0
  11. package/src/lib/auth/auth.routes.ts +16 -0
  12. package/src/lib/auth/index.ts +4 -0
  13. package/src/lib/auth/login.component.html +87 -0
  14. package/src/lib/auth/login.component.ts +158 -0
  15. package/src/lib/auth/models/index.ts +1 -0
  16. package/src/lib/auth/models/user.ts +25 -0
  17. package/src/lib/auth/register.component.html +157 -0
  18. package/src/lib/auth/register.component.ts +219 -0
  19. package/src/lib/auth/services/alert.service.ts +47 -0
  20. package/src/lib/auth/services/auth.service.ts +28 -0
  21. package/src/lib/auth/services/index.ts +3 -0
  22. package/src/lib/auth/services/user.service.spec.ts +112 -0
  23. package/src/lib/auth/services/user.service.ts +47 -0
  24. package/src/lib/common/card.component.html +72 -0
  25. package/src/lib/common/card.component.ts +102 -0
  26. package/src/lib/common/commands.component.html +8 -0
  27. package/src/lib/common/commands.component.ts +42 -0
  28. package/src/lib/common/context.component.html +9 -0
  29. package/src/lib/common/context.component.ts +38 -0
  30. package/src/lib/common/grid.component.html +20 -0
  31. package/src/lib/common/grid.component.ts +747 -0
  32. package/src/lib/common/index.ts +9 -0
  33. package/src/lib/common/loader.component.html +5 -0
  34. package/src/lib/common/loader.component.ts +27 -0
  35. package/src/lib/common/lookup.component.html +29 -0
  36. package/src/lib/common/lookup.component.ts +115 -0
  37. package/src/lib/common/messagebox.component.html +39 -0
  38. package/src/lib/common/messagebox.component.ts +74 -0
  39. package/src/lib/common/theme-toggle.component.ts +139 -0
  40. package/src/lib/config.ts +62 -0
  41. package/src/lib/containers/default-layout/default-layout.component.html +191 -0
  42. package/src/lib/containers/default-layout/default-layout.component.ts +158 -0
  43. package/src/lib/containers/default-layout/index.ts +1 -0
  44. package/src/lib/containers/index.ts +1 -0
  45. package/src/lib/directives/component.draggable.ts +80 -0
  46. package/src/lib/directives/index.ts +2 -0
  47. package/src/lib/directives/input.directive.spec.ts +158 -0
  48. package/src/lib/directives/input.directive.ts +210 -0
  49. package/src/lib/home/dashboard/dashboard.component.html +38 -0
  50. package/src/lib/home/dashboard/dashboard.component.ts +50 -0
  51. package/src/lib/home/dashboard/index.ts +1 -0
  52. package/src/lib/home/index.component.html +1 -0
  53. package/src/lib/home/index.component.ts +10 -0
  54. package/src/lib/home/index.routes.ts +29 -0
  55. package/src/lib/home/index.ts +1 -0
  56. package/src/lib/home/info/index.ts +1 -0
  57. package/src/lib/home/info/info.component.css +476 -0
  58. package/src/lib/home/info/info.component.html +174 -0
  59. package/src/lib/home/info/info.component.ts +287 -0
  60. package/src/lib/home/model/article.component.html +10 -0
  61. package/src/lib/home/model/article.component.ts +50 -0
  62. package/src/lib/home/model/barchart.component.html +8 -0
  63. package/src/lib/home/model/barchart.component.ts +59 -0
  64. package/src/lib/home/model/index.ts +7 -0
  65. package/src/lib/home/model/itemdetail.component.html +25 -0
  66. package/src/lib/home/model/itemdetail.component.ts +93 -0
  67. package/src/lib/home/model/itemtab.component.html +25 -0
  68. package/src/lib/home/model/itemtab.component.ts +105 -0
  69. package/src/lib/home/model/model.component.html +121 -0
  70. package/src/lib/home/model/model.component.ts +510 -0
  71. package/src/lib/home/model/modeltoolbar.component.html +111 -0
  72. package/src/lib/home/model/modeltoolbar.component.ts +157 -0
  73. package/src/lib/home/model/navigation.component.html +86 -0
  74. package/src/lib/home/model/navigation.component.ts +247 -0
  75. package/src/lib/home/model/services/index.ts +1 -0
  76. package/src/lib/home/model/services/model.service.spec.ts +423 -0
  77. package/src/lib/home/model/services/model.service.ts +319 -0
  78. package/src/lib/home/modelsearch/index.ts +1 -0
  79. package/src/lib/home/modelsearch/modelsearch.component.html +124 -0
  80. package/src/lib/home/modelsearch/modelsearch.component.ts +453 -0
  81. package/src/lib/interfaces/data.interface.ts +131 -0
  82. package/src/lib/interfaces/index.ts +2 -0
  83. package/src/lib/interfaces/item.interface.ts +438 -0
  84. package/src/lib/players/lookup/lookup.directive.ts +6 -0
  85. package/src/lib/players/lookup/lookup.item.component.ts +37 -0
  86. package/src/lib/players/lookup/lookup.item.ts +9 -0
  87. package/src/lib/players/lookup/lookup.player.component.ts +59 -0
  88. package/src/lib/players/lookup/lookup.selector.component.ts +41 -0
  89. package/src/lib/players/model/model.directive.ts +6 -0
  90. package/src/lib/players/model/model.item.component.spec.ts +311 -0
  91. package/src/lib/players/model/model.item.component.ts +3457 -0
  92. package/src/lib/players/model/model.item.ts +9 -0
  93. package/src/lib/players/model/model.player.component.ts +109 -0
  94. package/src/lib/players/model/model.selector.component.ts +59 -0
  95. package/src/lib/scheduler/scheduler.component.html +13 -0
  96. package/src/lib/scheduler/scheduler.component.scss +6 -0
  97. package/src/lib/scheduler/scheduler.component.ts +296 -0
  98. package/src/lib/scheduler/scheduler.routes.ts +15 -0
  99. package/src/lib/scheduler/schedulerdialog.component.html +72 -0
  100. package/src/lib/scheduler/schedulerdialog.component.ts +208 -0
  101. package/src/lib/scheduler/services/scheduler.service.ts +133 -0
  102. package/src/lib/services/auth-state.service.ts +129 -0
  103. package/src/lib/services/auth.interceptor.spec.ts +144 -0
  104. package/src/lib/services/auth.interceptor.ts +44 -0
  105. package/src/lib/services/cache.service.spec.ts +143 -0
  106. package/src/lib/services/cache.service.ts +71 -0
  107. package/src/lib/services/global-error-handler.spec.ts +39 -0
  108. package/src/lib/services/global-error-handler.ts +28 -0
  109. package/src/lib/services/global.service.spec.ts +801 -0
  110. package/src/lib/services/global.service.ts +724 -0
  111. package/src/lib/services/message.service.ts +556 -0
  112. package/src/lib/services/theme.service.ts +96 -0
  113. package/src/lib/template/authtemplate.component.html +6 -0
  114. package/src/lib/template/authtemplate.component.ts +13 -0
  115. package/src/lib/template/basetemplate.component.html +7 -0
  116. package/src/lib/template/basetemplate.component.ts +13 -0
  117. package/src/lib/template/index.ts +3 -0
  118. package/src/lib/template/modeltemplate.component.html +7 -0
  119. package/src/lib/template/modeltemplate.component.ts +21 -0
  120. package/src/lib/utils/piva.spec.ts +56 -0
  121. package/src/lib/utils/piva.ts +29 -0
  122. package/src/lib/validators/email.validator.spec.ts +57 -0
  123. package/src/lib/validators/email.validator.ts +17 -0
  124. package/src/lib/validators/equalPasswords.validator.spec.ts +54 -0
  125. package/src/lib/validators/equalPasswords.validator.ts +17 -0
  126. package/src/lib/validators/index.ts +2 -0
  127. package/src/lib/version.ts +1 -0
  128. package/src/public-api.ts +64 -0
  129. package/src/typings.d.ts +2 -0
  130. package/tsconfig.lib.json +18 -0
  131. package/tsconfig.lib.prod.json +9 -0
@@ -0,0 +1,157 @@
1
+ <!-- register.component -->
2
+ <authtemplate>
3
+ <div class="auth-card">
4
+ <div class="auth-brand">
5
+ <div class="auth-logo">
6
+ <i class="bi bi-person-plus"></i>
7
+ </div>
8
+ <h1 class="auth-app-name">Newport</h1>
9
+ <p class="auth-subtitle">Crea il tuo account</p>
10
+ </div>
11
+
12
+ <form
13
+ [formGroup]="form"
14
+ (ngSubmit)="onSubmit(form.value)"
15
+ role="form"
16
+ aria-label="Modulo di registrazione"
17
+ autocomplete="off">
18
+ <!-- Login -->
19
+ <div class="auth-field">
20
+ <span class="auth-field-icon" aria-hidden="true"><i class="bi bi-person"></i></span>
21
+ <label for="login" class="visually-hidden">{{ msg?.get('app.login') }}</label>
22
+ <input
23
+ nvg
24
+ [attr.data-state]="0"
25
+ tabindex="1"
26
+ [formControl]="login"
27
+ type="text"
28
+ class="form-control auth-input"
29
+ id="login"
30
+ placeholder="{{ msg?.get('app.login') }}"
31
+ [attr.aria-label]="msg?.get('app.login')"
32
+ aria-required="true"
33
+ autocomplete="off" />
34
+ </div>
35
+
36
+ <!-- Password + Generate -->
37
+ <div class="auth-field">
38
+ <span class="auth-field-icon" aria-hidden="true"><i class="bi bi-lock"></i></span>
39
+ <label for="password" class="visually-hidden">{{ msg?.get('app.password') }}</label>
40
+ <input
41
+ nvg
42
+ [attr.data-state]="0"
43
+ tabindex="2"
44
+ [formControl]="password"
45
+ [type]="showPassword ? 'text' : 'password'"
46
+ class="form-control auth-input auth-input-padded2"
47
+ id="password"
48
+ placeholder="{{ msg?.get('app.password') }}"
49
+ [attr.aria-label]="msg?.get('app.password')"
50
+ aria-required="true"
51
+ aria-describedby="password-strength"
52
+ autocomplete="off" />
53
+ <div class="auth-toggle-group">
54
+ <button
55
+ type="button"
56
+ class="auth-toggle-btn"
57
+ tabindex="-1"
58
+ [attr.aria-pressed]="showPassword"
59
+ [attr.aria-label]="showPassword ? 'Nascondi password' : 'Mostra password'"
60
+ (click)="showPassword = !showPassword"
61
+ [title]="showPassword ? 'Nascondi password' : 'Mostra password'">
62
+ <i
63
+ class="bi"
64
+ aria-hidden="true"
65
+ [class.bi-eye]="!showPassword"
66
+ [class.bi-eye-slash]="showPassword"></i>
67
+ </button>
68
+ <button
69
+ type="button"
70
+ class="auth-toggle-btn btn-generate"
71
+ tabindex="-1"
72
+ aria-label="Genera password sicura"
73
+ (click)="generatePassword()"
74
+ title="Genera password sicura">
75
+ <i class="bi bi-shield-lock" aria-hidden="true"></i>
76
+ </button>
77
+ </div>
78
+ </div>
79
+
80
+ <!-- Password strength indicator -->
81
+ @if (password.value) {
82
+ <div
83
+ id="password-strength"
84
+ class="strength-meter"
85
+ role="status"
86
+ aria-live="polite"
87
+ [attr.aria-label]="'Sicurezza password: ' + passwordStrength.label">
88
+ <div class="strength-bar" role="presentation">
89
+ <div
90
+ class="strength-fill"
91
+ [style.width.%]="passwordStrength.score * 25"
92
+ [class.strength-weak]="passwordStrength.score === 1"
93
+ [class.strength-fair]="passwordStrength.score === 2"
94
+ [class.strength-good]="passwordStrength.score === 3"
95
+ [class.strength-strong]="passwordStrength.score === 4"></div>
96
+ </div>
97
+ <span
98
+ class="strength-label"
99
+ [class]="'strength-text-' + passwordStrength.score">
100
+ {{ passwordStrength.label }}
101
+ </span>
102
+ </div>
103
+ @if (passwordStrength.suggestions.length) {
104
+ <ul class="strength-suggestions" aria-label="Suggerimenti per migliorare la password">
105
+ @for (tip of passwordStrength.suggestions; track tip) {
106
+ <li>{{ tip }}</li>
107
+ }
108
+ </ul>
109
+ }
110
+ }
111
+
112
+ <!-- Confirm password -->
113
+ <div class="auth-field">
114
+ <span class="auth-field-icon" aria-hidden="true"><i class="bi bi-lock-fill"></i></span>
115
+ <label for="confirmpassword" class="visually-hidden">{{ msg?.get('app.repeatepassword') }}</label>
116
+ <input
117
+ nvg
118
+ [attr.data-state]="0"
119
+ tabindex="3"
120
+ [formControl]="confirmpassword"
121
+ [type]="showPassword ? 'text' : 'password'"
122
+ class="form-control auth-input"
123
+ id="confirmpassword"
124
+ placeholder="{{ msg?.get('app.repeatepassword') }}"
125
+ [attr.aria-label]="msg?.get('app.repeatepassword')"
126
+ aria-required="true"
127
+ [attr.aria-invalid]="showPasswordError || null" />
128
+ </div>
129
+ @if (showPasswordError) {
130
+ <div class="auth-error-msg" role="alert" aria-live="assertive">
131
+ {{ msg?.get('app.passworddoesntmatch') }}
132
+ </div>
133
+ }
134
+
135
+ <button
136
+ type="submit"
137
+ nvg
138
+ [attr.data-state]="0"
139
+ tabindex="4"
140
+ [disabled]="isSubmitDisabled"
141
+ [attr.aria-disabled]="isSubmitDisabled"
142
+ class="btn btn-primary btn-auth-submit">
143
+ {{ msg?.get('app.confirm') }}
144
+ </button>
145
+
146
+ <div class="auth-alt-action">
147
+ <span>Hai già un account?</span>
148
+ <a (click)="navigate('/auth/login')" class="auth-alt-link">{{
149
+ msg?.get('app.authtitle')
150
+ }}</a>
151
+ </div>
152
+ </form>
153
+
154
+ <alert></alert>
155
+ </div>
156
+ </authtemplate>
157
+ <!-- fine register.component -->
@@ -0,0 +1,219 @@
1
+ import {
2
+ Component,
3
+ AfterViewInit,
4
+ ChangeDetectionStrategy,
5
+ DestroyRef,
6
+ inject,
7
+ } from '@angular/core';
8
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
+ import {
10
+ FormGroup,
11
+ AbstractControl,
12
+ FormBuilder,
13
+ Validators,
14
+ ReactiveFormsModule,
15
+ } from '@angular/forms';
16
+ import { EqualPasswordsValidator } from '../validators/index';
17
+
18
+ import { GlobalService } from '../services/global.service';
19
+
20
+ import { AppMessageService } from '../services/message.service';
21
+
22
+ import { AlertService, UserService } from './services/index';
23
+ import { User } from '../auth/models/user';
24
+ import { AuthTemplateComponent } from '../template/authtemplate.component';
25
+ import { InputDirective } from '../directives/input.directive';
26
+
27
+ import { AlertComponent } from './alert.component';
28
+
29
+ interface PasswordStrength {
30
+ score: number;
31
+ label: string;
32
+ suggestions: string[];
33
+ }
34
+
35
+ @Component({
36
+ templateUrl: 'register.component.html',
37
+ changeDetection: ChangeDetectionStrategy.OnPush,
38
+ providers: [AlertService, UserService],
39
+ imports: [
40
+ AuthTemplateComponent,
41
+ ReactiveFormsModule,
42
+ InputDirective,
43
+ AlertComponent,
44
+ ],
45
+ })
46
+ export class RegisterComponent implements AfterViewInit {
47
+ fb = inject(FormBuilder);
48
+ usv = inject(UserService);
49
+ als = inject(AlertService);
50
+ gsv = inject(GlobalService);
51
+ msg = inject(AppMessageService);
52
+
53
+ public form!: FormGroup;
54
+ public login!: AbstractControl;
55
+ public name!: AbstractControl;
56
+ public email!: AbstractControl;
57
+ public password!: AbstractControl;
58
+ public confirmpassword!: AbstractControl;
59
+ public passwords!: FormGroup;
60
+
61
+ public submitted: boolean = false;
62
+ public loading = false;
63
+ public showPassword = false;
64
+ public passwordStrength: PasswordStrength = {
65
+ score: 0,
66
+ label: '',
67
+ suggestions: [],
68
+ };
69
+ private destroyRef = inject(DestroyRef);
70
+
71
+ constructor() {
72
+ const fb = this.fb;
73
+
74
+ this.form = fb.group({
75
+ login: ['', Validators.compose([Validators.required])],
76
+ passwords: fb.group(
77
+ {
78
+ password: [
79
+ '',
80
+ Validators.compose([Validators.required, Validators.minLength(8)]),
81
+ ],
82
+ confirmpassword: ['', Validators.compose([Validators.required])],
83
+ },
84
+ {
85
+ validator: EqualPasswordsValidator.validate(
86
+ 'password',
87
+ 'confirmpassword'
88
+ ),
89
+ }
90
+ ),
91
+ });
92
+
93
+ this.login = this.form.controls['login'];
94
+ this.passwords = this.form.controls['passwords'] as FormGroup;
95
+ this.password = this.passwords.controls['password'];
96
+ this.confirmpassword = this.passwords.controls['confirmpassword'];
97
+
98
+ this.password.valueChanges
99
+ .pipe(takeUntilDestroyed(this.destroyRef))
100
+ .subscribe((value: string) => {
101
+ this.passwordStrength = this.evaluateStrength(value);
102
+ });
103
+ }
104
+
105
+ get showPasswordError() {
106
+ return !this.confirmpassword.valid && this.confirmpassword.touched;
107
+ }
108
+
109
+ get isSubmitDisabled() {
110
+ return this.loading || !this.form.valid;
111
+ }
112
+
113
+ /** Lifecycle: focuses the login field on first render. */
114
+ public ngAfterViewInit(): void {
115
+ const loginElement = document.getElementById('login');
116
+ loginElement?.focus();
117
+ }
118
+
119
+ /** Generates a cryptographically random 16-char password satisfying all complexity rules. */
120
+ public generatePassword(): void {
121
+ const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
122
+ const lower = 'abcdefghjkmnpqrstuvwxyz';
123
+ const digits = '23456789';
124
+ const symbols = '!@#$%&*?';
125
+ const all = upper + lower + digits + symbols;
126
+
127
+ const array = new Uint32Array(16);
128
+ crypto.getRandomValues(array);
129
+
130
+ // Guarantee at least one from each category
131
+ const required = [
132
+ upper[array[0] % upper.length],
133
+ lower[array[1] % lower.length],
134
+ digits[array[2] % digits.length],
135
+ symbols[array[3] % symbols.length],
136
+ ];
137
+
138
+ const rest: string[] = [];
139
+ for (let i = 4; i < 16; i++) {
140
+ rest.push(all[array[i] % all.length]);
141
+ }
142
+
143
+ // Shuffle using Fisher-Yates with crypto random
144
+ const combined = [...required, ...rest];
145
+ const shuffleArray = new Uint32Array(combined.length);
146
+ crypto.getRandomValues(shuffleArray);
147
+ for (let i = combined.length - 1; i > 0; i--) {
148
+ const j = shuffleArray[i] % (i + 1);
149
+ [combined[i], combined[j]] = [combined[j], combined[i]];
150
+ }
151
+
152
+ const generated = combined.join('');
153
+ this.password.setValue(generated);
154
+ this.confirmpassword.setValue(generated);
155
+ this.showPassword = true;
156
+ }
157
+
158
+ /** Scores password strength (0–4) and returns a label with improvement suggestions. */
159
+ public evaluateStrength(value: string): PasswordStrength {
160
+ if (!value) {
161
+ return { score: 0, label: '', suggestions: [] };
162
+ }
163
+
164
+ let score = 0;
165
+ const suggestions: string[] = [];
166
+
167
+ if (value.length >= 8) score++;
168
+ else suggestions.push('Almeno 8 caratteri');
169
+
170
+ if (value.length >= 12) score++;
171
+ else if (value.length >= 8)
172
+ suggestions.push('12+ caratteri per maggiore sicurezza');
173
+
174
+ if (/[a-z]/.test(value) && /[A-Z]/.test(value)) score++;
175
+ else suggestions.push('Maiuscole e minuscole');
176
+
177
+ if (/[0-9]/.test(value) && /[^a-zA-Z0-9]/.test(value)) score++;
178
+ else {
179
+ if (!/[0-9]/.test(value)) suggestions.push('Aggiungi numeri');
180
+ if (!/[^a-zA-Z0-9]/.test(value))
181
+ suggestions.push('Aggiungi simboli (!@#$%&*)');
182
+ }
183
+
184
+ const labels = ['', 'Debole', 'Discreta', 'Buona', 'Forte'];
185
+ return { score, label: labels[score] || '', suggestions };
186
+ }
187
+
188
+ /** Submits the registration form: creates the user via UserService and redirects to login on success. */
189
+ public onSubmit(usr: any): void {
190
+ this.submitted = true;
191
+ if (this.form.valid) {
192
+ this.loading = true;
193
+ const user = new User();
194
+ user.login = usr.login;
195
+ user.name = usr.name === undefined ? '' : usr.name;
196
+ user.email = usr.email === undefined ? '' : usr.email;
197
+ user.password = usr.passwords.password;
198
+ user.token = '';
199
+ this.gsv.setCurrentUser(user);
200
+ this.usv.post(user).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
201
+ next: () => {
202
+ this.gsv.logOff();
203
+ this.als.success('Registration successful', true);
204
+ this.gsv.navigate('/auth/login');
205
+ },
206
+ error: () => {
207
+ this.als.error(this.msg.get('app.registerfailed'));
208
+ this.loading = false;
209
+ this.gsv.setCurrentUser(null);
210
+ },
211
+ });
212
+ }
213
+ }
214
+
215
+ /** Delegates client-side navigation to GlobalService. */
216
+ public navigate(url: string) {
217
+ this.gsv.navigate(url);
218
+ }
219
+ }
@@ -0,0 +1,47 @@
1
+ import { Injectable, DestroyRef, inject } from '@angular/core';
2
+ import { Router, NavigationStart } from '@angular/router';
3
+ import { Observable, Subject } from 'rxjs';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { filter } from 'rxjs/operators';
6
+
7
+ @Injectable()
8
+ export class AlertService {
9
+ private router = inject(Router);
10
+ private destroyRef = inject(DestroyRef);
11
+
12
+ private subject = new Subject<{ type: string; text: string } | null>();
13
+ private keepAfterNavigationChange = false;
14
+
15
+ constructor() {
16
+ // clear alert message on route change
17
+ this.router.events
18
+ .pipe(
19
+ filter(event => event instanceof NavigationStart),
20
+ takeUntilDestroyed(this.destroyRef)
21
+ )
22
+ .subscribe(() => {
23
+ if (this.keepAfterNavigationChange) {
24
+ this.keepAfterNavigationChange = false;
25
+ } else {
26
+ this.subject.next(null);
27
+ }
28
+ });
29
+ }
30
+
31
+ /** Emits a success alert message; optionally persists it across the next navigation. */
32
+ public success(message: string, keepAfterNavigationChange = true) {
33
+ this.keepAfterNavigationChange = keepAfterNavigationChange;
34
+ this.subject.next({ type: 'success', text: message });
35
+ }
36
+
37
+ /** Emits an error alert message; optionally persists it across the next navigation. */
38
+ public error(message: string, keepAfterNavigationChange = true) {
39
+ this.keepAfterNavigationChange = keepAfterNavigationChange;
40
+ this.subject.next({ type: 'error', text: message });
41
+ }
42
+
43
+ /** Returns an Observable that emits alert messages (or null to clear). */
44
+ public getMessage(): Observable<any> {
45
+ return this.subject.asObservable();
46
+ }
47
+ }
@@ -0,0 +1,28 @@
1
+ import { inject } from '@angular/core';
2
+ import {
3
+ Router,
4
+ CanActivateFn,
5
+ ActivatedRouteSnapshot,
6
+ RouterStateSnapshot,
7
+ } from '@angular/router';
8
+ import { GlobalService } from '../../services/global.service';
9
+
10
+ export const authGuard: CanActivateFn = (
11
+ _route: ActivatedRouteSnapshot,
12
+ state: RouterStateSnapshot
13
+ ): boolean => {
14
+ const gsv = inject(GlobalService);
15
+ const router = inject(Router);
16
+
17
+ if (gsv.isLogged()) {
18
+ return true;
19
+ }
20
+ gsv.setActiveLink('/auth/login');
21
+ router.navigate(['/auth/login'], {
22
+ queryParams: { returnUrl: state.url },
23
+ });
24
+ return false;
25
+ };
26
+
27
+ /** @deprecated Use authGuard functional guard instead */
28
+ export const AuthGuard = authGuard;
@@ -0,0 +1,3 @@
1
+ export * from './alert.service';
2
+ export * from './user.service';
3
+ export * from './auth.service';
@@ -0,0 +1,112 @@
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 { Router } from '@angular/router';
5
+ import { Location } from '@angular/common';
6
+ import { UserService } from './user.service';
7
+ import { GlobalService } from '../../services/global.service';
8
+ import { User } from '../models/index';
9
+ import { NEWPORT_CONFIG } from '../../config';
10
+
11
+ const TEST_CONFIG = { apiUrl: 'http://test/', logicFactory: () => {}, projectsInfoLoader: () => Promise.resolve({}) };
12
+
13
+ describe('UserService', () => {
14
+ let svc: UserService;
15
+ let httpMock: HttpTestingController;
16
+ let gsv: GlobalService;
17
+
18
+ const routerStub = { navigateByUrl: jest.fn().mockResolvedValue(true) };
19
+ const locationStub = { normalize: (url: string) => url };
20
+
21
+ const expectedUrl = 'http://test/users';
22
+
23
+ beforeEach(() => {
24
+ localStorage.clear();
25
+ jest.clearAllMocks();
26
+ TestBed.configureTestingModule({
27
+ providers: [
28
+ UserService,
29
+ GlobalService,
30
+ { provide: Router, useValue: routerStub },
31
+ { provide: Location, useValue: locationStub },
32
+ { provide: NEWPORT_CONFIG, useValue: TEST_CONFIG },
33
+ provideHttpClient(),
34
+ provideHttpClientTesting(),
35
+ ],
36
+ });
37
+ svc = TestBed.inject(UserService);
38
+ httpMock = TestBed.inject(HttpTestingController);
39
+ gsv = TestBed.inject(GlobalService);
40
+ });
41
+
42
+ afterEach(() => httpMock.verify());
43
+
44
+ describe('put() — login', () => {
45
+ it('sends a PUT request to the users endpoint', () => {
46
+ const credentials = Object.assign(new User(), { login: 'mario', password: 'secret' });
47
+ const mockResponse: Partial<User> = { token: 'jwt-token', name: 'Mario' };
48
+
49
+ svc.put(credentials).subscribe(res => {
50
+ expect(res.token).toBe('jwt-token');
51
+ });
52
+
53
+ const req = httpMock.expectOne(expectedUrl);
54
+ expect(req.request.method).toBe('PUT');
55
+ req.flush(mockResponse);
56
+ });
57
+
58
+ it('includes the Authorization header from GlobalService.jwt()', () => {
59
+ const credentials = new User();
60
+ svc.put(credentials).subscribe();
61
+
62
+ const req = httpMock.expectOne(expectedUrl);
63
+ expect(req.request.headers.get('Content-Type')).toBe('application/json');
64
+ req.flush({});
65
+ });
66
+ });
67
+
68
+ describe('post() — register', () => {
69
+ it('sends a POST request to the users endpoint', () => {
70
+ const newUser = Object.assign(new User(), { login: 'mario', password: 'secret' });
71
+ const mockResponse: Partial<User> = { token: 'new-token', name: 'Mario' };
72
+
73
+ svc.post(newUser).subscribe(res => {
74
+ expect(res.token).toBe('new-token');
75
+ });
76
+
77
+ const req = httpMock.expectOne(expectedUrl);
78
+ expect(req.request.method).toBe('POST');
79
+ req.flush(mockResponse);
80
+ });
81
+ });
82
+
83
+ describe('error handling', () => {
84
+ it('maps a server 400 error to a typed Error observable', done => {
85
+ // post() has no retry, so exactly one request is made
86
+ svc.post(new User()).subscribe({
87
+ error: (err: Error) => {
88
+ expect(err).toBeInstanceOf(Error);
89
+ expect(err.message).toContain('400');
90
+ done();
91
+ },
92
+ });
93
+
94
+ httpMock.expectOne(expectedUrl).flush(
95
+ { message: 'Bad Request' },
96
+ { status: 400, statusText: 'Bad Request' }
97
+ );
98
+ });
99
+
100
+ it('maps a network error to a typed Error observable', done => {
101
+ svc.post(new User()).subscribe({
102
+ error: (err: Error) => {
103
+ expect(err).toBeInstanceOf(Error);
104
+ expect(err.message).toBeTruthy();
105
+ done();
106
+ },
107
+ });
108
+
109
+ httpMock.expectOne(expectedUrl).error(new ErrorEvent('error', { message: 'Network failure' }));
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,47 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
3
+ import { Observable, throwError } from 'rxjs';
4
+ import { GlobalService } from '../../services/global.service';
5
+
6
+ import { User } from '../models/index';
7
+
8
+ import { catchError, retry } from 'rxjs/operators';
9
+
10
+ @Injectable()
11
+ export class UserService {
12
+ private http = inject(HttpClient);
13
+ private globalService = inject(GlobalService);
14
+
15
+ private usersUrl: string = this.globalService.restService() + 'users';
16
+
17
+ public put(user: User): Observable<User> {
18
+ return this.http
19
+ .put<User>(this.usersUrl, user, this.globalService.jwt())
20
+ .pipe(retry(3), catchError(this.handleError));
21
+ }
22
+
23
+ public post(user: User): Observable<User> {
24
+ return this.http
25
+ .post<User>(this.usersUrl, user, this.globalService.jwt())
26
+ .pipe(catchError(this.handleError));
27
+ }
28
+
29
+ private handleError(error: HttpErrorResponse): Observable<never> {
30
+ let errorMessage = 'An error occurred';
31
+
32
+ if (error.error instanceof ErrorEvent) {
33
+ // Client-side or network error
34
+ errorMessage = `Client Error: ${error.error.message}`;
35
+ console.error('Client-side error:', error.error.message);
36
+ } else {
37
+ // Backend error
38
+ errorMessage = `Server Error (${error.status}): ${error.message}`;
39
+ console.error(
40
+ `Backend returned code ${error.status}, body:`,
41
+ error.error
42
+ );
43
+ }
44
+
45
+ return throwError(() => new Error(errorMessage));
46
+ }
47
+ }
@@ -0,0 +1,72 @@
1
+ <div [@flipState]="flip()" class="card">
2
+ <div #front [hidden]="flip() === 'back'" class="flipfront">
3
+ <div>
4
+ <div class="card-body">
5
+ <div class="card-title fs-18px">
6
+ <span class="align-self-start" [ngClass]="stateClass()"> </span>
7
+ <span
8
+ class="bg-transparent truncate"
9
+ title="{{ msg?.getProjectDescription('foldertitle') }}">
10
+ {{ msg?.getProjectDescription('foldertitle') }}
11
+ </span>
12
+ </div>
13
+ @for (front of item().frontitems; track front) {
14
+ <div>
15
+ <div>{{ front.name_id }}</div>
16
+ <div id="{{ front.id }}">{{ front.value_id }}</div>
17
+ </div>
18
+ }
19
+ </div>
20
+ <div class="nopaddingtae">
21
+ <div class="p-1">
22
+ <div
23
+ title="{{ msg?.get('app.modify') }}"
24
+ class="btn bi bi-box-arrow-right"
25
+ (click)="activateitem(item().key_id)"></div>
26
+ <div
27
+ title="{{ msg?.get('app.minus') }}"
28
+ class="btn bi bi-patch-minus"
29
+ (click)="deleteitem(item().key_id)"></div>
30
+ <div
31
+ [hidden]="printHidden"
32
+ title="{{ msg?.get('app.print') }}"
33
+ class="btn bi bi-printer"
34
+ (click)="printitem(item().key_id)"></div>
35
+ <div
36
+ [hidden]="summaryHidden"
37
+ title="{{ msg?.get('app.back') }}"
38
+ class="btn bi bi-arrow-clockwise"
39
+ (click)="toggleFlip(item().key_id)"></div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ <div [hidden]="flip() === 'front'" class="flipback">
45
+ <div class="card">
46
+ <div class="card-body">
47
+ <h5 class="card-title">
48
+ <span class="align-self-start" [ngClass]="stateClass()"> </span>
49
+ <span
50
+ class="bg-transparent truncate"
51
+ title="{{ msg?.getProjectDescription('foldertitle') }}">
52
+ {{ msg?.getProjectDescription('foldertitle') }}
53
+ </span>
54
+ </h5>
55
+ @for (back of item().backitems; track back) {
56
+ <div>
57
+ <div id="{{ back.id }}">{{ back.name_id }}</div>
58
+ <div>{{ back.value_id }}</div>
59
+ </div>
60
+ }
61
+ </div>
62
+ <div class="nopaddingtae">
63
+ <div class="p-1">
64
+ <div
65
+ title="{{ msg?.get('app.front') }}"
66
+ class="btn bi bi-arrow-counterclockwise"
67
+ (click)="toggleFlip(item().key_id)"></div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>