Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
const eslint = require("@eslint/js");
const importPlugin = require('eslint-plugin-import');
const tseslint = require("typescript-eslint");
const { defineConfig } = require('eslint/config');
const angular = require("angular-eslint");
const ngrx = require("@ngrx/eslint-plugin/v9");
const stylistic = require('@stylistic/eslint-plugin');

module.exports = tseslint.config(
module.exports = defineConfig(
{
files: ["**/*.ts"],
extends: [
eslint.configs.recommended,
stylistic.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylistic,
Expand Down Expand Up @@ -118,6 +121,62 @@ module.exports = tseslint.config(
]
}
],
"@stylistic/semi": ["error", "always"],
"@stylistic/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "comma",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
},
"overrides": {
"interface": {
"multiline": {
"delimiter": "semi",
"requireLast": true
}
}
}
}
],
"@stylistic/space-infix-ops": [
"error",
{
"ignoreTypes": true,
}
],
"@stylistic/no-multi-spaces": [
"error",
{
"exceptions": {
"TSEnumMember": true
}
}
],
"@stylistic/indent": [
"error",
2,
{
ignoredNodes: ["ObjectExpression"]
}
],
"@stylistic/brace-style": [
"error",
"1tbs"
],
"@stylistic/operator-linebreak": [
"error",
"before",
{
"overrides": {
"=": "after"
}
}
],
"import/no-extraneous-dependencies": [
"error",
{
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"@angular/language-service": "20.3.2",
"@bartholomej/ngx-translate-extract": "8.0.2",
"@ngrx/eslint-plugin": "20.0.1",
"@stylistic/eslint-plugin": "5.4.0",
"@types/jasmine": "5.1.9",
"@types/jasminewd2": "2.0.13",
"@types/luxon": "3.7.1",
Expand Down
24 changes: 13 additions & 11 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ export class AppComponent implements OnInit, OnDestroy {
this.logout(null);
} else if (userData !== null && !this.loggedIn) {
this.authenticationService
.isAuthenticated()
.pipe(take(1))
.subscribe((loggedIn: boolean): void => loggedIn
? this.store.dispatch(authenticationActions.loginSuccess({ userData }))
: this.logout(marker('messages.authentication.timeout')),
);
.isAuthenticated()
.pipe(take(1))
.subscribe((loggedIn: boolean): void => loggedIn
? this.store.dispatch(authenticationActions.loginSuccess({ userData }))
: this.logout(marker('messages.authentication.timeout')),
);
}
}),
);
Expand Down Expand Up @@ -158,11 +158,13 @@ export class AppComponent implements OnInit, OnDestroy {
* store.
*/
private checkToken(): void {
this.authenticationService.isAuthenticated().pipe(
take(1),
filter((authenticated: boolean): boolean => this.loggedIn && !authenticated),
)
.subscribe((): void => this.logout(marker('messages.authentication.timeout')));
this.authenticationService
.isAuthenticated()
.pipe(
take(1),
filter((authenticated: boolean): boolean => this.loggedIn && !authenticated),
)
.subscribe((): void => this.logout(marker('messages.authentication.timeout')));
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/app/shared/components/footer/footer.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export class FooterComponent implements OnInit, OnDestroy, AfterViewInit {
setTimeout((): void => {
const height = parseInt(this.footerContainer.nativeElement.offsetHeight, 10);

this.topOffset = `${ height }px`;
this.topMargin = `-${ height }px`;
this.topOffset = `${height}px`;
this.topMargin = `-${height}px`;
}, 0);
}

Expand Down
1 change: 0 additions & 1 deletion src/app/shared/components/header/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import { authenticationActions, authenticationSelectors, layoutActions, layoutSe
})

export class HeaderComponent implements OnInit, OnDestroy {

@ViewChild('userMenu') private readonly userMenu!: MatMenuTrigger;

public profile: UserProfileInterface|null = null;
Expand Down
2 changes: 1 addition & 1 deletion src/app/shared/factories/http-loader.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
export const httpLoaderFactory = (httpClient: HttpClient): TranslateHttpLoader => {
const ts = Math.round((new Date()).getTime() / 1000);

return new TranslateHttpLoader(httpClient, './assets/i18n/', `.json?t=${ ts }`);
return new TranslateHttpLoader(httpClient, './assets/i18n/', `.json?t=${ts}`);
};
27 changes: 14 additions & 13 deletions src/app/shared/interceptors/backend-version.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,20 @@ export class BackendVersionInterceptor implements HttpInterceptor {
* user to reload application OR continue using it with old version.
*/
private handle(httpEvent: Observable<HttpEvent<any>>): void {
const apiUrl = ConfigurationService.configuration.apiUrl;
const apiUrl: string = ConfigurationService.configuration.apiUrl;

httpEvent.pipe(
filter((event: any): boolean => event instanceof HttpResponse),
filter((event: HttpResponse<any>): boolean => new URL(event.url ?? '').host === new URL(apiUrl).host),
filter((event: HttpResponse<any>): boolean => !event.url?.includes('/version')),
filter((event: HttpResponse<any>): boolean => event.headers.has('X-API-VERSION')),
withLatestFrom(this.store.select(versionSelectors.selectBackendVersion)),
filter(([event, version]: [HttpResponse<any>, string]): boolean =>
version !== '0.0.0' && event.headers.get('X-API-VERSION') !== version,
),
map(([event]: [HttpResponse<any>, string]): string => event.headers.get('X-API-VERSION') ?? ''),
)
.subscribe((backendVersion: string): void => this.store.dispatch(versionActions.newBackendVersion({ backendVersion })));
httpEvent
.pipe(
filter((event: any): boolean => event instanceof HttpResponse),
filter((event: HttpResponse<any>): boolean => new URL(event.url ?? '').host === new URL(apiUrl).host),
filter((event: HttpResponse<any>): boolean => !event.url?.includes('/version')),
filter((event: HttpResponse<any>): boolean => event.headers.has('X-API-VERSION')),
withLatestFrom(this.store.select(versionSelectors.selectBackendVersion)),
filter(([event, version]: [HttpResponse<any>, string]): boolean =>
version !== '0.0.0' && event.headers.get('X-API-VERSION') !== version,
),
map(([event]: [HttpResponse<any>, string]): string => event.headers.get('X-API-VERSION') ?? ''),
)
.subscribe((backendVersion: string): void => this.store.dispatch(versionActions.newBackendVersion({ backendVersion })));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ErrorMessageServerInterface} from 'src/app/shared/interfaces/error-message-server.interface';
import { ErrorMessageServerInterface } from 'src/app/shared/interfaces/error-message-server.interface';

/**
* Interface definition for client side error messages - server side error
Expand Down
6 changes: 0 additions & 6 deletions src/app/shared/interfaces/http-cache.interface.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/app/shared/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export * from 'src/app/shared/interfaces/environment.interface';
export * from 'src/app/shared/interfaces/error-message.interface';
export * from 'src/app/shared/interfaces/error-message-client.interface';
export * from 'src/app/shared/interfaces/error-message-server.interface';
export * from 'src/app/shared/interfaces/http-cache.interface';
export * from 'src/app/shared/interfaces/language-value.interface';
export * from 'src/app/shared/interfaces/locale-value.interface';
export * from 'src/app/shared/interfaces/localization.interface';
Expand Down
2 changes: 1 addition & 1 deletion src/app/shared/interfaces/server-error-value.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ServerErrorInterface } from 'src/app/shared/interfaces/server-error.interface';

export interface ServerErrorValueInterface {
export interface ServerErrorValueInterface {
error: ServerErrorInterface;
}
3 changes: 0 additions & 3 deletions src/app/shared/pipes/local-date.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,6 @@ export class LocalDatePipe implements PipeTransform, OnDestroy {
if (this.cachedOutput === null || this.cachedLocale !== this.locale || this.cachedTimezone !== this.timezone) {
this.cachedLocale = this.locale;
this.cachedTimezone = this.timezone;

//this.cachedOutput = moment(value).tz(this.timezone).locale(this.locale).format(format || 'x').toString();

this.cachedOutput = DateTime
.fromISO(value instanceof Date ? value.toDateString() : value)
.setZone(this.timezone)
Expand Down
12 changes: 6 additions & 6 deletions src/app/shared/services/configuration-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export class ConfigurationService {
public static configuration: ApplicationConfigurationInterface;
public static initialized: boolean = false;

private static configurationFile: string = `/assets/config/config.${ environment.name }.json`;
private static configurationFileLocal: string = `/assets/config/config.${ environment.name }.local.json`;
private static configurationFile: string = `/assets/config/config.${environment.name}.json`;
private static configurationFileLocal: string = `/assets/config/config.${environment.name}.local.json`;

/**
* Method to initialize application configuration for production and
Expand All @@ -32,7 +32,7 @@ export class ConfigurationService {
.then((): void => resolve())
.catch((error: string): void => {
console.warn(error);
console.warn(`Fallback to '${ ConfigurationService.configurationFile }' configuration file`);
console.warn(`Fallback to '${ConfigurationService.configurationFile}' configuration file`);

ConfigurationService.loadConfiguration(ConfigurationService.configurationFile)
.then((): void => resolve())
Expand All @@ -49,7 +49,7 @@ export class ConfigurationService {
const ts = Math.round((new Date()).getTime() / 1000);

return new Promise<void>((resolve: () => void, reject: (error: Error) => any): void => {
fetch(`${ configurationFile }?t=${ ts }`)
fetch(`${configurationFile}?t=${ts}`)
.then((response: Response): void => {
response
.json()
Expand All @@ -59,9 +59,9 @@ export class ConfigurationService {

resolve();
})
.catch((error: string): void => reject(new Error(`Invalid JSON in file '${ configurationFile }' - ${ error }`)));
.catch((error: string): void => reject(new Error(`Invalid JSON in file '${configurationFile}' - ${error}`)));
})
.catch((error: string): void => reject(new Error(`Could not load file '${ configurationFile }' - ${ error }`)));
.catch((error: string): void => reject(new Error(`Could not load file '${configurationFile}' - ${error}`)));
});
}
}
4 changes: 2 additions & 2 deletions src/app/shared/services/http-cache.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';

import { HttpCacheInterface } from 'src/app/shared/interfaces';
import { HttpCacheType } from 'src/app/shared/types';

@Injectable({
providedIn: 'root',
})
export class HttpCacheService {
private requests: HttpCacheInterface;
private requests: HttpCacheType;

public constructor() {
this.requests = {};
Expand Down
4 changes: 2 additions & 2 deletions src/app/shared/services/version.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class VersionService {

return new Observable((observer: Observer<string>): void => {
this.httpClient
.get(`/assets/version.json?t=${ ts }`)
.get(`/assets/version.json?t=${ts}`)
.pipe(take(1))
.subscribe({
next: (data: VersionInterface|any): void => observer.next(data.version),
Expand All @@ -46,7 +46,7 @@ export class VersionService {

return new Observable((observer: Observer<string>): void => {
this.httpClient
.get(`${ConfigurationService.configuration.apiUrl}/version?t=${ ts }`)
.get(`${ConfigurationService.configuration.apiUrl}/version?t=${ts}`)
.pipe(take(1))
.subscribe({
next: (data: VersionInterface|any): void => observer.next(data.version),
Expand Down
6 changes: 6 additions & 0 deletions src/app/shared/types/http-cache.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { HttpResponse } from '@angular/common/http';

export type HttpCacheType = Record<string, {
response: HttpResponse<any>,
timestamp: number,
}>;
1 change: 1 addition & 0 deletions src/app/shared/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'src/app/shared/types/http-cache.type';
37 changes: 20 additions & 17 deletions src/app/store/authentication/authentication.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,28 @@ const selectFilteredError = pipe(
* This selector can return a boolean or UrlTree value according to given
* metaData object. See `BaseRole` class for more information about this.
*/
const selectRoleGuard =
(role: Role, metaData: RoleGuardMetaDataInterface, router: Router): MemoizedSelector<Partial<AuthenticationState>, boolean|UrlTree> =>
createSelector(
selectIsLoggedIn,
selectRoles,
(isLoggedIn: boolean, userRoles: Array<Role>): boolean|UrlTree => {
let output;
const selectRoleGuard = (
role: Role,
metaData: RoleGuardMetaDataInterface,
router: Router,
): MemoizedSelector<Partial<AuthenticationState>, boolean|UrlTree> =>
createSelector(
selectIsLoggedIn,
selectRoles,
(isLoggedIn: boolean, userRoles: Array<Role>): boolean|UrlTree => {
let output;

if (!isLoggedIn) {
output = metaData.redirect ? router.parseUrl(metaData.routeNotLoggedIn) : false;
} else if (!userRoles.includes(role)) {
output = metaData.redirect ? router.parseUrl(metaData.routeNoRole) : false;
} else {
output = true;
}
if (!isLoggedIn) {
output = metaData.redirect ? router.parseUrl(metaData.routeNotLoggedIn) : false;
} else if (!userRoles.includes(role)) {
output = metaData.redirect ? router.parseUrl(metaData.routeNoRole) : false;
} else {
output = true;
}

return output;
},
);
return output;
},
);

// Export all store selectors, so that those can be used easily.
export const authenticationSelectors = {
Expand Down
9 changes: 3 additions & 6 deletions src/app/store/authentication/authentication.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,12 @@ export enum AuthenticationType {
}

// Authentication login types
export type AuthenticationLoginTypes =
AuthenticationType.LOGIN_SUCCESS
export type AuthenticationLoginTypes = AuthenticationType.LOGIN_SUCCESS
| AuthenticationType.LOGIN_FAILURE;

// Authentication profile types
export type AuthenticationProfileTypes =
AuthenticationType.PROFILE_SUCCESS
export type AuthenticationProfileTypes = AuthenticationType.PROFILE_SUCCESS
| AuthenticationType.PROFILE_FAILURE;

export type AuthenticationLoginSuccessTypes =
AuthenticationType.PROFILE
export type AuthenticationLoginSuccessTypes = AuthenticationType.PROFILE
| LayoutType.UPDATE_LOCALIZATION;
4 changes: 2 additions & 2 deletions src/app/store/layout/layout.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const changeLocale = createAction(LayoutType.CHANGE_LOCALE, props<{ locale: Loca
const changeTimezone = createAction(LayoutType.CHANGE_TIMEZONE, props<{ timezone: string }>());
const changeTheme = createAction(LayoutType.CHANGE_THEME, props<{ theme: Theme }>());
const setLanguage = createAction(LayoutType.SET_LANGUAGE, props<{ language: Language }>());
const scrollTo = createAction(LayoutType.SCROLL_TO, props<{ anchor: string; instant?: boolean }>());
const scrollTo = createAction(LayoutType.SCROLL_TO, props<{ anchor: string, instant?: boolean }>());

/**
* Action to trigger browser to scroll to top of the page. This action is
Expand All @@ -52,7 +52,7 @@ const updateLocalization = createAction(LayoutType.UPDATE_LOCALIZATION, props<{

const snackbarMessage = createAction(
LayoutType.SNACKBAR_MESSAGE,
props<{ message: string; duration?: number; params?: DictionaryInterface<string> }>(),
props<{ message: string, duration?: number, params?: DictionaryInterface<string> }>(),
);

/**
Expand Down
Loading
Loading