diff --git a/apps/isa-app/src/app/app.module.ts b/apps/isa-app/src/app/app.module.ts index 4e03a7d67..04021c29f 100644 --- a/apps/isa-app/src/app/app.module.ts +++ b/apps/isa-app/src/app/app.module.ts @@ -1,10 +1,17 @@ import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import { ErrorHandler, Injector, LOCALE_ID, NgModule, inject, provideAppInitializer } from '@angular/core'; +import { + ErrorHandler, + Injector, + LOCALE_ID, + NgModule, + inject, + provideAppInitializer, +} from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { PlatformModule } from '@angular/cdk/platform'; -import { Config, ConfigModule, JsonConfigLoader } from '@core/config'; +import { Config } from '@core/config'; import { AuthModule, AuthService, LoginStrategy } from '@core/auth'; import { CoreCommandModule } from '@core/command'; @@ -66,7 +73,6 @@ export function _appInitializerFactory(config: Config, injector: Injector) { } statusElement.innerHTML = 'Konfigurationen werden geladen...'; - await config.init(); statusElement.innerHTML = 'Scanner wird initialisiert...'; const scanAdapter = injector.get(ScanAdapterService); @@ -120,7 +126,10 @@ export function _appInitializerFactory(config: Config, injector: Injector) { }; } -export function _notificationsHubOptionsFactory(config: Config, auth: AuthService): SignalRHubOptions { +export function _notificationsHubOptionsFactory( + config: Config, + auth: AuthService, +): SignalRHubOptions { const options = { ...config.get('hubs').notifications }; options.httpOptions.accessTokenFactory = () => auth.getToken(); return options; @@ -137,10 +146,6 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic AppSwaggerModule, AppDomainModule, CoreBreadcrumbModule.forRoot(), - ConfigModule.forRoot({ - useConfigLoader: JsonConfigLoader, - jsonConfigLoaderUrl: '/config/config.json', - }), CoreCommandModule.forRoot(Object.values(Commands)), CoreLoggerModule.forRoot(), AppStoreModule, @@ -186,7 +191,11 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic }, { provide: LOCALE_ID, useValue: 'de-DE' }, provideHttpClient(withInterceptorsFromDi()), - provideMatomo({ trackerUrl: 'https://matomo.paragon-data.net', siteId: '1' }, withRouter(), withRouteData()), + provideMatomo( + { trackerUrl: 'https://matomo.paragon-data.net', siteId: '1' }, + withRouter(), + withRouteData(), + ), ], }) export class AppModule {} diff --git a/apps/isa-app/src/core/config/config-loaders/config-loader.ts b/apps/isa-app/src/core/config/config-loaders/config-loader.ts deleted file mode 100644 index f8222460d..000000000 --- a/apps/isa-app/src/core/config/config-loaders/config-loader.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Observable } from 'rxjs'; - -/** - * Config loader interface for loading configurations - */ -export interface ConfigLoader { - load(): Promise; -} diff --git a/apps/isa-app/src/core/config/config-loaders/index.ts b/apps/isa-app/src/core/config/config-loaders/index.ts deleted file mode 100644 index be3543595..000000000 --- a/apps/isa-app/src/core/config/config-loaders/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// start:ng42.barrel -export * from './config-loader'; -export * from './json.config-loader'; -// end:ng42.barrel diff --git a/apps/isa-app/src/core/config/config-loaders/json.config-loader.spec.ts b/apps/isa-app/src/core/config/config-loaders/json.config-loader.spec.ts deleted file mode 100644 index e7da234c4..000000000 --- a/apps/isa-app/src/core/config/config-loaders/json.config-loader.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -// // unit test JsonConfigLoader -// import { HttpTestingController } from '@angular/common/http/testing'; -// import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; -// import { CORE_JSON_CONFIG_LOADER_URL } from '../tokens'; -// import { JsonConfigLoader } from './json.config-loader'; - -// describe('JsonConfigLoader', () => { -// let spectator: SpectatorService; -// const createService = createServiceFactory({ -// imports: [HttpClientTestingModule], -// service: JsonConfigLoader, -// mocks: [], -// providers: [{ provide: CORE_JSON_CONFIG_LOADER_URL, useValue: '/assets/config.json' }], -// }); -// let httpTestingController: HttpTestingController; - -// beforeEach(() => { -// spectator = createService(); -// httpTestingController = spectator.inject(HttpTestingController); -// }); - -// it('should create', () => { -// expect(spectator.service).toBeTruthy(); -// }); - -// describe('load', () => { -// it('should call the provided url', async () => { -// const reqPromise = spectator.service.load(); -// const req = httpTestingController.expectOne('/assets/config.json'); -// req.flush({ unit: 'test' }); -// const result = await reqPromise; -// httpTestingController.verify(); -// expect(result).toEqual({ unit: 'test' }); -// }); -// }); -// }); diff --git a/apps/isa-app/src/core/config/config-loaders/json.config-loader.ts b/apps/isa-app/src/core/config/config-loaders/json.config-loader.ts deleted file mode 100644 index cf5cbb5ec..000000000 --- a/apps/isa-app/src/core/config/config-loaders/json.config-loader.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { Inject, Injectable } from '@angular/core'; -import { ConfigLoader } from './config-loader'; -import { CORE_JSON_CONFIG_LOADER_URL } from '../tokens'; - -@Injectable() -export class JsonConfigLoader implements ConfigLoader { - constructor( - @Inject(CORE_JSON_CONFIG_LOADER_URL) private url: string, - private http: HttpClient, - ) {} - - load(): Promise { - return this.http.get(this.url).toPromise(); - } -} diff --git a/apps/isa-app/src/core/config/config-module-options.ts b/apps/isa-app/src/core/config/config-module-options.ts deleted file mode 100644 index 21a488736..000000000 --- a/apps/isa-app/src/core/config/config-module-options.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Type } from '@angular/core'; -import { ConfigLoader } from './config-loaders'; - -export interface ConfigModuleOptions { - useConfigLoader: Type; - jsonConfigLoaderUrl?: string; -} diff --git a/apps/isa-app/src/core/config/config.module.ts b/apps/isa-app/src/core/config/config.module.ts deleted file mode 100644 index e1e704c2f..000000000 --- a/apps/isa-app/src/core/config/config.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core'; -import { CORE_CONFIG_LOADER } from '@core/config'; -import { Config } from './config'; -import { ConfigModuleOptions } from './config-module-options'; -import { CORE_JSON_CONFIG_LOADER_URL } from './tokens'; - -export function _initializeConfigFactory(config: Config) { - return () => config.init(); -} - -@NgModule({}) -export class ConfigModule { - static forRoot(options: ConfigModuleOptions): ModuleWithProviders { - const configLoaderProvider = { - provide: CORE_CONFIG_LOADER, - useClass: options.useConfigLoader, - }; - - return { - ngModule: ConfigModule, - providers: [ - Config, - configLoaderProvider, - options.jsonConfigLoaderUrl - ? { provide: CORE_JSON_CONFIG_LOADER_URL, useValue: options.jsonConfigLoaderUrl } - : null, - ], - }; - } -} diff --git a/apps/isa-app/src/core/config/config.spec.ts b/apps/isa-app/src/core/config/config.spec.ts deleted file mode 100644 index 293bfeb14..000000000 --- a/apps/isa-app/src/core/config/config.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; -import { Config } from './config'; -import { ConfigLoader } from './config-loaders'; -import { CORE_CONFIG_LOADER } from './tokens'; - -class TestConfigLoader implements ConfigLoader { - load() { - return Promise.resolve({}); - } -} - -// Unit test Config -describe('Config', () => { - let spectator: SpectatorService; - const createService = createServiceFactory({ - service: Config, - providers: [{ provide: CORE_CONFIG_LOADER, useClass: TestConfigLoader }], - }); - let configLoader: ConfigLoader; - - beforeEach(() => { - spectator = createService(); - configLoader = spectator.inject(CORE_CONFIG_LOADER); - }); - - it('should create', () => { - expect(spectator.service).toBeTruthy(); - }); - - describe('init()', () => { - it('should load config and assigns it to _config', async () => { - const config = { unit: 'test' }; - spyOn(configLoader, 'load').and.returnValue(Promise.resolve(config)); - await spectator.service.init(); - expect(spectator.service['_config']).toEqual(config); - }); - }); - - describe('get()', () => { - it('should return config value', () => { - spectator.service['_config'] = { test: 'test' }; - expect(spectator.service.get('test')).toEqual('test'); - }); - }); -}); diff --git a/apps/isa-app/src/core/config/config.ts b/apps/isa-app/src/core/config/config.ts deleted file mode 100644 index e21d298e6..000000000 --- a/apps/isa-app/src/core/config/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Inject, Injectable } from '@angular/core'; -import { ReplaySubject } from 'rxjs'; -import { ConfigLoader } from './config-loaders'; -import { CORE_CONFIG_LOADER } from './tokens'; -import { pick } from './utils'; - -@Injectable() -export class Config { - private _config: any; - - private readonly _initilized = new ReplaySubject(1); - get initialized() { - return this._initilized.asObservable(); - } - - constructor(@Inject(CORE_CONFIG_LOADER) private readonly _configLoader: ConfigLoader) {} - - // load config and assign it to this._config - async init() { - this._config = await this._configLoader.load(); - this._initilized.next(); - } - - get(path: string) { - return pick(path, this._config); - } -} diff --git a/apps/isa-app/src/core/config/index.ts b/apps/isa-app/src/core/config/index.ts deleted file mode 100644 index b2a88e447..000000000 --- a/apps/isa-app/src/core/config/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './config-loaders'; -export * from './config-module-options'; -export * from './config.module'; -export * from './config'; -export * from './tokens'; -export * from './utils'; diff --git a/apps/isa-app/src/core/config/tokens.ts b/apps/isa-app/src/core/config/tokens.ts deleted file mode 100644 index b8b72f761..000000000 --- a/apps/isa-app/src/core/config/tokens.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { InjectionToken } from '@angular/core'; -import { ConfigLoader } from './config-loaders'; - -export const CORE_CONFIG_LOADER = new InjectionToken('core.config.loader'); - -export const CORE_JSON_CONFIG_LOADER_URL = new InjectionToken('core.json.config.loader.url'); diff --git a/apps/isa-app/src/core/config/utils/index.ts b/apps/isa-app/src/core/config/utils/index.ts deleted file mode 100644 index ba6a59327..000000000 --- a/apps/isa-app/src/core/config/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// start:ng42.barrel -export * from './pick'; -// end:ng42.barrel diff --git a/apps/isa-app/src/core/config/utils/pick.spec.ts b/apps/isa-app/src/core/config/utils/pick.spec.ts deleted file mode 100644 index 0097582b7..000000000 --- a/apps/isa-app/src/core/config/utils/pick.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { pick } from './pick'; - -describe('pick', () => { - it('should pick properties from the 1st level from the object', () => { - const obj = { - foo: 'bar', - }; - expect(pick('foo', obj)).toEqual('bar'); - }); - - it('should pick properties from the 2nd level from the object', () => { - const obj = { - foo: { - bar: 'baz', - }, - }; - expect(pick('foo.bar', obj)).toEqual('baz'); - }); - - it('should pick properties from the 3rd level from the object', () => { - const obj = { - foo: { - bar: { - baz: 'qux', - }, - }, - }; - expect(pick('foo.bar.baz', obj)).toEqual('qux'); - }); - - it('should throw an error of obj is not an object', () => { - expect(() => pick('foo', 'bar')).toThrowError(`bar is not an object`); - }); - - it('should return undefined if the property is not found', () => { - const obj = { - foo: 'bar', - }; - expect(pick('bar', obj)).toEqual(undefined); - }); -}); diff --git a/apps/isa-app/src/core/config/utils/pick.ts b/apps/isa-app/src/core/config/utils/pick.ts deleted file mode 100644 index 9e854b5a5..000000000 --- a/apps/isa-app/src/core/config/utils/pick.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Pick a value from an object at a given path. - * @param path path of the value to pick - * @param obj object to pick from - * @returns the value at the path or undefined - * @throws if obj is not an object - */ -export function pick(path: string, obj: object): T { - const paths = path.split('.'); - - // check if obj is null or undefined - if (obj == null) { - return undefined; - } - - // check if obj is of type object and not an array - // and throw an error if not - if (typeof obj !== 'object' || Array.isArray(obj)) { - throw new Error(`${obj} is not an object`); - } - - let result = obj; - - // loop through the path and pick the value - // early exit if the path is empty - for (const path of paths) { - result = result[path]; - if (result == null) { - return undefined; - } - } - return result as T; -} diff --git a/apps/isa-app/src/main.ts b/apps/isa-app/src/main.ts index 8538ec5e0..783e3cb2c 100644 --- a/apps/isa-app/src/main.ts +++ b/apps/isa-app/src/main.ts @@ -1,42 +1,27 @@ -import { enableProdMode } from '@angular/core'; +import { enableProdMode, isDevMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { CONFIG_DATA } from '@isa/core/config'; import * as moment from 'moment'; moment.locale('de'); import { AppModule } from './app/app.module'; -import { DebugService } from './app/debug/debug.service'; -import { environment } from './environments/environment'; -if (environment.production) { +if (!isDevMode()) { enableProdMode(); } -const debugService = new DebugService(); +async function bootstrap() { + const configRes = await fetch('/config/config.json'); -if (environment.debug) { - const consoleLog = console.log; + const config = await configRes.json(); - console.log = (...args) => { - debugService.add({ type: 'log', args }); - consoleLog(...args); - }; - - const consoleWarn = console.warn; - - console.warn = (...args) => { - debugService.add({ type: 'warn', args }); - consoleWarn(...args); - }; - - const consoleError = console.error; - - console.error = (...args) => { - debugService.add({ type: 'error', args }); - consoleError(...args); - }; + platformBrowserDynamic([{ provide: CONFIG_DATA, useValue: config }]).bootstrapModule(AppModule); +} + +try { + bootstrap(); +} catch (error) { + console.error(error); } -platformBrowserDynamic([{ provide: DebugService, useValue: debugService }]) - .bootstrapModule(AppModule) - .catch((err) => console.error(err)); diff --git a/libs/core/config/README.md b/libs/core/config/README.md new file mode 100644 index 000000000..3fcab5cd0 --- /dev/null +++ b/libs/core/config/README.md @@ -0,0 +1,7 @@ +# core-config + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test core-config` to execute the unit tests. diff --git a/libs/core/config/eslint.config.mjs b/libs/core/config/eslint.config.mjs new file mode 100644 index 000000000..3bf3b5ae0 --- /dev/null +++ b/libs/core/config/eslint.config.mjs @@ -0,0 +1,34 @@ +import nx from '@nx/eslint-plugin'; +import baseConfig from '../../../eslint.config.mjs'; + +export default [ + ...baseConfig, + ...nx.configs['flat/angular'], + ...nx.configs['flat/angular-template'], + { + files: ['**/*.ts'], + rules: { + '@angular-eslint/directive-selector': [ + 'error', + { + type: 'attribute', + prefix: 'lib', + style: 'camelCase', + }, + ], + '@angular-eslint/component-selector': [ + 'error', + { + type: 'element', + prefix: 'lib', + style: 'kebab-case', + }, + ], + }, + }, + { + files: ['**/*.html'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/libs/core/config/jest.config.ts b/libs/core/config/jest.config.ts new file mode 100644 index 000000000..b856ea2b9 --- /dev/null +++ b/libs/core/config/jest.config.ts @@ -0,0 +1,21 @@ +export default { + displayName: 'core-config', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../coverage/libs/core/config', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/libs/core/config/project.json b/libs/core/config/project.json new file mode 100644 index 000000000..ca662300a --- /dev/null +++ b/libs/core/config/project.json @@ -0,0 +1,20 @@ +{ + "name": "core-config", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/core/config/src", + "prefix": "lib", + "projectType": "library", + "tags": [], + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/core/config/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/libs/core/config/src/index.ts b/libs/core/config/src/index.ts new file mode 100644 index 000000000..db3b131b2 --- /dev/null +++ b/libs/core/config/src/index.ts @@ -0,0 +1 @@ +export * from './lib/config'; diff --git a/libs/core/config/src/lib/config.ts b/libs/core/config/src/lib/config.ts new file mode 100644 index 000000000..90e1bc2c5 --- /dev/null +++ b/libs/core/config/src/lib/config.ts @@ -0,0 +1,33 @@ +import { inject, Injectable, InjectionToken } from '@angular/core'; +import { z } from 'zod'; +import { coerceArray } from '@angular/cdk/coercion'; + +type JsonPrimitive = string | number | boolean | null | undefined; + +export type JsonValue = Array | Record | JsonPrimitive; + +export const CONFIG_DATA = new InjectionToken('ConfigData'); + +@Injectable({ providedIn: 'root' }) +export class Config { + #config = inject(CONFIG_DATA); + + get(path: string | string[]): any; + get(path: string | string[], zSchema: z.ZodSchema): TOut; + get(path: string | string[], zSchema?: z.ZodSchema): TOut | any { + let result: JsonValue = this.#config; + + for (const p of coerceArray(path)) { + if (typeof result === 'object' && result !== null && !Array.isArray(result)) { + result = (result as Record)[p]; + } else { + return undefined; + } + if (result === null || result === undefined) { + return undefined; + } + } + + return zSchema ? zSchema.parse(result) : result; + } +} diff --git a/libs/core/config/src/test-setup.ts b/libs/core/config/src/test-setup.ts new file mode 100644 index 000000000..ea414013f --- /dev/null +++ b/libs/core/config/src/test-setup.ts @@ -0,0 +1,6 @@ +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; + +setupZoneTestEnv({ + errorOnUnknownElements: true, + errorOnUnknownProperties: true, +}); diff --git a/libs/core/config/tsconfig.json b/libs/core/config/tsconfig.json new file mode 100644 index 000000000..fde35eab0 --- /dev/null +++ b/libs/core/config/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es2022", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/libs/core/config/tsconfig.lib.json b/libs/core/config/tsconfig.lib.json new file mode 100644 index 000000000..9b49be758 --- /dev/null +++ b/libs/core/config/tsconfig.lib.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "src/**/*.spec.ts", + "src/test-setup.ts", + "jest.config.ts", + "src/**/*.test.ts" + ], + "include": ["src/**/*.ts"] +} diff --git a/libs/core/config/tsconfig.spec.json b/libs/core/config/tsconfig.spec.json new file mode 100644 index 000000000..f858ef78c --- /dev/null +++ b/libs/core/config/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/libs/shared/product-image/README.md b/libs/shared/product-image/README.md new file mode 100644 index 000000000..da93db267 --- /dev/null +++ b/libs/shared/product-image/README.md @@ -0,0 +1,7 @@ +# shared-product-image + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test shared-product-image` to execute the unit tests. diff --git a/libs/shared/product-image/eslint.config.mjs b/libs/shared/product-image/eslint.config.mjs new file mode 100644 index 000000000..7cda994a7 --- /dev/null +++ b/libs/shared/product-image/eslint.config.mjs @@ -0,0 +1,34 @@ +import nx from '@nx/eslint-plugin'; +import baseConfig from '../../../eslint.config.mjs'; + +export default [ + ...baseConfig, + ...nx.configs['flat/angular'], + ...nx.configs['flat/angular-template'], + { + files: ['**/*.ts'], + rules: { + '@angular-eslint/directive-selector': [ + 'error', + { + type: 'attribute', + prefix: 'shared', + style: 'camelCase', + }, + ], + '@angular-eslint/component-selector': [ + 'error', + { + type: 'element', + prefix: 'shared', + style: 'kebab-case', + }, + ], + }, + }, + { + files: ['**/*.html'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/libs/shared/product-image/jest.config.ts b/libs/shared/product-image/jest.config.ts new file mode 100644 index 000000000..c06614420 --- /dev/null +++ b/libs/shared/product-image/jest.config.ts @@ -0,0 +1,21 @@ +export default { + displayName: 'shared-product-image', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../coverage/libs/shared/product-image', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/libs/shared/product-image/project.json b/libs/shared/product-image/project.json new file mode 100644 index 000000000..99e5fed86 --- /dev/null +++ b/libs/shared/product-image/project.json @@ -0,0 +1,20 @@ +{ + "name": "shared-product-image", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/shared/product-image/src", + "prefix": "lib", + "projectType": "library", + "tags": [], + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/shared/product-image/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/libs/shared/product-image/src/index.ts b/libs/shared/product-image/src/index.ts new file mode 100644 index 000000000..a1e3f66d9 --- /dev/null +++ b/libs/shared/product-image/src/index.ts @@ -0,0 +1 @@ +export * from './lib/product-image.service'; diff --git a/libs/shared/product-image/src/lib/product-image.service.ts b/libs/shared/product-image/src/lib/product-image.service.ts new file mode 100644 index 000000000..05ad4c8a7 --- /dev/null +++ b/libs/shared/product-image/src/lib/product-image.service.ts @@ -0,0 +1,26 @@ +import { inject, Injectable, InjectionToken } from '@angular/core'; +import { Config } from '@isa/core/config'; +import { z } from 'zod'; + +export const PRODUCT_IMAGE_URL = new InjectionToken('PRODUCT_IMAGE_URL', { + factory: () => inject(Config).get('@cdn/product-image.url', z.string().url()), +}); + +@Injectable({ providedIn: 'root' }) +export class ProductImageService { + readonly imageUrl = inject(PRODUCT_IMAGE_URL); + + getImageUrl({ + imageId, + width = 150, + height = 150, + showDummy = true, + }: { + imageId: string; + width?: number; + height?: number; + showDummy?: boolean; + }): string { + return `${this.imageUrl}/${imageId}_${width}x${height}.jpg?showDummy=${showDummy}`; + } +} diff --git a/libs/shared/product-image/src/test-setup.ts b/libs/shared/product-image/src/test-setup.ts new file mode 100644 index 000000000..ea414013f --- /dev/null +++ b/libs/shared/product-image/src/test-setup.ts @@ -0,0 +1,6 @@ +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; + +setupZoneTestEnv({ + errorOnUnknownElements: true, + errorOnUnknownProperties: true, +}); diff --git a/libs/shared/product-image/tsconfig.json b/libs/shared/product-image/tsconfig.json new file mode 100644 index 000000000..fde35eab0 --- /dev/null +++ b/libs/shared/product-image/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es2022", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/libs/shared/product-image/tsconfig.lib.json b/libs/shared/product-image/tsconfig.lib.json new file mode 100644 index 000000000..9b49be758 --- /dev/null +++ b/libs/shared/product-image/tsconfig.lib.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "src/**/*.spec.ts", + "src/test-setup.ts", + "jest.config.ts", + "src/**/*.test.ts" + ], + "include": ["src/**/*.ts"] +} diff --git a/libs/shared/product-image/tsconfig.spec.json b/libs/shared/product-image/tsconfig.spec.json new file mode 100644 index 000000000..f858ef78c --- /dev/null +++ b/libs/shared/product-image/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 7c58c85b7..493f2a820 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -43,9 +43,12 @@ ], "@generated/swagger/wws-api": ["generated/swagger/wws-api/src/index.ts"], "@hub/*": ["apps/isa-app/src/hub/*/index.ts"], + "@isa/core/config": ["libs/core/config/src/index.ts"], + "@core/config": ["libs/core/config/src/index.ts"], // fallback for old imports "@isa/core/process": ["libs/core/process/src/index.ts"], "@isa/icons": ["libs/icons/src/index.ts"], "@isa/shared/filter": ["libs/shared/filter/src/index.ts"], + "@isa/shared/product-image": ["libs/shared/product-image/src/index.ts"], "@isa/ui/buttons": ["libs/ui/buttons/src/index.ts"], "@isa/ui/input-controls": ["libs/ui/input-controls/src/index.ts"], "@isa/ui/search-bar": ["libs/ui/search-bar/src/index.ts"],