From 0cfcd0b42016fe3d21022caeb1f4b436449d65b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A6var=20M=C3=A1r=20Atlason?= <54210288+saevarma@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:11:58 +0000 Subject: [PATCH 01/13] fix(auth-nest-tools): Client claims prefix is defaulted to `client_` with single underscore (#15954) --- libs/auth-nest-tools/src/lib/jwt.payload.ts | 2 +- libs/auth-nest-tools/src/lib/jwt.strategy.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/auth-nest-tools/src/lib/jwt.payload.ts b/libs/auth-nest-tools/src/lib/jwt.payload.ts index 7592547f9024..a06394aaeb4d 100644 --- a/libs/auth-nest-tools/src/lib/jwt.payload.ts +++ b/libs/auth-nest-tools/src/lib/jwt.payload.ts @@ -21,5 +21,5 @@ export interface JwtPayload { scope?: string | string[] } audkenni_sim_number?: string - client__delegation_provider?: AuthDelegationProvider + client_delegation_provider?: AuthDelegationProvider } diff --git a/libs/auth-nest-tools/src/lib/jwt.strategy.ts b/libs/auth-nest-tools/src/lib/jwt.strategy.ts index 119d5954798b..5796cf1eade3 100644 --- a/libs/auth-nest-tools/src/lib/jwt.strategy.ts +++ b/libs/auth-nest-tools/src/lib/jwt.strategy.ts @@ -61,7 +61,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { ip: String(request.headers['x-forwarded-for'] ?? request.ip), userAgent: request.headers['user-agent'], audkenniSimNumber: payload.audkenni_sim_number, - delegationProvider: payload.client__delegation_provider, + delegationProvider: payload.client_delegation_provider, } } } From 1cc48459b3e26420496842f4dab69d3bc3ffe554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnlaugur=20Gu=C3=B0mundsson?= <34029342+GunnlaugurG@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:51:37 +0000 Subject: [PATCH 02/13] feat(auth-admin): Delegation admin module (#15867) * created new admin module for paper delegations and get route * field resolver DelegationAdminModel * lookup for delegations with detailed view * Cleanup and rest and graphql for DeleteDelegation * small cleanup * chore: nx format:write update dirty files * move delegationAdmin service to admin-api from delegation-api * chore: nx format:write update dirty files * fix config value * chore: charts update dirty files * fix api build issues * fix pr comments * remove resolved comment --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/portals/admin/src/auth.ts | 2 + .../portals/admin/src/lib/masterNavigation.ts | 4 + apps/portals/admin/src/lib/modules.ts | 2 + .../auth/admin-api/infra/auth-admin-api.ts | 38 ++++- .../auth/admin-api/src/app/app.module.ts | 18 ++- .../delegation-admin.controller.ts | 98 +++++++++++++ .../v2/delegations/delegation-admin.module.ts | 12 ++ charts/identity-server/values.dev.yaml | 9 ++ charts/identity-server/values.prod.yaml | 9 ++ charts/identity-server/values.staging.yaml | 9 ++ .../auth-admin/src/lib/auth-admin.module.ts | 11 +- .../delegation-admin.resolver.ts | 115 +++++++++++++++ .../delegation-admin.service.ts | 42 ++++++ .../models/delegation.model.ts | 17 +++ .../delegation-provider.resolver.ts | 2 +- libs/api/domains/auth/src/index.ts | 8 + libs/auth-api-lib/src/index.ts | 2 + .../admin/delegation-admin-custom.service.ts | 138 ++++++++++++++++++ .../delegations/delegation-scope.service.ts | 7 +- .../src/lib/delegations/delegations.module.ts | 3 + .../dto/delegation-admin-custom.dto.ts | 11 ++ .../auth/scopes/src/lib/admin-portal.scope.ts | 2 + libs/auth/scopes/src/lib/auth.scope.ts | 5 + libs/clients/auth/admin-api/src/index.ts | 2 + libs/clients/auth/admin-api/src/lib/apis.ts | 55 ++++--- .../src/lib/auth-admin-api-client.config.ts | 7 + libs/clients/auth/delegation-api/src/index.ts | 2 + libs/portals/admin/delegation-admin/.babelrc | 12 ++ .../admin/delegation-admin/.eslintrc.json | 18 +++ libs/portals/admin/delegation-admin/README.md | 7 + .../admin/delegation-admin/codegen.yml | 18 +++ .../admin/delegation-admin/jest.config.ts | 11 ++ .../admin/delegation-admin/project.json | 26 ++++ .../src/components/DelegationList.tsx | 30 ++++ .../admin/delegation-admin/src/index.ts | 3 + .../delegation-admin/src/lib/messages.ts | 44 ++++++ .../delegation-admin/src/lib/navigation.ts | 21 +++ .../admin/delegation-admin/src/lib/paths.ts | 4 + .../admin/delegation-admin/src/module.tsx | 42 ++++++ .../DelegationAdmin.graphql | 58 ++++++++ .../DelegationAdmin.loader.ts | 54 +++++++ .../DelegationAdmin.tsx | 72 +++++++++ .../src/screens/Root.action.ts | 70 +++++++++ .../delegation-admin/src/screens/Root.tsx | 72 +++++++++ .../admin/delegation-admin/tsconfig.json | 20 +++ .../admin/delegation-admin/tsconfig.lib.json | 24 +++ .../admin/delegation-admin/tsconfig.spec.json | 20 +++ .../src/components/access/AccessCard.tsx | 12 +- .../incoming/DelegationsIncoming.tsx | 1 + .../shared-modules/delegations/src/index.ts | 4 + .../testing/src/fixtures/fixture-factory.ts | 2 +- tsconfig.base.json | 3 +- 52 files changed, 1247 insertions(+), 31 deletions(-) create mode 100644 apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.controller.ts create mode 100644 apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.module.ts create mode 100644 libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.resolver.ts create mode 100644 libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.service.ts create mode 100644 libs/api/domains/auth-admin/src/lib/delegationAdmin/models/delegation.model.ts create mode 100644 libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts create mode 100644 libs/auth-api-lib/src/lib/delegations/dto/delegation-admin-custom.dto.ts create mode 100644 libs/portals/admin/delegation-admin/.babelrc create mode 100644 libs/portals/admin/delegation-admin/.eslintrc.json create mode 100644 libs/portals/admin/delegation-admin/README.md create mode 100644 libs/portals/admin/delegation-admin/codegen.yml create mode 100644 libs/portals/admin/delegation-admin/jest.config.ts create mode 100644 libs/portals/admin/delegation-admin/project.json create mode 100644 libs/portals/admin/delegation-admin/src/components/DelegationList.tsx create mode 100644 libs/portals/admin/delegation-admin/src/index.ts create mode 100644 libs/portals/admin/delegation-admin/src/lib/messages.ts create mode 100644 libs/portals/admin/delegation-admin/src/lib/navigation.ts create mode 100644 libs/portals/admin/delegation-admin/src/lib/paths.ts create mode 100644 libs/portals/admin/delegation-admin/src/module.tsx create mode 100644 libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.graphql create mode 100644 libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.loader.ts create mode 100644 libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx create mode 100644 libs/portals/admin/delegation-admin/src/screens/Root.action.ts create mode 100644 libs/portals/admin/delegation-admin/src/screens/Root.tsx create mode 100644 libs/portals/admin/delegation-admin/tsconfig.json create mode 100644 libs/portals/admin/delegation-admin/tsconfig.lib.json create mode 100644 libs/portals/admin/delegation-admin/tsconfig.spec.json diff --git a/apps/portals/admin/src/auth.ts b/apps/portals/admin/src/auth.ts index 4a081c7312ea..944cdc454551 100644 --- a/apps/portals/admin/src/auth.ts +++ b/apps/portals/admin/src/auth.ts @@ -38,6 +38,8 @@ if (userMocked) { AdminPortalScope.signatureCollectionProcess, AdminPortalScope.formSystem, AdminPortalScope.formSystemSuperUser, + AdminPortalScope.delegationSystem, + AdminPortalScope.delegationSystemAdmin, ], post_logout_redirect_uri: `${window.location.origin}`, userStorePrefix: 'ap.', diff --git a/apps/portals/admin/src/lib/masterNavigation.ts b/apps/portals/admin/src/lib/masterNavigation.ts index 5405d89aee5c..0a02cb5e4030 100644 --- a/apps/portals/admin/src/lib/masterNavigation.ts +++ b/apps/portals/admin/src/lib/masterNavigation.ts @@ -14,6 +14,7 @@ import { signatureCollectionNavigation } from '@island.is/portals/admin/signatur import { serviceDeskNavigation } from '@island.is/portals/admin/service-desk' import { petitionNavigation } from '@island.is/portals/admin/petition' import { formSystemNavigation } from '@island.is/form-system' +import { delegationAdminNav } from 'delegation-admin' export const rootNavigationItem: PortalNavigationItem = { name: coreMessages.overview, @@ -44,7 +45,10 @@ export const TOP_NAVIGATION: PortalNavigationItem = { serviceDeskNavigation, // Meðmælasöfnun signatureCollectionNavigation, + // Formakerfi formSystemNavigation, + // Aðgangsstýring umboð + delegationAdminNav, ], } export const BOTTOM_NAVIGATION: PortalNavigationItem = { diff --git a/apps/portals/admin/src/lib/modules.ts b/apps/portals/admin/src/lib/modules.ts index 73fc69898b39..9a840d14262d 100644 --- a/apps/portals/admin/src/lib/modules.ts +++ b/apps/portals/admin/src/lib/modules.ts @@ -10,6 +10,7 @@ import { petitionModule } from '@island.is/portals/admin/petition' import { serviceDeskModule } from '@island.is/portals/admin/service-desk' import { signatureCollectionModule } from '@island.is/portals/admin/signature-collection' import { formSystemModule } from '@island.is/form-system' +import { delegationAdminModule } from 'delegation-admin' /** * NOTE: @@ -30,4 +31,5 @@ export const modules: PortalModule[] = [ serviceDeskModule, signatureCollectionModule, formSystemModule, + delegationAdminModule, ] diff --git a/apps/services/auth/admin-api/infra/auth-admin-api.ts b/apps/services/auth/admin-api/infra/auth-admin-api.ts index 04b9465afa8b..439643e2763b 100644 --- a/apps/services/auth/admin-api/infra/auth-admin-api.ts +++ b/apps/services/auth/admin-api/infra/auth-admin-api.ts @@ -1,4 +1,21 @@ -import { json, service, ServiceBuilder } from '../../../../../infra/src/dsl/dsl' +import { + json, + ref, + service, + ServiceBuilder, +} from '../../../../../infra/src/dsl/dsl' + +const REDIS_NODE_CONFIG = { + dev: json([ + 'clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379', + ]), + staging: json([ + 'clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379', + ]), + prod: json([ + 'clustercfg.general-redis-cluster-group.dnugi2.euw1.cache.amazonaws.com:6379', + ]), +} export const serviceSetup = (): ServiceBuilder<'services-auth-admin-api'> => { return service('services-auth-admin-api') @@ -25,10 +42,29 @@ export const serviceSetup = (): ServiceBuilder<'services-auth-admin-api'> => { ]), prod: json(['https://innskra.island.is']), }, + XROAD_NATIONAL_REGISTRY_ACTOR_TOKEN: 'true', + XROAD_RSK_PROCURING_ACTOR_TOKEN: 'true', + XROAD_NATIONAL_REGISTRY_SERVICE_PATH: { + dev: 'IS-DEV/GOV/10001/SKRA-Protected/Einstaklingar-v1', + staging: 'IS-TEST/GOV/6503760649/SKRA-Protected/Einstaklingar-v1', + prod: 'IS/GOV/6503760649/SKRA-Protected/Einstaklingar-v1', + }, + XROAD_NATIONAL_REGISTRY_REDIS_NODES: REDIS_NODE_CONFIG, + XROAD_RSK_PROCURING_REDIS_NODES: REDIS_NODE_CONFIG, + COMPANY_REGISTRY_XROAD_PROVIDER_ID: { + dev: 'IS-DEV/GOV/10006/Skatturinn/ft-v1', + staging: 'IS-TEST/GOV/5402696029/Skatturinn/ft-v1', + prod: 'IS/GOV/5402696029/Skatturinn/ft-v1', + }, + COMPANY_REGISTRY_REDIS_NODES: REDIS_NODE_CONFIG, }) .secrets({ CLIENT_SECRET_ENCRYPTION_KEY: '/k8s/services-auth/admin-api/CLIENT_SECRET_ENCRYPTION_KEY', + IDENTITY_SERVER_CLIENT_SECRET: + '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET', + NATIONAL_REGISTRY_IDS_CLIENT_SECRET: + '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET', }) .ingress({ primary: { diff --git a/apps/services/auth/admin-api/src/app/app.module.ts b/apps/services/auth/admin-api/src/app/app.module.ts index 68b95ae3ed59..b409548fb8a2 100644 --- a/apps/services/auth/admin-api/src/app/app.module.ts +++ b/apps/services/auth/admin-api/src/app/app.module.ts @@ -3,6 +3,7 @@ import { ConfigModule } from '@nestjs/config' import { SequelizeModule } from '@nestjs/sequelize' import { + DelegationApiUserSystemNotificationConfig, DelegationConfig, SequelizeConfigService, } from '@island.is/auth-api-lib' @@ -24,6 +25,12 @@ import { ClientSecretsModule } from './v2/secrets/client-secrets.module' import { TenantsModule } from './v2/tenants/tenants.module' import { ScopesModule } from './v2/scopes/scopes.module' import { ProvidersModule } from './v2/providers/providers.module' +import { DelegationAdminModule } from './v2/delegations/delegation-admin.module' +import { RskRelationshipsClientConfig } from '@island.is/clients-rsk-relationships' +import { FeatureFlagConfig } from '@island.is/nest/feature-flags' +import { IdsClientConfig, XRoadConfig } from '@island.is/nest/config' +import { NationalRegistryClientConfig } from '@island.is/clients/national-registry-v2' +import { CompanyRegistryConfig } from '@island.is/clients/rsk/company-registry' @Module({ imports: [ @@ -46,9 +53,18 @@ import { ProvidersModule } from './v2/providers/providers.module' ProblemModule, ProvidersModule, ScopesModule, + DelegationAdminModule, ConfigModule.forRoot({ isGlobal: true, - load: [DelegationConfig], + load: [ + DelegationConfig, + RskRelationshipsClientConfig, + NationalRegistryClientConfig, + CompanyRegistryConfig, + FeatureFlagConfig, + XRoadConfig, + IdsClientConfig, + ], envFilePath: ['.env', '.env.secret'], }), ], diff --git a/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.controller.ts b/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.controller.ts new file mode 100644 index 000000000000..1889f4886baf --- /dev/null +++ b/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.controller.ts @@ -0,0 +1,98 @@ +import { + Controller, + Delete, + Get, + Headers, + Param, + UseGuards, +} from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' + +import { + CurrentUser, + IdsUserGuard, + Scopes, + ScopesGuard, + User, +} from '@island.is/auth-nest-tools' +import { + DelegationAdminCustomDto, + DelegationAdminCustomService, +} from '@island.is/auth-api-lib' +import { Documentation } from '@island.is/nest/swagger' +import { Audit, AuditService } from '@island.is/nest/audit' +import { DelegationAdminScopes } from '@island.is/auth/scopes' +import flatMap from 'lodash/flatMap' +import { isDefined } from '@island.is/shared/utils' + +const namespace = '@island.is/auth/delegation-admin' + +@UseGuards(IdsUserGuard, ScopesGuard) +@Scopes(DelegationAdminScopes.read) +@ApiTags('delegation-admin') +@Controller('delegation-admin') +@Audit({ namespace }) +export class DelegationAdminController { + constructor( + private readonly delegationAdminService: DelegationAdminCustomService, + private readonly auditService: AuditService, + ) {} + + @Get() + @Documentation({ + response: { status: 200, type: DelegationAdminCustomDto }, + request: { + header: { + 'X-Query-National-Id': { + required: true, + description: 'fetch delegations for this national id', + }, + }, + }, + }) + @Audit({ + resources: (delegation) => + flatMap([ + ...delegation.incoming.map((d) => d.id ?? undefined), + ...delegation.outgoing.map((d) => d.id ?? undefined), + ]).filter(isDefined), + }) + async getDelegationAdmin( + @Headers('X-Query-National-Id') nationalId: string, + ): Promise { + return await this.delegationAdminService.getAllDelegationsByNationalId( + nationalId, + ) + } + + @Delete(':delegationId') + @Scopes(DelegationAdminScopes.admin) + @Documentation({ + response: { status: 204 }, + request: { + params: { + delegationId: { + required: true, + description: 'The id of the delegation to delete', + }, + }, + }, + }) + delete( + @CurrentUser() user: User, + @Param('delegationId') delegationId: string, + ) { + return this.auditService.auditPromise( + { + auth: user, + namespace, + action: 'delete', + resources: delegationId, + meta: (deleted) => ({ + deleted, + }), + }, + this.delegationAdminService.deleteDelegation(user, delegationId), + ) + } +} diff --git a/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.module.ts b/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.module.ts new file mode 100644 index 000000000000..09d0a1dd624d --- /dev/null +++ b/apps/services/auth/admin-api/src/app/v2/delegations/delegation-admin.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common' + +import { DelegationsModule as AuthDelegationsModule } from '@island.is/auth-api-lib' +import { FeatureFlagModule } from '@island.is/nest/feature-flags' +import { DelegationAdminController } from './delegation-admin.controller' + +@Module({ + imports: [AuthDelegationsModule, FeatureFlagModule], + controllers: [DelegationAdminController], + providers: [], +}) +export class DelegationAdminModule {} diff --git a/charts/identity-server/values.dev.yaml b/charts/identity-server/values.dev.yaml index 1de58ff732d4..9494dd9708b6 100644 --- a/charts/identity-server/values.dev.yaml +++ b/charts/identity-server/values.dev.yaml @@ -213,6 +213,8 @@ namespaces: services-auth-admin-api: enabled: true env: + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS-DEV/GOV/10006/Skatturinn/ft-v1' DB_HOST: 'postgres-applications.internal' DB_NAME: 'servicesauth' DB_REPLICAS_HOST: 'postgres-applications-reader.internal' @@ -222,6 +224,11 @@ services-auth-admin-api: LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' SERVERSIDE_FEATURES_ON: '' + XROAD_NATIONAL_REGISTRY_ACTOR_TOKEN: 'true' + XROAD_NATIONAL_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' + XROAD_NATIONAL_REGISTRY_SERVICE_PATH: 'IS-DEV/GOV/10001/SKRA-Protected/Einstaklingar-v1' + XROAD_RSK_PROCURING_ACTOR_TOKEN: 'true' + XROAD_RSK_PROCURING_REDIS_NODES: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' grantNamespaces: - 'nginx-ingress-external' - 'nginx-ingress-internal' @@ -275,6 +282,8 @@ services-auth-admin-api: CLIENT_SECRET_ENCRYPTION_KEY: '/k8s/services-auth/admin-api/CLIENT_SECRET_ENCRYPTION_KEY' CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' DB_PASS: '/k8s/servicesauth/DB_PASSWORD' + IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET' + NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET' securityContext: allowPrivilegeEscalation: false privileged: false diff --git a/charts/identity-server/values.prod.yaml b/charts/identity-server/values.prod.yaml index d05db0f9e15c..e6cce69d8c6a 100644 --- a/charts/identity-server/values.prod.yaml +++ b/charts/identity-server/values.prod.yaml @@ -210,6 +210,8 @@ namespaces: services-auth-admin-api: enabled: true env: + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.dnugi2.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS/GOV/5402696029/Skatturinn/ft-v1' DB_HOST: 'postgres-ids.internal' DB_NAME: 'servicesauth' DB_REPLICAS_HOST: 'postgres-ids.internal' @@ -219,6 +221,11 @@ services-auth-admin-api: LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + XROAD_NATIONAL_REGISTRY_ACTOR_TOKEN: 'true' + XROAD_NATIONAL_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.dnugi2.euw1.cache.amazonaws.com:6379"]' + XROAD_NATIONAL_REGISTRY_SERVICE_PATH: 'IS/GOV/6503760649/SKRA-Protected/Einstaklingar-v1' + XROAD_RSK_PROCURING_ACTOR_TOKEN: 'true' + XROAD_RSK_PROCURING_REDIS_NODES: '["clustercfg.general-redis-cluster-group.dnugi2.euw1.cache.amazonaws.com:6379"]' grantNamespaces: - 'nginx-ingress-external' - 'nginx-ingress-internal' @@ -272,6 +279,8 @@ services-auth-admin-api: CLIENT_SECRET_ENCRYPTION_KEY: '/k8s/services-auth/admin-api/CLIENT_SECRET_ENCRYPTION_KEY' CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' DB_PASS: '/k8s/servicesauth/DB_PASSWORD' + IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET' + NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET' securityContext: allowPrivilegeEscalation: false privileged: false diff --git a/charts/identity-server/values.staging.yaml b/charts/identity-server/values.staging.yaml index e8b29ca0e315..9bca8e248e64 100644 --- a/charts/identity-server/values.staging.yaml +++ b/charts/identity-server/values.staging.yaml @@ -213,6 +213,8 @@ namespaces: services-auth-admin-api: enabled: true env: + COMPANY_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' + COMPANY_REGISTRY_XROAD_PROVIDER_ID: 'IS-TEST/GOV/5402696029/Skatturinn/ft-v1' DB_HOST: 'postgres-applications.internal' DB_NAME: 'servicesauth' DB_REPLICAS_HOST: 'postgres-applications.internal' @@ -222,6 +224,11 @@ services-auth-admin-api: LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' SERVERSIDE_FEATURES_ON: '' + XROAD_NATIONAL_REGISTRY_ACTOR_TOKEN: 'true' + XROAD_NATIONAL_REGISTRY_REDIS_NODES: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' + XROAD_NATIONAL_REGISTRY_SERVICE_PATH: 'IS-TEST/GOV/6503760649/SKRA-Protected/Einstaklingar-v1' + XROAD_RSK_PROCURING_ACTOR_TOKEN: 'true' + XROAD_RSK_PROCURING_REDIS_NODES: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' grantNamespaces: - 'nginx-ingress-external' - 'nginx-ingress-internal' @@ -275,6 +282,8 @@ services-auth-admin-api: CLIENT_SECRET_ENCRYPTION_KEY: '/k8s/services-auth/admin-api/CLIENT_SECRET_ENCRYPTION_KEY' CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' DB_PASS: '/k8s/servicesauth/DB_PASSWORD' + IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET' + NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET' securityContext: allowPrivilegeEscalation: false privileged: false diff --git a/libs/api/domains/auth-admin/src/lib/auth-admin.module.ts b/libs/api/domains/auth-admin/src/lib/auth-admin.module.ts index 9fd435143145..b130bdace9e6 100644 --- a/libs/api/domains/auth-admin/src/lib/auth-admin.module.ts +++ b/libs/api/domains/auth-admin/src/lib/auth-admin.module.ts @@ -13,10 +13,13 @@ import { ClientSecretLoader } from './client/client-secret.loader' import { ScopeResolver } from './scope/scope.resolver' import { ScopeService } from './scope/scope.service' import { DelegationProviderService } from './delegationProvider/delegation-provider.service' -import { DelegationProviderResolverResolver } from './delegationProvider/delegation-provider.resolver' +import { DelegationProviderResolver } from './delegationProvider/delegation-provider.resolver' +import { DelegationAdminResolver } from './delegationAdmin/delegation-admin.resolver' +import { DelegationAdminService } from './delegationAdmin/delegation-admin.service' +import { AuthDelegationApiClientModule } from '@island.is/clients/auth/delegation-api' @Module({ - imports: [AuthAdminApiClientModule], + imports: [AuthAdminApiClientModule, AuthDelegationApiClientModule], providers: [ TenantResolver, TenantEnvironmentResolver, @@ -29,7 +32,9 @@ import { DelegationProviderResolverResolver } from './delegationProvider/delegat ScopeResolver, ScopeService, DelegationProviderService, - DelegationProviderResolverResolver, + DelegationProviderResolver, + DelegationAdminResolver, + DelegationAdminService, ], }) export class AuthAdminModule {} diff --git a/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.resolver.ts b/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.resolver.ts new file mode 100644 index 000000000000..56c8aebe59a1 --- /dev/null +++ b/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.resolver.ts @@ -0,0 +1,115 @@ +import { + Args, + Mutation, + Parent, + Query, + ResolveField, + Resolver, +} from '@nestjs/graphql' +import { NotFoundException, UseGuards } from '@nestjs/common' + +import { DelegationAdminService } from './delegation-admin.service' +import { DelegationAdminCustomModel } from './models/delegation.model' + +import { + type User, + CurrentUser, + IdsUserGuard, +} from '@island.is/auth-nest-tools' +import { + Identity, + type IdentityDataLoader, + IdentityLoader, +} from '@island.is/api/domains/identity' +import { Loader } from '@island.is/nest/dataloader' +import { DelegationDTO } from '@island.is/auth-api-lib' +import { + type DomainDataLoader, + CustomDelegation, + Domain, + DomainLoader, + ISLAND_DOMAIN, +} from '@island.is/api/domains/auth' + +@UseGuards(IdsUserGuard) +@Resolver(CustomDelegation) +export class DelegationAdminResolver { + constructor( + private readonly delegationAdminService: DelegationAdminService, + ) {} + + @Query(() => DelegationAdminCustomModel, { name: 'authAdminDelegationAdmin' }) + async getDelegationSystem( + @Args('nationalId') nationalId: string, + @CurrentUser() user: User, + @Loader(IdentityLoader) identityLoader: IdentityDataLoader, + ) { + const delegations = await this.delegationAdminService.getDelegationAdmin( + user, + nationalId, + ) + const identityCard = await identityLoader.load(nationalId) + + return { + nationalId: nationalId, + name: identityCard.name, + incoming: delegations.incoming, + outgoing: delegations.outgoing, + } + } + + @Mutation(() => Boolean, { name: 'authDeleteAdminDelegation' }) + async deleteDelegationSystem( + @Args('id') id: string, + @CurrentUser() user: User, + ) { + return this.delegationAdminService.deleteDelegationAdmin(user, id) + } + + @ResolveField('from', () => Identity) + resolveFromIdentity( + @Loader(IdentityLoader) identityLoader: IdentityDataLoader, + @Parent() customDelegation: DelegationDTO, + ) { + return identityLoader.load(customDelegation.fromNationalId) + } + + @ResolveField('to', () => Identity) + resolveToIdentity( + @Loader(IdentityLoader) identityLoader: IdentityDataLoader, + @Parent() customDelegation: DelegationDTO, + ) { + return identityLoader.load(customDelegation.toNationalId) + } + + @ResolveField('validTo', () => Date, { nullable: true }) + resolveValidTo(@Parent() delegation: DelegationDTO): Date | undefined { + if (!delegation.validTo) { + return undefined + } + + return delegation.scopes?.every( + (scope) => scope.validTo?.toString() === delegation.validTo?.toString(), + ) + ? delegation.validTo + : undefined + } + + @ResolveField('domain', () => Domain) + async resolveDomain( + @Loader(DomainLoader) domainLoader: DomainDataLoader, + @Parent() delegation: DelegationDTO, + ): Promise { + const domainName = delegation.domainName ?? ISLAND_DOMAIN + const domain = await domainLoader.load({ + lang: 'is', + domain: domainName, + }) + + if (!domain) { + throw new NotFoundException(`Could not find domain: ${domainName}`) + } + + return domain + } +} diff --git a/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.service.ts b/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.service.ts new file mode 100644 index 000000000000..7f884ed7325f --- /dev/null +++ b/libs/api/domains/auth-admin/src/lib/delegationAdmin/delegation-admin.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common' + +import { + DelegationAdminApi, + DelegationAdminCustomDto, +} from '@island.is/clients/auth/admin-api' +import { Auth, AuthMiddleware, User } from '@island.is/auth-nest-tools' + +@Injectable() +export class DelegationAdminService { + constructor(private readonly delegationAdminApi: DelegationAdminApi) {} + + delegationsWithAuth(auth: Auth) { + return this.delegationAdminApi.withMiddleware(new AuthMiddleware(auth)) + } + + async getDelegationAdmin( + user: User, + nationalId: string, + ): Promise { + return await this.delegationsWithAuth( + user, + ).delegationAdminControllerGetDelegationAdmin({ + xQueryNationalId: nationalId, + }) + } + + async deleteDelegationAdmin( + user: User, + delegationId: string, + ): Promise { + try { + await this.delegationsWithAuth(user).delegationAdminControllerDelete({ + delegationId, + }) + + return true + } catch { + return false + } + } +} diff --git a/libs/api/domains/auth-admin/src/lib/delegationAdmin/models/delegation.model.ts b/libs/api/domains/auth-admin/src/lib/delegationAdmin/models/delegation.model.ts new file mode 100644 index 000000000000..a97a1367d554 --- /dev/null +++ b/libs/api/domains/auth-admin/src/lib/delegationAdmin/models/delegation.model.ts @@ -0,0 +1,17 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { CustomDelegation } from '@island.is/api/domains/auth' + +@ObjectType('DelegationAdminCustomModel') +export class DelegationAdminCustomModel { + @Field(() => String, { nullable: false }) + nationalId!: string + + @Field(() => String, { nullable: false }) + name!: string + + @Field(() => [CustomDelegation]) + incoming!: CustomDelegation[] + + @Field(() => [CustomDelegation]) + outgoing!: CustomDelegation[] +} diff --git a/libs/api/domains/auth-admin/src/lib/delegationProvider/delegation-provider.resolver.ts b/libs/api/domains/auth-admin/src/lib/delegationProvider/delegation-provider.resolver.ts index b70c5bbbacb0..6a78581b03b2 100644 --- a/libs/api/domains/auth-admin/src/lib/delegationProvider/delegation-provider.resolver.ts +++ b/libs/api/domains/auth-admin/src/lib/delegationProvider/delegation-provider.resolver.ts @@ -12,7 +12,7 @@ import { DelegationProviderService } from './delegation-provider.service' @UseGuards(IdsUserGuard) @Resolver(() => DelegationProvider) -export class DelegationProviderResolverResolver { +export class DelegationProviderResolver { constructor(private delegationProvider: DelegationProviderService) {} @Query(() => DelegationProviderPayload, { diff --git a/libs/api/domains/auth/src/index.ts b/libs/api/domains/auth/src/index.ts index 781ba4ad68a5..c6bd4b5d90f1 100644 --- a/libs/api/domains/auth/src/index.ts +++ b/libs/api/domains/auth/src/index.ts @@ -3,4 +3,12 @@ export { AuthModule } from './lib/auth.module' export { ClientLoader } from './lib/loaders/client.loader' export type { ClientDataLoader } from './lib/loaders/client.loader' +export { DomainLoader } from './lib/loaders/domain.loader' +export type { DomainDataLoader } from './lib/loaders/domain.loader' + +export { Domain } from './lib/models/domain.model' export { Client } from './lib/models/client.model' +export { DelegationScope } from './lib/models/delegationScope.model' +export { CustomDelegation } from './lib/models/delegation.model' + +export { ISLAND_DOMAIN } from './lib/services/constants' diff --git a/libs/auth-api-lib/src/index.ts b/libs/auth-api-lib/src/index.ts index de58b144a146..d97dc8d1e24f 100644 --- a/libs/auth-api-lib/src/index.ts +++ b/libs/auth-api-lib/src/index.ts @@ -40,6 +40,7 @@ export * from './lib/delegations/types/delegationType' export * from './lib/delegations/types/delegationRecord' export * from './lib/delegations/types/delegationValidity' export * from './lib/delegations/dto/delegation-scope.dto' +export * from './lib/delegations/dto/delegation-admin-custom.dto' export * from './lib/delegations/dto/delegation.dto' export * from './lib/delegations/dto/delegation-index.dto' export * from './lib/delegations/dto/paginated-delegation-provider.dto' @@ -54,6 +55,7 @@ export * from './lib/delegations/models/delegation-type.model' export * from './lib/delegations/models/delegation-provider.model' export * from './lib/delegations/DelegationConfig' export * from './lib/delegations/utils/scopes' +export * from './lib/delegations/admin/delegation-admin-custom.service' export * from './lib/delegations/constants/names' // Resources module diff --git a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts new file mode 100644 index 000000000000..fd07334a8a1f --- /dev/null +++ b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts @@ -0,0 +1,138 @@ +import { Injectable } from '@nestjs/common' +import { InjectModel } from '@nestjs/sequelize' + +import { Delegation } from '../models/delegation.model' +import { DelegationAdminCustomDto } from '../dto/delegation-admin-custom.dto' +import { DelegationScope } from '../models/delegation-scope.model' +import { ApiScope } from '../../resources/models/api-scope.model' +import { ApiScopeDelegationType } from '../../resources/models/api-scope-delegation-type.model' +import { AuthDelegationType } from '@island.is/shared/types' +import { User } from '@island.is/auth-nest-tools' +import { DelegationResourcesService } from '../../resources/delegation-resources.service' +import { DelegationsIndexService } from '../delegations-index.service' +import { DelegationScopeService } from '../delegation-scope.service' +import { NoContentException } from '@island.is/nest/problem' +import { Sequelize } from 'sequelize-typescript' + +@Injectable() +export class DelegationAdminCustomService { + constructor( + @InjectModel(Delegation) + private delegationModel: typeof Delegation, + private delegationResourceService: DelegationResourcesService, + private delegationIndexService: DelegationsIndexService, + private delegationScopeService: DelegationScopeService, + private sequelize: Sequelize, + ) {} + + async getAllDelegationsByNationalId( + nationalId: string, + ): Promise { + const incomingDelegations = await this.delegationModel.findAll({ + where: { + toNationalId: nationalId, + }, + include: [ + { + model: DelegationScope, + required: true, + include: [ + { + model: ApiScope, + as: 'apiScope', + required: true, + where: { + enabled: true, + }, + include: [ + { + model: ApiScopeDelegationType, + required: true, + where: { + delegationType: AuthDelegationType.Custom, + }, + }, + ], + }, + ], + }, + ], + }) + + const outgoingDelegations = await this.delegationModel.findAll({ + where: { + fromNationalId: nationalId, + }, + include: [ + { + model: DelegationScope, + required: true, + include: [ + { + model: ApiScope, + required: true, + as: 'apiScope', + where: { + enabled: true, + }, + include: [ + { + model: ApiScopeDelegationType, + required: true, + where: { + delegationType: AuthDelegationType.Custom, + }, + }, + ], + }, + ], + }, + ], + }) + + return { + incoming: incomingDelegations.map((delegation) => delegation.toDTO()), + outgoing: outgoingDelegations.map((delegation) => delegation.toDTO()), + } + } + + async deleteDelegation(user: User, delegationId: string): Promise { + // TODO: Check if delegation has a ReferenceId and throw error if it does not. + const delegation = await this.delegationModel.findByPk(delegationId) + + if (!delegation) { + throw new NoContentException() + } + + const userScopes = await this.delegationResourceService.findScopes( + user, + delegation.domainName, + ) + + await this.sequelize.transaction(async (transaction) => { + await this.delegationScopeService.delete( + delegationId, + userScopes.map((scope) => scope.name), + transaction, + ) + + // If no scopes are left delete the delegation. + const remainingScopes = await this.delegationScopeService.findAll( + delegationId, + ) + if (remainingScopes.length === 0) { + await this.delegationModel.destroy({ + transaction, + where: { + id: delegationId, + }, + }) + } + + // Index custom delegations for the toNationalId + void this.delegationIndexService.indexCustomDelegations( + delegation.toNationalId, + ) + }) + } +} diff --git a/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts b/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts index 7444e3ce3883..bda3d0856fcc 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts @@ -3,7 +3,7 @@ import { ConfigType } from '@nestjs/config' import { InjectModel } from '@nestjs/sequelize' import addDays from 'date-fns/addDays' import startOfDay from 'date-fns/startOfDay' -import { Op } from 'sequelize' +import { Op, Transaction } from 'sequelize' import { uuid } from 'uuidv4' import { AuthDelegationProvider } from '@island.is/shared/types' @@ -16,7 +16,6 @@ import { IdentityResource } from '../resources/models/identity-resource.model' import { DelegationProviderService } from './delegation-provider.service' import { DelegationConfig } from './DelegationConfig' import { UpdateDelegationScopeDTO } from './dto/delegation-scope.dto' -import { DelegationProviderModel } from './models/delegation-provider.model' import { DelegationScope } from './models/delegation-scope.model' import { DelegationTypeModel } from './models/delegation-type.model' import { Delegation } from './models/delegation.model' @@ -81,15 +80,18 @@ export class DelegationScopeService { async delete( delegationId: string, scopeNames?: string[] | null, + transaction?: Transaction, ): Promise { if (scopeNames) { return this.delegationScopeModel.destroy({ where: { delegationId, scopeName: scopeNames }, + transaction, }) } return this.delegationScopeModel.destroy({ where: { delegationId }, + transaction, }) } @@ -101,6 +103,7 @@ export class DelegationScopeService { async findAll(delegationId: string): Promise { return this.delegationScopeModel.findAll({ + useMaster: true, where: { delegationId }, include: [ { diff --git a/libs/auth-api-lib/src/lib/delegations/delegations.module.ts b/libs/auth-api-lib/src/lib/delegations/delegations.module.ts index 4406495d5b3e..6353fdd7d211 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations.module.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations.module.ts @@ -33,6 +33,7 @@ import { DelegationTypeModel } from './models/delegation-type.model' import { DelegationProviderModel } from './models/delegation-provider.model' import { DelegationProviderService } from './delegation-provider.service' import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation-type.model' +import { DelegationAdminCustomService } from './admin/delegation-admin-custom.service' @Module({ imports: [ @@ -71,6 +72,7 @@ import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation DelegationsIncomingRepresentativeService, DelegationsIndexService, DelegationProviderService, + DelegationAdminCustomService, ], exports: [ DelegationsService, @@ -79,6 +81,7 @@ import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation DelegationScopeService, DelegationsIndexService, DelegationProviderService, + DelegationAdminCustomService, ], }) export class DelegationsModule {} diff --git a/libs/auth-api-lib/src/lib/delegations/dto/delegation-admin-custom.dto.ts b/libs/auth-api-lib/src/lib/delegations/dto/delegation-admin-custom.dto.ts new file mode 100644 index 000000000000..458a415ab346 --- /dev/null +++ b/libs/auth-api-lib/src/lib/delegations/dto/delegation-admin-custom.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger' + +import { DelegationDTO } from './delegation.dto' + +export class DelegationAdminCustomDto { + @ApiProperty({ type: [DelegationDTO], nullable: false, default: [] }) + incoming!: DelegationDTO[] + + @ApiProperty({ type: [DelegationDTO], nullable: false, default: [] }) + outgoing!: DelegationDTO[] +} diff --git a/libs/auth/scopes/src/lib/admin-portal.scope.ts b/libs/auth/scopes/src/lib/admin-portal.scope.ts index c869276bacf5..7994bf36cac2 100644 --- a/libs/auth/scopes/src/lib/admin-portal.scope.ts +++ b/libs/auth/scopes/src/lib/admin-portal.scope.ts @@ -16,4 +16,6 @@ export enum AdminPortalScope { signatureCollectionManage = '@admin.island.is/signature-collection:manage', formSystem = '@admin.island.is/form-system', formSystemSuperUser = '@admin.island.is/form-system:admin', + delegationSystem = '@admin.island.is/delegation-system', + delegationSystemAdmin = '@admin.island.is/delegation-system:admin', } diff --git a/libs/auth/scopes/src/lib/auth.scope.ts b/libs/auth/scopes/src/lib/auth.scope.ts index 40d367c37cb1..3c97ef0d3df0 100644 --- a/libs/auth/scopes/src/lib/auth.scope.ts +++ b/libs/auth/scopes/src/lib/auth.scope.ts @@ -15,3 +15,8 @@ export const delegationScopes = [ AuthScope.delegations, AdminPortalScope.delegations, ] + +export const DelegationAdminScopes = { + read: '@admin.island.is/delegation-system', + admin: '@admin.island.is/delegation-system:admin', +} diff --git a/libs/clients/auth/admin-api/src/index.ts b/libs/clients/auth/admin-api/src/index.ts index 925109642dc0..eebdda7e8c9e 100644 --- a/libs/clients/auth/admin-api/src/index.ts +++ b/libs/clients/auth/admin-api/src/index.ts @@ -13,6 +13,8 @@ export { TenantDto, PaginatedDelegationProviderDto, DelegationProviderDto, + DelegationAdminApi, + DelegationAdminCustomDto, } from '../gen/fetch' export * from './lib/apis' export * from './lib/auth-admin-api-client.config' diff --git a/libs/clients/auth/admin-api/src/lib/apis.ts b/libs/clients/auth/admin-api/src/lib/apis.ts index 1ba0a90c75fd..fc289a39e19b 100644 --- a/libs/clients/auth/admin-api/src/lib/apis.ts +++ b/libs/clients/auth/admin-api/src/lib/apis.ts @@ -2,8 +2,9 @@ import { ConfigType } from '@island.is/nest/config' import { createEnhancedFetch } from '@island.is/clients/middlewares' import { Environment } from '@island.is/shared/types' -import { AdminApi, Configuration } from '../../gen/fetch' +import { AdminApi, Configuration, DelegationAdminApi } from '../../gen/fetch' import { AuthAdminApiClientConfig } from './auth-admin-api-client.config' +import { Provider } from '@nestjs/common' interface AdminApiEnv { env: Environment @@ -23,22 +24,38 @@ export const AdminProdApi: AdminApiEnv = { key: 'AdminProdApi', } -export const exportedApis = [AdminDevApi, AdminStagingApi, AdminProdApi].map( - (adminApi) => { - return { - provide: adminApi.key, - useFactory: (config: ConfigType) => - config.basePaths[adminApi.env] - ? new AdminApi( - new Configuration({ - fetchApi: createEnhancedFetch({ - name: `clients-auth-admin-${adminApi.env}-api`, - }), - basePath: config.basePaths[adminApi.env], +export const exportedApis: Provider[] = [ + AdminDevApi, + AdminStagingApi, + AdminProdApi, +].map((adminApi) => { + return { + provide: adminApi.key, + useFactory: (config: ConfigType) => + config.basePaths[adminApi.env] + ? new AdminApi( + new Configuration({ + fetchApi: createEnhancedFetch({ + name: `clients-auth-admin-${adminApi.env}-api`, }), - ) - : undefined, - inject: [AuthAdminApiClientConfig.KEY], - } - }, -) + basePath: config.basePaths[adminApi.env], + }), + ) + : undefined, + inject: [AuthAdminApiClientConfig.KEY], + } +}) + +exportedApis.push({ + provide: DelegationAdminApi, + inject: [AuthAdminApiClientConfig.KEY], + useFactory: (config: ConfigType) => + new DelegationAdminApi( + new Configuration({ + fetchApi: createEnhancedFetch({ + name: 'clients-auth-delegation-admin-api', + }), + basePath: config.basePath, + }), + ), +}) diff --git a/libs/clients/auth/admin-api/src/lib/auth-admin-api-client.config.ts b/libs/clients/auth/admin-api/src/lib/auth-admin-api-client.config.ts index b64cb002df6b..c1c105659ae5 100644 --- a/libs/clients/auth/admin-api/src/lib/auth-admin-api-client.config.ts +++ b/libs/clients/auth/admin-api/src/lib/auth-admin-api-client.config.ts @@ -3,11 +3,14 @@ import * as z from 'zod' import { defineConfig } from '@island.is/nest/config' const schema = z.object({ + // Paths to the different environments basePaths: z.object({ development: z.string().optional(), staging: z.string().optional(), production: z.string().optional(), }), + // Current deployed environment + basePath: z.string(), }) export const AuthAdminApiClientConfig = defineConfig({ @@ -18,6 +21,10 @@ export const AuthAdminApiClientConfig = defineConfig({ basePaths: env.requiredJSON('AUTH_ADMIN_API_PATHS', { development: 'http://localhost:6333/backend', }), + basePath: env.required( + 'AUTH_ADMIN_API_PATH', + 'http://localhost:6333/backend', + ), } }, }) diff --git a/libs/clients/auth/delegation-api/src/index.ts b/libs/clients/auth/delegation-api/src/index.ts index 1e28d9781e3a..b4a2227fc436 100644 --- a/libs/clients/auth/delegation-api/src/index.ts +++ b/libs/clients/auth/delegation-api/src/index.ts @@ -1,3 +1,5 @@ export * from './lib/auth-delegation-api-client.module' export * from './lib/auth-delegation-api-client.config' export * from '../gen/fetch' + +export * from '../gen/fetch/models/DelegationDTO' diff --git a/libs/portals/admin/delegation-admin/.babelrc b/libs/portals/admin/delegation-admin/.babelrc new file mode 100644 index 000000000000..1ea870ead410 --- /dev/null +++ b/libs/portals/admin/delegation-admin/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/portals/admin/delegation-admin/.eslintrc.json b/libs/portals/admin/delegation-admin/.eslintrc.json new file mode 100644 index 000000000000..772a43d27834 --- /dev/null +++ b/libs/portals/admin/delegation-admin/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nx/react", "../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/portals/admin/delegation-admin/README.md b/libs/portals/admin/delegation-admin/README.md new file mode 100644 index 000000000000..b50573166245 --- /dev/null +++ b/libs/portals/admin/delegation-admin/README.md @@ -0,0 +1,7 @@ +# delegation-admin + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test delegation-admin` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/portals/admin/delegation-admin/codegen.yml b/libs/portals/admin/delegation-admin/codegen.yml new file mode 100644 index 000000000000..0773efe42479 --- /dev/null +++ b/libs/portals/admin/delegation-admin/codegen.yml @@ -0,0 +1,18 @@ +schema: + - apps/api/src/api.graphql +documents: + - libs/portals/admin/delegation-admin/src/**/*.graphql +generates: + libs/portals/admin/ids-admin/src/: + preset: 'near-operation-file' + presetConfig: + baseTypesPath: '~@island.is/api/schema' + plugins: + - typescript-operations + - typescript-react-apollo + config: + scalars: + DateTime: string +hooks: + afterAllFileWrite: + - prettier --write diff --git a/libs/portals/admin/delegation-admin/jest.config.ts b/libs/portals/admin/delegation-admin/jest.config.ts new file mode 100644 index 000000000000..74e73206277d --- /dev/null +++ b/libs/portals/admin/delegation-admin/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'delegation-admin', + preset: '../../../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../../../coverage/libs/portals/admin/delegation-admin', +} diff --git a/libs/portals/admin/delegation-admin/project.json b/libs/portals/admin/delegation-admin/project.json new file mode 100644 index 000000000000..5088d4044799 --- /dev/null +++ b/libs/portals/admin/delegation-admin/project.json @@ -0,0 +1,26 @@ +{ + "name": "delegation-admin", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/portals/admin/delegation-admin/src", + "projectType": "library", + "tags": ["scope:portals-admin", "lib:portals-admin"], + "targets": { + "lint": { + "executor": "@nx/eslint:lint" + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/portals/admin/delegation-admin/jest.config.ts" + } + }, + "codegen/frontend-client": { + "executor": "nx:run-commands", + "options": { + "output": "libs/portals/admin/delegation-admin/src/**/*.generated.ts", + "command": "graphql-codegen --config libs/portals/admin/delegation-admin/codegen.yml" + } + } + } +} diff --git a/libs/portals/admin/delegation-admin/src/components/DelegationList.tsx b/libs/portals/admin/delegation-admin/src/components/DelegationList.tsx new file mode 100644 index 000000000000..b248b3e20505 --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/components/DelegationList.tsx @@ -0,0 +1,30 @@ +import { AuthCustomDelegation } from '@island.is/api/schema' +import { Box, Stack } from '@island.is/island-ui/core' +import { AccessCard } from '@island.is/portals/shared-modules/delegations' + +interface DelegationProps { + direction: 'incoming' | 'outgoing' + delegationsList: AuthCustomDelegation[] +} + +const DelegationList = ({ delegationsList, direction }: DelegationProps) => { + return ( + + + {delegationsList.map((delegation) => ( + { + console.warn('Delete delegation') + }} + /> + ))} + + + ) +} + +export default DelegationList diff --git a/libs/portals/admin/delegation-admin/src/index.ts b/libs/portals/admin/delegation-admin/src/index.ts new file mode 100644 index 000000000000..a42f1391dc61 --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/index.ts @@ -0,0 +1,3 @@ +export { delegationAdminModule } from './module' +export { m } from './lib/messages' +export { delegationAdminNav } from './lib/navigation' diff --git a/libs/portals/admin/delegation-admin/src/lib/messages.ts b/libs/portals/admin/delegation-admin/src/lib/messages.ts new file mode 100644 index 000000000000..16a131c73447 --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/lib/messages.ts @@ -0,0 +1,44 @@ +import { defineMessages } from 'react-intl' + +export const m = defineMessages({ + delegationAdmin: { + id: 'admin.delegationSystem:delegationAdmin', + defaultMessage: 'Umboð', + }, + delegationAdminDescription: { + id: 'admin.delegationAdmin:delegationAdminDescription', + defaultMessage: 'Flettu upp notanda til að birta upplýsingar um umboð.', + }, + search: { + id: 'admin.delegationAdmin:delegationAdminSearch', + defaultMessage: 'Leita eftir kennitölu', + }, + back: { + id: 'admin.delegationAdmin:delegationAdminBack', + defaultMessage: 'Til baka', + }, + createNewDelegation: { + id: 'admin.delegationAdmin:delegationAdminCreateNewDelegation', + defaultMessage: 'Stofna nýtt umboð', + }, + delegationFrom: { + id: 'admin.delegationAdmin:delegationAdminDelegationFrom', + defaultMessage: 'Umboð frá notanda', + }, + delegationTo: { + id: 'admin.delegationAdmin:delegationAdminDelegationT', + defaultMessage: 'Umboð til notanda', + }, + delegationFromNotFound: { + id: 'admin.delegationAdmin:delegationAdminDelegationFromNotFound', + defaultMessage: 'Engin umboð frá notanda fundust', + }, + delegationToNotFound: { + id: 'admin.delegationAdmin:delegationAdminDelegationToNotFound', + defaultMessage: 'Engin umboð til notanda fundust', + }, + nationalIdNotFound: { + id: 'admin.delegationAdmin:nationalIdNotFound', + defaultMessage: 'Kennitala fannst ekki', + }, +}) diff --git a/libs/portals/admin/delegation-admin/src/lib/navigation.ts b/libs/portals/admin/delegation-admin/src/lib/navigation.ts new file mode 100644 index 000000000000..4bdd2991259b --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/lib/navigation.ts @@ -0,0 +1,21 @@ +import { PortalNavigationItem } from '@island.is/portals/core' +import { m } from './messages' +import { DelegationAdminPaths } from './paths' + +export const delegationAdminNav: PortalNavigationItem = { + name: m.delegationAdmin, + icon: { + icon: 'fileTrayFull', + }, + path: DelegationAdminPaths.Root, + description: m.delegationAdminDescription, + activeIfExact: true, + children: [ + { + name: m.delegationAdmin, + path: DelegationAdminPaths.DelegationAdmin, + activeIfExact: true, + navHide: true, + }, + ], +} diff --git a/libs/portals/admin/delegation-admin/src/lib/paths.ts b/libs/portals/admin/delegation-admin/src/lib/paths.ts new file mode 100644 index 000000000000..7c6202329c15 --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/lib/paths.ts @@ -0,0 +1,4 @@ +export enum DelegationAdminPaths { + Root = '/delegation-admin', + DelegationAdmin = '/delegation-admin/:nationalId', +} diff --git a/libs/portals/admin/delegation-admin/src/module.tsx b/libs/portals/admin/delegation-admin/src/module.tsx new file mode 100644 index 000000000000..bf0bb9fad84e --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/module.tsx @@ -0,0 +1,42 @@ +import { PortalModule } from '@island.is/portals/core' +import { m } from './lib/messages' +import { DelegationAdminPaths } from './lib/paths' +import { lazy } from 'react' +import { AdminPortalScope } from '@island.is/auth/scopes' +import { delegationAdminLoader } from './screens/DelegationAdminDetails/DelegationAdmin.loader' +import { FindDelegationForNationalId } from './screens/Root.action' + +const DelegationAdminScreen = lazy(() => + import('./screens/DelegationAdminDetails/DelegationAdmin'), +) +const RootScreen = lazy(() => import('./screens/Root')) + +const allowedScopes: string[] = [ + AdminPortalScope.delegationSystem, + AdminPortalScope.delegationSystemAdmin, +] + +export const delegationAdminModule: PortalModule = { + name: m.delegationAdmin, + enabled({ userInfo }) { + return userInfo.scopes.some((scope) => allowedScopes.includes(scope)) + }, + routes(props) { + return [ + { + name: m.delegationAdmin, + path: DelegationAdminPaths.Root, + index: true, + action: FindDelegationForNationalId(props), + element: , + children: [], + }, + { + name: m.delegationAdmin, + path: DelegationAdminPaths.DelegationAdmin, + element: , + loader: delegationAdminLoader(props), + }, + ] + }, +} diff --git a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.graphql b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.graphql new file mode 100644 index 000000000000..ba61ef19ccae --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.graphql @@ -0,0 +1,58 @@ +query getCustomDelegationsAdmin($nationalId: String!) { + authAdminDelegationAdmin(nationalId: $nationalId) { + name + nationalId + incoming { + id + validTo + domain { + name + organisationLogoKey + organisationLogoUrl + displayName + nationalId + } + type + from { + name + nationalId + } + to { + name + nationalId + } + scopes { + name + displayName + id + validTo + } + } + outgoing { + id + validTo + domain { + name + organisationLogoKey + organisationLogoUrl + displayName + nationalId + } + type + from { + name + nationalId + } + to { + name + nationalId + } + scopes { + name + displayName + id + validTo + } + } + } +} diff --git a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.loader.ts b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.loader.ts new file mode 100644 index 000000000000..9e0d77d0ecc1 --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.loader.ts @@ -0,0 +1,54 @@ +import { redirect } from 'react-router-dom' + +import { WrappedLoaderFn } from '@island.is/portals/core' + +import { + GetCustomDelegationsAdminDocument, + GetCustomDelegationsAdminQuery, + GetCustomDelegationsAdminQueryVariables, +} from './DelegationAdmin.generated' +import { DelegationAdminPaths } from '../../lib/paths' +import { unmaskString } from '@island.is/shared/utils' + +export type DelegationAdminResult = NonNullable< + GetCustomDelegationsAdminQuery['authAdminDelegationAdmin'] +> + +export type Delegations = NonNullable< + GetCustomDelegationsAdminQuery['authAdminDelegationAdmin']['incoming'] +> + +export const delegationAdminLoader: WrappedLoaderFn = ({ + client, + userInfo, +}) => { + return async ({ params }): Promise => { + const nationalId = params['nationalId'] + + if (!nationalId) throw new Error('NationalId not found') + + const unMaskNationalId = + (await unmaskString(nationalId, userInfo.profile.nationalId)) ?? '' + + const res = await client.query< + GetCustomDelegationsAdminQuery, + GetCustomDelegationsAdminQueryVariables + >({ + query: GetCustomDelegationsAdminDocument, + fetchPolicy: 'network-only', + variables: { + nationalId: unMaskNationalId, + }, + }) + + if (res.error) { + throw res.error + } + + if (!res.data?.authAdminDelegationAdmin) { + return redirect(DelegationAdminPaths.Root) + } + + return res.data.authAdminDelegationAdmin + } +} diff --git a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx new file mode 100644 index 000000000000..2bfcabf4fece --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx @@ -0,0 +1,72 @@ +import { Box, Stack, Tabs } from '@island.is/island-ui/core' +import { BackButton } from '@island.is/portals/admin/core' +import { useLocale } from '@island.is/localization' +import { useLoaderData, useNavigate } from 'react-router-dom' +import { DelegationAdminResult } from './DelegationAdmin.loader' +import { DelegationAdminPaths } from '../../lib/paths' +import { IntroHeader } from '@island.is/portals/core' +import { m } from '../../lib/messages' +import React from 'react' +import DelegationList from '../../components/DelegationList' +import { AuthCustomDelegation } from '@island.is/api/schema' +import { DelegationsEmptyState } from '@island.is/portals/shared-modules/delegations' + +const DelegationAdminScreen = () => { + const { formatMessage } = useLocale() + const navigate = useNavigate() + const delegationAdmin = useLoaderData() as DelegationAdminResult + + return ( + + navigate(DelegationAdminPaths.Root)} /> + + + + 0 ? ( + + ) : ( + + ), + }, + { + label: formatMessage(m.delegationTo), + content: + delegationAdmin.outgoing.length > 0 ? ( + + ) : ( + + ), + }, + ]} + /> + + ) +} + +export default DelegationAdminScreen diff --git a/libs/portals/admin/delegation-admin/src/screens/Root.action.ts b/libs/portals/admin/delegation-admin/src/screens/Root.action.ts new file mode 100644 index 000000000000..634c1111fe3d --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/screens/Root.action.ts @@ -0,0 +1,70 @@ +import { + RawRouterActionResponse, + WrappedActionFn, +} from '@island.is/portals/core' +import { z } from 'zod' +import { + replaceParams, + validateFormData, + ValidateFormDataResult, +} from '@island.is/react-spa/shared' +import * as kennitala from 'kennitala' +import { redirect } from 'react-router-dom' +import { DelegationAdminPaths } from '../lib/paths' +import { maskString } from '@island.is/shared/utils' +import { GetCustomDelegationsAdminQuery } from './DelegationAdminDetails/DelegationAdmin.generated' + +export enum ErrorType { + InvalidNationalId = 'INVALID_NATIONAL_ID', +} + +export type GetDelegationForNationalIdResult = RawRouterActionResponse< + GetCustomDelegationsAdminQuery['authAdminDelegationAdmin'], + ValidateFormDataResult['errors'] +> + +const schema = z.object({ + nationalId: z + .string() + .length(10) + .superRefine((value, ctx) => { + if (!kennitala.isValid(value)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: ErrorType.InvalidNationalId, + }) + return false + } + return true + }), +}) + +export const FindDelegationForNationalId: WrappedActionFn = + ({ userInfo }) => + async ({ request }) => { + const formData = await request.formData() + + const { data, errors } = await validateFormData({ + formData, + schema, + }) + + if (errors || !data) { + return { + errors, + data: null, + } + } + + const nationalId = formData.get('nationalId') as string + + return redirect( + replaceParams({ + href: DelegationAdminPaths.DelegationAdmin, + params: { + nationalId: + (await maskString(nationalId, userInfo.profile.nationalId)) ?? '', + }, + }), + ) + } diff --git a/libs/portals/admin/delegation-admin/src/screens/Root.tsx b/libs/portals/admin/delegation-admin/src/screens/Root.tsx new file mode 100644 index 000000000000..474cea162bb3 --- /dev/null +++ b/libs/portals/admin/delegation-admin/src/screens/Root.tsx @@ -0,0 +1,72 @@ +import { useLocale } from '@island.is/localization' +import { IntroHeader } from '@island.is/portals/core' +import { m } from '../lib/messages' +import { Form, Outlet, useActionData } from 'react-router-dom' +import { AsyncSearchInput, Box } from '@island.is/island-ui/core' +import React, { useEffect, useState } from 'react' +import { useSubmitting } from '@island.is/react-spa/shared' +import { GetDelegationForNationalIdResult } from './Root.action' + +const Root = () => { + const [focused, setFocused] = useState(false) + const [searchInput, setSearchInput] = useState('') + const actionData = useActionData() as GetDelegationForNationalIdResult + const { formatMessage } = useLocale() + const { isSubmitting, isLoading } = useSubmitting() + const [error, setError] = useState({ hasError: false, message: '' }) + + useEffect(() => { + if (actionData?.errors) { + setError({ + hasError: true, + message: formatMessage(m.nationalIdNotFound), + }) + } else { + setError({ + hasError: false, + message: '', + }) + } + }, [actionData]) + + const onFocus = () => setFocused(true) + const onBlur = () => setFocused(false) + return ( + <> + + + +
+ + setSearchInput(event.target.value), + onBlur, + onFocus, + placeholder: formatMessage(formatMessage(m.search)), + colored: true, + }} + buttonProps={{ + type: 'submit', + disabled: searchInput.length === 0, + }} + hasError={error.hasError} + errorMessage={error.hasError ? error.message : undefined} + /> + +
+ + + + ) +} + +export default Root diff --git a/libs/portals/admin/delegation-admin/tsconfig.json b/libs/portals/admin/delegation-admin/tsconfig.json new file mode 100644 index 000000000000..c88d07daddd5 --- /dev/null +++ b/libs/portals/admin/delegation-admin/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../../tsconfig.base.json" +} diff --git a/libs/portals/admin/delegation-admin/tsconfig.lib.json b/libs/portals/admin/delegation-admin/tsconfig.lib.json new file mode 100644 index 000000000000..7d4982e6f258 --- /dev/null +++ b/libs/portals/admin/delegation-admin/tsconfig.lib.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "types": [ + "node", + + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/portals/admin/delegation-admin/tsconfig.spec.json b/libs/portals/admin/delegation-admin/tsconfig.spec.json new file mode 100644 index 000000000000..1033686367b0 --- /dev/null +++ b/libs/portals/admin/delegation-admin/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/libs/portals/shared-modules/delegations/src/components/access/AccessCard.tsx b/libs/portals/shared-modules/delegations/src/components/access/AccessCard.tsx index 0ab6ba887002..a7589e542044 100644 --- a/libs/portals/shared-modules/delegations/src/components/access/AccessCard.tsx +++ b/libs/portals/shared-modules/delegations/src/components/access/AccessCard.tsx @@ -48,9 +48,15 @@ const getTags = (delegation: AuthCustomDelegation) => interface AccessCardProps { delegation: AuthCustomDelegation + onDelete(delegation: AuthCustomDelegation): void + onView?(delegation: AuthCustomDelegation): void + variant?: 'outgoing' | 'incoming' + direction?: 'incoming' | 'outgoing' + + canModify?: boolean } export const AccessCard = ({ @@ -58,6 +64,8 @@ export const AccessCard = ({ onDelete, onView, variant = 'outgoing', + direction = 'outgoing', + canModify = true, }: AccessCardProps) => { const { formatMessage } = useLocale() const navigate = useNavigate() @@ -149,7 +157,7 @@ export const AccessCard = ({ } const showActions = - isOutgoing || delegation.type === AuthDelegationType.Custom + canModify && (isOutgoing || delegation.type === AuthDelegationType.Custom) const canDelete = isOutgoing || (!isOutgoing && delegation.type === AuthDelegationType.Custom) @@ -196,7 +204,7 @@ export const AccessCard = ({ {formatMessage(m.accessHolder)} - {isOutgoing + {direction === 'outgoing' ? (delegation as AuthCustomDelegationOutgoing)?.to?.name : (delegation as AuthCustomDelegationIncoming)?.from?.name} diff --git a/libs/portals/shared-modules/delegations/src/components/delegations/incoming/DelegationsIncoming.tsx b/libs/portals/shared-modules/delegations/src/components/delegations/incoming/DelegationsIncoming.tsx index c453725e552d..c444a298339b 100644 --- a/libs/portals/shared-modules/delegations/src/components/delegations/incoming/DelegationsIncoming.tsx +++ b/libs/portals/shared-modules/delegations/src/components/delegations/incoming/DelegationsIncoming.tsx @@ -76,6 +76,7 @@ export const DelegationsIncoming = () => { delegation as AuthCustomDelegationIncoming, ) }} + direction="incoming" variant="incoming" /> ), diff --git a/libs/portals/shared-modules/delegations/src/index.ts b/libs/portals/shared-modules/delegations/src/index.ts index 5ebec5fdf159..2f51425ae045 100644 --- a/libs/portals/shared-modules/delegations/src/index.ts +++ b/libs/portals/shared-modules/delegations/src/index.ts @@ -3,3 +3,7 @@ export { delegationsModule } from './module' // libs export * from './lib/paths' export * from './lib/navigation' + +// components +export * from './components/access/AccessCard' +export * from './components/delegations/DelegationsEmptyState' diff --git a/libs/services/auth/testing/src/fixtures/fixture-factory.ts b/libs/services/auth/testing/src/fixtures/fixture-factory.ts index 9e6f3acaf428..a8929a9a9f13 100644 --- a/libs/services/auth/testing/src/fixtures/fixture-factory.ts +++ b/libs/services/auth/testing/src/fixtures/fixture-factory.ts @@ -415,7 +415,7 @@ export class FixtureFactory { }): Promise { const scope = await this.get(DelegationScope).create({ id: faker.datatype.uuid(), - delegationId: delegationId, + delegationId, scopeName, validFrom: validFrom ?? startOfDay(new Date()), validTo: validTo ?? addYears(new Date(), 1), diff --git a/tsconfig.base.json b/tsconfig.base.json index 82b52bbfc428..a1aac4d2c148 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1111,7 +1111,8 @@ "@island.is/web/component*": ["apps/web/component*/real.ts"], "api/domains/financial-statement-cemetery": [ "libs/api/domains/financial-statement-cemetery/src/index.ts" - ] + ], + "delegation-admin": ["libs/portals/admin/delegation-admin/src/index.ts"] } } } From 75e2db838a63cf88cdae82bf29485a1c824f8f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:25:13 +0000 Subject: [PATCH 03/13] feat(web): Team List - Display email, phone and tags for team members (#15947) * Add explicit email and phone fields to team member content type * Display filter tags * Add missing key prop * Sort imports --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Slice/TeamListSlice/TeamListSlice.tsx | 48 +++++++++++++++-- apps/web/screens/queries/TeamList.ts | 12 +++++ .../src/lib/generated/contentfulTypes.d.ts | 8 ++- libs/cms/src/lib/models/teamMember.model.ts | 8 +++ .../src/lib/TeamList/TeamList.css.ts | 4 ++ .../contentful/src/lib/TeamList/TeamList.tsx | 52 +++++++++++++++++-- 6 files changed, 123 insertions(+), 9 deletions(-) diff --git a/apps/web/components/Organization/Slice/TeamListSlice/TeamListSlice.tsx b/apps/web/components/Organization/Slice/TeamListSlice/TeamListSlice.tsx index cfaaf02018c9..c6b06f0a76a6 100644 --- a/apps/web/components/Organization/Slice/TeamListSlice/TeamListSlice.tsx +++ b/apps/web/components/Organization/Slice/TeamListSlice/TeamListSlice.tsx @@ -1,8 +1,9 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import flatten from 'lodash/flatten' import { useLazyQuery } from '@apollo/client' import { TeamList, type TeamListProps } from '@island.is/island-ui/contentful' +import { sortAlpha } from '@island.is/shared/utils' import { GenericList } from '@island.is/web/components' import { type GenericTag, @@ -65,7 +66,44 @@ export const TeamMemberListWrapper = ({ ) const totalItems = itemsResponse?.total ?? 0 - const items = itemsResponse?.items ?? [] + + const items = useMemo( + () => + (itemsResponse?.items ?? []).map((item) => { + const tagGroups: { groupLabel: string; tagLabels: string[] }[] = [] + for (const tag of item.filterTags ?? []) { + if (!tag.genericTagGroup?.title || !tag.title) { + continue + } + const index = tagGroups.findIndex( + (group) => group.groupLabel === tag.genericTagGroup?.title, + ) + if (index >= 0) { + tagGroups[index].tagLabels.push(tag.title) + } else { + tagGroups.push({ + groupLabel: tag.genericTagGroup.title, + tagLabels: [tag.title], + }) + } + + // Add a colon to the end of group labels if it doesn't have one + for (const group of tagGroups) { + if (!group.groupLabel.endsWith(':')) { + group.groupLabel += ':' + } + } + } + + tagGroups.sort(sortAlpha('groupLabel')) + + return { + ...(item as TeamListProps['teamMembers'][number]), + tagGroups, + } + }), + [itemsResponse], + ) return ( ) diff --git a/apps/web/screens/queries/TeamList.ts b/apps/web/screens/queries/TeamList.ts index 99aebeaa1702..b76eb3fb0946 100644 --- a/apps/web/screens/queries/TeamList.ts +++ b/apps/web/screens/queries/TeamList.ts @@ -14,6 +14,18 @@ export const GET_TEAM_MEMBERS_QUERY = gql` items { name title + email + phone + filterTags { + id + title + slug + genericTagGroup { + id + title + slug + } + } image { ...ImageFields } diff --git a/libs/cms/src/lib/generated/contentfulTypes.d.ts b/libs/cms/src/lib/generated/contentfulTypes.d.ts index f0e1d38a31e0..a1597c4e903c 100644 --- a/libs/cms/src/lib/generated/contentfulTypes.d.ts +++ b/libs/cms/src/lib/generated/contentfulTypes.d.ts @@ -4408,7 +4408,13 @@ export interface ITeamMemberFields { /** Filter Tags */ filterTags?: IGenericTag[] | undefined - /** Intro */ + /** Accordion email */ + email?: string | undefined + + /** Accordion phone */ + phone?: string | undefined + + /** Accordion free text */ intro?: Document | undefined } diff --git a/libs/cms/src/lib/models/teamMember.model.ts b/libs/cms/src/lib/models/teamMember.model.ts index 97f1be997eb4..86440aa37677 100644 --- a/libs/cms/src/lib/models/teamMember.model.ts +++ b/libs/cms/src/lib/models/teamMember.model.ts @@ -13,6 +13,12 @@ export class TeamMember { @Field() title!: string + @Field({ nullable: true }) + email?: string + + @Field({ nullable: true }) + phone?: string + @CacheField(() => Image) image!: Image @@ -33,4 +39,6 @@ export const mapTeamMember = ({ fields, sys }: ITeamMember): TeamMember => ({ imageOnSelect: fields.imageOnSelect ? mapImage(fields.imageOnSelect) : null, filterTags: fields.filterTags ? fields.filterTags.map(mapGenericTag) : [], intro: fields.intro ? mapDocument(fields.intro, `${sys.id}:intro`) : [], + email: fields.email, + phone: fields.phone, }) diff --git a/libs/island-ui/contentful/src/lib/TeamList/TeamList.css.ts b/libs/island-ui/contentful/src/lib/TeamList/TeamList.css.ts index 3c59f6787b22..031af68c14c7 100644 --- a/libs/island-ui/contentful/src/lib/TeamList/TeamList.css.ts +++ b/libs/island-ui/contentful/src/lib/TeamList/TeamList.css.ts @@ -3,3 +3,7 @@ import { style } from '@vanilla-extract/css' export const teamMemberImage = style({ maxHeight: '165px', }) + +export const email = style({ + wordBreak: 'break-all', +}) diff --git a/libs/island-ui/contentful/src/lib/TeamList/TeamList.tsx b/libs/island-ui/contentful/src/lib/TeamList/TeamList.tsx index 157697ee2b9e..f8a79517eca5 100644 --- a/libs/island-ui/contentful/src/lib/TeamList/TeamList.tsx +++ b/libs/island-ui/contentful/src/lib/TeamList/TeamList.tsx @@ -6,6 +6,7 @@ import { BoxProps, GridColumn, GridRow, + Inline, ProfileCard, Stack, Text, @@ -17,15 +18,26 @@ import * as styles from './TeamList.css' const imagePostfix = '?w=400' export interface TeamListProps { + variant?: 'card' | 'accordion' + prefixes?: { + email?: string + phone?: string + } teamMembers: { title: string name: string image?: { url: string } imageOnSelect?: { url: string } | null - /** Field is only visible if variant is set to "accordion" */ + + /** Fields below are only visible if variant is set to "accordion" */ intro?: SliceType[] | null + email?: string + phone?: string + tagGroups?: { + groupLabel: string + tagLabels: string[] + }[] }[] - variant?: 'card' | 'accordion' } const loadedImageUrls = new Map() @@ -128,9 +140,10 @@ export const TeamMemberCardList = ({ const TeamMemberAccordionList = ({ teamMembers, -}: Pick) => { + prefixes, +}: Pick) => { return ( - + {teamMembers.map((member) => { const id = `${member.name}-${member.title}` return ( @@ -159,7 +172,36 @@ const TeamMemberAccordionList = ({ /> - {richText(member.intro ?? [])} + + {member.email && ( + + + {prefixes?.email ?? 'Netfang:'} + + + {member.email} + + + )} + {member.phone && ( + + + {prefixes?.phone ?? 'Sími:'} + + {member.phone} + + )} + {member.tagGroups?.map((tagGroup) => ( + + {tagGroup.groupLabel} + + {tagGroup.tagLabels.join(', ')} + + + ))} + + {richText(member.intro ?? [])} + From bc4f1b56f3b79ede22a76d446f19fe12a45f12da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9tur=20Neisti=20Erlingsson?= Date: Wed, 11 Sep 2024 13:56:33 +0000 Subject: [PATCH 04/13] fix: Scaling issues with web due to low CPU limits (#15955) * fix: Scaling issues with web due to low CPU limits * chore: charts update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/infra/web.ts | 4 ++-- charts/islandis/values.dev.yaml | 4 ++-- charts/islandis/values.prod.yaml | 4 ++-- charts/islandis/values.staging.yaml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/web/infra/web.ts b/apps/web/infra/web.ts index 9ce9bf73fd4f..fd73c10031e9 100644 --- a/apps/web/infra/web.ts +++ b/apps/web/infra/web.ts @@ -54,8 +54,8 @@ export const serviceSetup = (services: { .liveness('/liveness') .readiness({ path: '/readiness', initialDelaySeconds: 20 }) .resources({ - limits: { cpu: '400m', memory: '768Mi' }, - requests: { cpu: '200m', memory: '384Mi' }, + limits: { cpu: '1000m', memory: '768Mi' }, + requests: { cpu: '300m', memory: '384Mi' }, }) .replicaCount({ default: 2, diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index afa723c26f82..3a089235bd45 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -3326,10 +3326,10 @@ web: min: 2 resources: limits: - cpu: '400m' + cpu: '1000m' memory: '768Mi' requests: - cpu: '200m' + cpu: '300m' memory: '384Mi' secrets: CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 9421ae497461..9d12ceb47191 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -3207,10 +3207,10 @@ web: min: 2 resources: limits: - cpu: '400m' + cpu: '1000m' memory: '768Mi' requests: - cpu: '200m' + cpu: '300m' memory: '384Mi' secrets: CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index 4cf11cde3c61..4da765468d39 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -3070,10 +3070,10 @@ web: min: 2 resources: limits: - cpu: '400m' + cpu: '1000m' memory: '768Mi' requests: - cpu: '200m' + cpu: '300m' memory: '384Mi' secrets: CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' From 82360b08a92abd85d564a5a17635494aab92c4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9tur=20Neisti=20Erlingsson?= Date: Wed, 11 Sep 2024 14:04:43 +0000 Subject: [PATCH 05/13] fix: license-api replicas too high due to CPU request being too low (#15956) * fix: license-api replicas too high due to CPU request being too low * chore: charts update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/services/license-api/infra/license-api.ts | 2 +- charts/islandis/values.dev.yaml | 2 +- charts/islandis/values.prod.yaml | 2 +- charts/islandis/values.staging.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/services/license-api/infra/license-api.ts b/apps/services/license-api/infra/license-api.ts index beed0f9cc795..586ded487c92 100644 --- a/apps/services/license-api/infra/license-api.ts +++ b/apps/services/license-api/infra/license-api.ts @@ -13,7 +13,7 @@ export const serviceSetup = (): ServiceBuilder<'license-api'> => .namespace('license-api') .resources({ limits: { cpu: '400m', memory: '512Mi' }, - requests: { cpu: '15m', memory: '256Mi' }, + requests: { cpu: '50m', memory: '256Mi' }, }) .env({ IDENTITY_SERVER_ISSUER_URL: { diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index 3a089235bd45..85a162c06079 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -1735,7 +1735,7 @@ license-api: cpu: '400m' memory: '512Mi' requests: - cpu: '15m' + cpu: '50m' memory: '256Mi' secrets: ADR_LICENSE_PASS_TEMPLATE_ID: '/k8s/api/ADR_LICENSE_PASS_TEMPLATE_ID' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 9d12ceb47191..80208b6aaef4 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -1602,7 +1602,7 @@ license-api: cpu: '400m' memory: '512Mi' requests: - cpu: '15m' + cpu: '50m' memory: '256Mi' secrets: ADR_LICENSE_PASS_TEMPLATE_ID: '/k8s/api/ADR_LICENSE_PASS_TEMPLATE_ID' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index 4da765468d39..b23cb6d4752d 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -1480,7 +1480,7 @@ license-api: cpu: '400m' memory: '512Mi' requests: - cpu: '15m' + cpu: '50m' memory: '256Mi' secrets: ADR_LICENSE_PASS_TEMPLATE_ID: '/k8s/api/ADR_LICENSE_PASS_TEMPLATE_ID' From bcf0cde224f857a654a15ec5d8ce13b31d130f1b Mon Sep 17 00:00:00 2001 From: berglindoma13 Date: Wed, 11 Sep 2024 14:17:16 +0000 Subject: [PATCH 06/13] fix(nafnskirteini): small string updates (#15957) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../templates/id-card/src/forms/Approved.ts | 3 --- .../IdInformation/ConditionInformation.ts | 25 +++++++++++++++++-- .../IdInformation/TypeOfIdSubSection.ts | 15 +++++++++-- .../id-card/src/forms/Prerequisites/index.ts | 3 ++- .../id-card/src/lib/messages/idInformation.ts | 7 +----- .../id-card/src/utils/getChosenApplicant.ts | 16 ++++++------ .../id-card/src/utils/updateAnswers.ts | 1 - 7 files changed, 46 insertions(+), 24 deletions(-) diff --git a/libs/application/templates/id-card/src/forms/Approved.ts b/libs/application/templates/id-card/src/forms/Approved.ts index 0f6d01423b21..46a6c69b31a7 100644 --- a/libs/application/templates/id-card/src/forms/Approved.ts +++ b/libs/application/templates/id-card/src/forms/Approved.ts @@ -30,7 +30,6 @@ export const Approved: Form = buildForm({ '', ) as string const chosenApplicant = getChosenApplicant( - application.answers, application.externalData, applicantNationalId, ) @@ -54,7 +53,6 @@ export const Approved: Form = buildForm({ '', ) as string const chosenApplicant = getChosenApplicant( - formValue, externalData, applicantNationalId, ) @@ -79,7 +77,6 @@ export const Approved: Form = buildForm({ '', ) as string const chosenApplicant = getChosenApplicant( - formValue, externalData, applicantNationalId, ) diff --git a/libs/application/templates/id-card/src/forms/IdCardForm/IdInformation/ConditionInformation.ts b/libs/application/templates/id-card/src/forms/IdCardForm/IdInformation/ConditionInformation.ts index fc1297f675e7..8a8a5629beec 100644 --- a/libs/application/templates/id-card/src/forms/IdCardForm/IdInformation/ConditionInformation.ts +++ b/libs/application/templates/id-card/src/forms/IdCardForm/IdInformation/ConditionInformation.ts @@ -1,6 +1,7 @@ import { buildSubSection, buildDescriptionField, + getValueViaPath, } from '@island.is/application/core' import { Routes } from '../../../lib/constants' import { idInformation } from '../../../lib/messages/idInformation' @@ -10,7 +11,14 @@ export const ConditionInformationSection = buildSubSection({ id: Routes.CONDITIONINFORMATION, title: idInformation.general.conditionSectionTitle, condition: (formValue, externalData) => { - const chosenApplicant = getChosenApplicant(formValue, externalData) + const radioAnswerApplicant = getValueViaPath( + formValue, + 'chosenApplicants', + ) as string + const chosenApplicant = getChosenApplicant( + externalData, + radioAnswerApplicant, + ) const applicantHasReviewer = hasReviewer(formValue, externalData) return !chosenApplicant.isApplicant && applicantHasReviewer @@ -19,7 +27,20 @@ export const ConditionInformationSection = buildSubSection({ buildDescriptionField({ id: `${Routes.CONDITIONINFORMATION}MultiField`, title: idInformation.general.conditionSectionTitle, - description: idInformation.labels.conditionDescription, + description: (application) => { + const radioAnswerApplicant = getValueViaPath( + application.answers, + 'chosenApplicants', + ) as string + const chosenChild = getChosenApplicant( + application.externalData, + radioAnswerApplicant, + ) + return { + id: idInformation.labels.conditionDescription.id, + values: { parentBName: chosenChild.secondParentName }, + } + }, space: 2, }), ], diff --git a/libs/application/templates/id-card/src/forms/IdCardForm/IdInformation/TypeOfIdSubSection.ts b/libs/application/templates/id-card/src/forms/IdCardForm/IdInformation/TypeOfIdSubSection.ts index d6a0ad0ec889..95e412404524 100644 --- a/libs/application/templates/id-card/src/forms/IdCardForm/IdInformation/TypeOfIdSubSection.ts +++ b/libs/application/templates/id-card/src/forms/IdCardForm/IdInformation/TypeOfIdSubSection.ts @@ -30,9 +30,13 @@ export const TypeOfIdSubSection = buildSubSection({ options: (application) => { const combinedAppplicantInformation = getCombinedApplicantInformation(application.externalData) - const chosenApplicant = getChosenApplicant( + const radioAnswerApplicant = getValueViaPath( application.answers, + 'chosenApplicants', + ) as string + const chosenApplicant = getChosenApplicant( application.externalData, + radioAnswerApplicant, ) if (!chosenApplicant.isApplicant) { @@ -79,7 +83,14 @@ export const TypeOfIdSubSection = buildSubSection({ condition: (answers, externalData) => { const combinedAppplicantInformation = getCombinedApplicantInformation(externalData) - const chosenApplicant = getChosenApplicant(answers, externalData) + const radioAnswerApplicant = getValueViaPath( + answers, + 'chosenApplicants', + ) as string + const chosenApplicant = getChosenApplicant( + externalData, + radioAnswerApplicant, + ) if (!chosenApplicant.isApplicant) { const childPassports = getValueViaPath( externalData, diff --git a/libs/application/templates/id-card/src/forms/Prerequisites/index.ts b/libs/application/templates/id-card/src/forms/Prerequisites/index.ts index f9bfb2f5535a..80e59e4a84c3 100644 --- a/libs/application/templates/id-card/src/forms/Prerequisites/index.ts +++ b/libs/application/templates/id-card/src/forms/Prerequisites/index.ts @@ -106,7 +106,8 @@ export const Prerequisites: Form = buildForm({ }), buildDataProviderItem({ provider: IdentityDocumentApi, - title: '', + title: externalData.identityDocument.title, + subTitle: externalData.identityDocument.subTitle, }), buildDataProviderItem({ provider: SyslumadurPaymentCatalogApi, diff --git a/libs/application/templates/id-card/src/lib/messages/idInformation.ts b/libs/application/templates/id-card/src/lib/messages/idInformation.ts index 021508177acb..e97c4b1d8e33 100644 --- a/libs/application/templates/id-card/src/lib/messages/idInformation.ts +++ b/libs/application/templates/id-card/src/lib/messages/idInformation.ts @@ -86,12 +86,7 @@ export const idInformation = { }, conditionDescription: { id: 'id.application:idInformation.labels.conditionDescription#markdown', - defaultMessage: `Þegar sótt er um fyrir barn þurfa **báðir forsjáraðilar** að - samþykkja umsóknina innan **7 daga**. Viðkomandi fær tilkynningu - um undirritun og getur samþykkt með rafrænum hætti. - \n\nSamþykkið er **vistað rafrænt** hjá Þjóðskrá Íslands. - \n\nSéu rafræn skilríki ekki fyrir hendi er hægt að skila inn - **skriflegu samþykki til Sýslumanns/Þjóðskrár.**`, + defaultMessage: `Þegar sótt er um fyrir barn þurfa báðir forsjáraðilar að samþykkja umsóknina. Þegar þú hefur klárað umsóknina mun {parentBName} fá tilkynningu um að samþykkja þurfi þessa umsókn inni á island.is með rafrænum skilríkjum. {parentBName} hefur 7 daga til að ganga frá samþykkinu og það verður síðan vistað rafrænt hjá Þjóðskrá Íslands. Ef rafræn skilríki eru ekki fyrir hendi er ekki hægt að klára forskráningu hér og sækja verður um á umsóknarstað.`, description: 'Description on condition page', }, }), diff --git a/libs/application/templates/id-card/src/utils/getChosenApplicant.ts b/libs/application/templates/id-card/src/utils/getChosenApplicant.ts index 62a534ddcad3..b70232c00d37 100644 --- a/libs/application/templates/id-card/src/utils/getChosenApplicant.ts +++ b/libs/application/templates/id-card/src/utils/getChosenApplicant.ts @@ -1,20 +1,17 @@ import { getValueViaPath } from '@island.is/application/core' -import { - FormValue, - NationalRegistryIndividual, -} from '@island.is/application/types' +import { NationalRegistryIndividual } from '@island.is/application/types' import { IdentityDocumentChild } from '../lib/constants' export interface ChosenApplicant { name?: string | null isApplicant: boolean - nationalId?: string | null + nationalId: string | null + secondParentName?: string | null } export const getChosenApplicant = ( - answers: FormValue, externalData: any, - nationalId?: string | null, + nationalId: string | null, ): ChosenApplicant => { const applicantIdentity = getValueViaPath( externalData, @@ -28,7 +25,7 @@ export const getChosenApplicant = ( [], ) as Array - if (!nationalId || applicantIdentity?.nationalId === nationalId) { + if (applicantIdentity?.nationalId === nationalId) { return { name: applicantIdentity?.fullName, isApplicant: true, @@ -41,7 +38,8 @@ export const getChosenApplicant = ( return { name: chosenChild.childName, isApplicant: false, - nationalId: chosenChild.childNationalId, + nationalId: chosenChild.childNationalId || '', + secondParentName: chosenChild.secondParentName, } } } diff --git a/libs/application/templates/id-card/src/utils/updateAnswers.ts b/libs/application/templates/id-card/src/utils/updateAnswers.ts index 582681c09b2e..a8dd138072d1 100644 --- a/libs/application/templates/id-card/src/utils/updateAnswers.ts +++ b/libs/application/templates/id-card/src/utils/updateAnswers.ts @@ -9,7 +9,6 @@ export const updateAnswers = ( setValue: (name: string, value: unknown, config?: Object) => void, ): Object => { const chosenApplicants = getChosenApplicant( - application.answers, application.externalData, nationalId, ) From b3af8c0a0b6e799222597c9f714a6be80d2e9517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9tur=20Neisti=20Erlingsson?= Date: Wed, 11 Sep 2024 14:24:43 +0000 Subject: [PATCH 07/13] fix: application-system-api replicas too high due to CPU request being too low (#15958) * fix: application-system-api replicas too high due to CPU request being too low * chore: charts update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/application-system/api/infra/application-system-api.ts | 2 +- charts/islandis/values.dev.yaml | 2 +- charts/islandis/values.prod.yaml | 2 +- charts/islandis/values.staging.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/application-system/api/infra/application-system-api.ts b/apps/application-system/api/infra/application-system-api.ts index 1e5cf164b74e..98e258a3d972 100644 --- a/apps/application-system/api/infra/application-system-api.ts +++ b/apps/application-system/api/infra/application-system-api.ts @@ -113,7 +113,7 @@ export const workerSetup = }) .resources({ limits: { cpu: '400m', memory: '768Mi' }, - requests: { cpu: '100m', memory: '384Mi' }, + requests: { cpu: '150m', memory: '384Mi' }, }) export const serviceSetup = (services: { diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index 85a162c06079..910070f734b9 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -882,7 +882,7 @@ application-system-api-worker: cpu: '400m' memory: '768Mi' requests: - cpu: '100m' + cpu: '150m' memory: '384Mi' schedule: '*/30 * * * *' secrets: diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 80208b6aaef4..3a23969c8f10 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -872,7 +872,7 @@ application-system-api-worker: cpu: '400m' memory: '768Mi' requests: - cpu: '100m' + cpu: '150m' memory: '384Mi' schedule: '*/30 * * * *' secrets: diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index b23cb6d4752d..586ed48cbd17 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -880,7 +880,7 @@ application-system-api-worker: cpu: '400m' memory: '768Mi' requests: - cpu: '100m' + cpu: '150m' memory: '384Mi' schedule: '*/30 * * * *' secrets: From 72fc394b990ba88a2194c5fcdc868af6547d87cd Mon Sep 17 00:00:00 2001 From: birkirkristmunds <142495885+birkirkristmunds@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:38:55 +0000 Subject: [PATCH 08/13] feat(Skilavottord): Handle number plates and information about pre-deregistered vehicles (#15878) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TS-866 Rough first implementation * TS-866 Implemented Samgöngustofa Traffic REST call. * TS-866 Fix unit test * TS-866 Continue working - Translations - UI * TS-866 Remove deregister column * TS-866 Fixing wrong data sent to Samgöngustofu * TS-866 Fixing show right alert box and hide selection box * TS-866 Refactoring code * TS-866 Fix after Coderabbit review * TS-866 Fix after Coderabbit review * TS-866 Fix after Coderabbit review * TS-866 Fix after Coderabbit review * TS-866 Code refactoring * TS-866 Removed checkbox blue background * TS-866 Fix 0 mileage * TS-866 Fix after coderabbit review * TS-866 Add back owner * TS-866 improve logging --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../CarDetailsBox/CarDetailsBox.tsx | 31 --- .../CarDetailsBox2/CarDetailsBox2.tsx | 181 ++++++++++++++++++ .../web/components/CarDetailsBox2/index.ts | 1 + .../components/InlineError/InlineError.tsx | 16 +- apps/skilavottord/web/components/index.ts | 1 + apps/skilavottord/web/i18n/locales/en.json | 18 +- apps/skilavottord/web/i18n/locales/is.json | 18 +- .../web/i18n/locales/translation.d.ts | 18 ++ .../DeregisterVehicle/Confirm/Confirm.tsx | 116 +++++++---- apps/skilavottord/web/utils/consts.ts | 13 ++ .../20201008200611-vehicle_owner.js | 2 - .../ws/migrations/20201008210611-vehicle.js | 1 - .../20201008210711-recycling-partners.js | 2 - .../20201008220611-recycling_request.js | 2 - .../ws/migrations/20201008230611-gdpr.js | 2 - .../20211207210715-access-control.js | 2 - ...0318210711-addcolumn-recycling-partners.js | 2 - ...20220318210715-addcolumn-access-control.js | 2 - ...0318210715-alow-null-recycling-partners.js | 2 - .../20231113210715-addcolumn-mileage.js | 2 - .../migrations/20240307280301-add-indexes.js | 2 - ...09083210715-addcolumns-numberplate-info.js | 18 ++ .../recyclingRequest.resolver.ts | 7 +- .../recyclingRequest.service.ts | 38 ++-- .../samgongustofa/samgongustofa.model.ts | 54 ++++++ .../samgongustofa/samgongustofa.module.ts | 3 +- .../samgongustofa/samgongustofa.resolver.ts | 12 +- .../samgongustofa/samgongustofa.service.ts | 79 +++++++- .../test/samgongustofa.service.spec.ts | 7 + .../modules/samgongustofa/transport/index.ts | 1 + .../transport/transport.service.ts | 111 +++++++++++ .../src/app/modules/vehicle/vehicle.model.ts | 12 ++ .../app/modules/vehicle/vehicle.resolver.ts | 11 +- .../app/modules/vehicle/vehicle.service.ts | 15 +- .../modules/vehicle/vehicleAppSys.resolver.ts | 10 +- .../icelandicTransportAuthority.module.ts | 7 +- .../icelandicTransportAuthority.services.ts | 125 +----------- apps/skilavottord/ws/src/app/utils/const.ts | 5 + apps/skilavottord/ws/src/app/utils/index.ts | 2 + .../ws/src/app/utils/skilavottordUtils.ts | 8 + 40 files changed, 709 insertions(+), 250 deletions(-) create mode 100644 apps/skilavottord/web/components/CarDetailsBox2/CarDetailsBox2.tsx create mode 100644 apps/skilavottord/web/components/CarDetailsBox2/index.ts create mode 100644 apps/skilavottord/ws/migrations/202409083210715-addcolumns-numberplate-info.js create mode 100644 apps/skilavottord/ws/src/app/modules/samgongustofa/transport/index.ts create mode 100644 apps/skilavottord/ws/src/app/modules/samgongustofa/transport/transport.service.ts create mode 100644 apps/skilavottord/ws/src/app/utils/const.ts create mode 100644 apps/skilavottord/ws/src/app/utils/index.ts create mode 100644 apps/skilavottord/ws/src/app/utils/skilavottordUtils.ts diff --git a/apps/skilavottord/web/components/CarDetailsBox/CarDetailsBox.tsx b/apps/skilavottord/web/components/CarDetailsBox/CarDetailsBox.tsx index 7b226468f4d2..08f270833fd8 100644 --- a/apps/skilavottord/web/components/CarDetailsBox/CarDetailsBox.tsx +++ b/apps/skilavottord/web/components/CarDetailsBox/CarDetailsBox.tsx @@ -2,19 +2,11 @@ import { Box, Stack, Text } from '@island.is/island-ui/core' import { OutlinedBox } from '@island.is/skilavottord-web/components' import React, { FC } from 'react' -import { useI18n } from '@island.is/skilavottord-web/i18n' - -import { InputController } from '@island.is/shared/form-fields' -import { Control, FieldValues } from 'react-hook-form' - interface BoxProps { vehicleId: string vehicleType: string modelYear: string vehicleOwner?: string | null - mileage?: number - control?: Control - showMileage?: boolean } export const CarDetailsBox: FC> = ({ @@ -22,16 +14,7 @@ export const CarDetailsBox: FC> = ({ vehicleType, modelYear, vehicleOwner, - mileage, - control, - showMileage, }) => { - const { - t: { - deregisterVehicle: { deregister: t }, - }, - } = useI18n() - return ( > = ({ > {vehicleId} - {showMileage && {mileage} km} {`${vehicleType}, ${modelYear}`} {vehicleOwner} - - {showMileage && ( - - - - )} ) diff --git a/apps/skilavottord/web/components/CarDetailsBox2/CarDetailsBox2.tsx b/apps/skilavottord/web/components/CarDetailsBox2/CarDetailsBox2.tsx new file mode 100644 index 000000000000..b76419da7cf3 --- /dev/null +++ b/apps/skilavottord/web/components/CarDetailsBox2/CarDetailsBox2.tsx @@ -0,0 +1,181 @@ +import { + AlertMessage, + Box, + Checkbox, + GridColumn, + GridContainer, + GridRow, + Stack, + Text, +} from '@island.is/island-ui/core' +import React, { FC, useState } from 'react' + +import { useI18n } from '@island.is/skilavottord-web/i18n' + +import { + InputController, + SelectController, +} from '@island.is/shared/form-fields' + +import { + OutInUsage, + PlateInfo, + UseStatus, +} from '@island.is/skilavottord-web/utils/consts' +import { Controller } from 'react-hook-form' + +interface BoxProps { + vehicleId: string + vehicleType: string + modelYear: string + mileage?: number + vehicleOwner?: string | null + vinNumber?: string + outInStatus: number + useStatus: string +} + +export const CarDetailsBox2: FC> = ({ + vehicleId, + vehicleType, + modelYear, + vehicleOwner, + vinNumber, + mileage, + outInStatus, + useStatus, +}) => { + const { + t: { + deregisterVehicle: { deregister: t }, + }, + } = useI18n() + + const [missingPlates, setMissingPlates] = useState(false) + const [lostPlate, setLostPlate] = useState(false) + + return ( + + + + + + + {vehicleId} + {mileage} km + {`${vehicleType}, ${modelYear}`} + {vinNumber && VIN: {vinNumber}} + {vehicleOwner} + + + + + + + + + + {t.numberplate.sectionTitle} + + + + {outInStatus === OutInUsage.OUT && ( + + + {useStatus !== UseStatus.OUT_TICKET && ( + + )} + {useStatus === UseStatus.OUT_TICKET && ( + + )} + + + )} + + {(outInStatus === OutInUsage.IN || + (outInStatus === OutInUsage.OUT && + useStatus === UseStatus.OUT_TICKET)) && ( + + + { + if (option?.value === 2) { + setMissingPlates(false) + setLostPlate(false) + } else { + setMissingPlates(true) + } + }} + defaultValue={2} + /> + + + )} + {missingPlates && ( + + + { + return ( + { + if (!lostPlate) { + onChange(PlateInfo.PLATE_LOST) + } else { + onChange() + } + + setLostPlate(!lostPlate) + }} + /> + ) + }} + /> + + + )} + + {lostPlate && ( + + + + + + )} + + + ) +} diff --git a/apps/skilavottord/web/components/CarDetailsBox2/index.ts b/apps/skilavottord/web/components/CarDetailsBox2/index.ts new file mode 100644 index 000000000000..9007f2a0b0b7 --- /dev/null +++ b/apps/skilavottord/web/components/CarDetailsBox2/index.ts @@ -0,0 +1 @@ +export * from './CarDetailsBox2' diff --git a/apps/skilavottord/web/components/InlineError/InlineError.tsx b/apps/skilavottord/web/components/InlineError/InlineError.tsx index 7078847692a9..77ad20b6cfb7 100644 --- a/apps/skilavottord/web/components/InlineError/InlineError.tsx +++ b/apps/skilavottord/web/components/InlineError/InlineError.tsx @@ -4,7 +4,7 @@ import { Box, Button, Icon, Stack, Text } from '@island.is/island-ui/core' export interface InlineErrorProps { title: string message: string - primaryButton: Button + primaryButton?: Button secondaryButton?: Button } @@ -40,12 +40,14 @@ export const InlineError: FC> = ({ {secondaryButton.text} )} - + {primaryButton && ( + + )} ) diff --git a/apps/skilavottord/web/components/index.ts b/apps/skilavottord/web/components/index.ts index bf898f6a3fe5..29e01cd92868 100644 --- a/apps/skilavottord/web/components/index.ts +++ b/apps/skilavottord/web/components/index.ts @@ -13,6 +13,7 @@ export * from './Sidenav/Sidenav' export * from './GDPR' export * from './Modal/Modal' export * from './CarDetailsBox/CarDetailsBox' +export * from './CarDetailsBox2/CarDetailsBox2' export * from './LinkProvider/LinkProvider' export * from './Layouts/AppLayout' export * from './Layouts/PageLayout' diff --git a/apps/skilavottord/web/i18n/locales/en.json b/apps/skilavottord/web/i18n/locales/en.json index 44b282fe1f66..cb3f7c2f3f2c 100644 --- a/apps/skilavottord/web/i18n/locales/en.json +++ b/apps/skilavottord/web/i18n/locales/en.json @@ -329,7 +329,23 @@ "primaryButton": "Try again", "secondaryButton": "Cancel" }, - "currentMileage": "Current mileage" + "currentMileage": "Current mileage", + "numberplate": { + "sectionTitle": "Number plates information", + "alert": { + "info": { + "title": "The vehicle is registered out of use at the Icelandic Transport Authority.", + "message": "The number plates are deposited at the Icelandic Transport Authority" + }, + "warning": { + "title": "The vehicle is registered out of use with a sticker at the Icelandic Transport Authority.", + "message": "The number plates must accompany the vehicle" + } + }, + "count": "Number of license plates returned with the vehicle", + "lost": "Number plates are missing", + "missingInfo": "If the number plates are found, they must be returned to the Icelandic Transport Authority" + } } }, "recyclingFundOverview": { diff --git a/apps/skilavottord/web/i18n/locales/is.json b/apps/skilavottord/web/i18n/locales/is.json index 7b4cfc5c7825..d8ec320474ca 100644 --- a/apps/skilavottord/web/i18n/locales/is.json +++ b/apps/skilavottord/web/i18n/locales/is.json @@ -329,7 +329,23 @@ "primaryButton": "Reyna aftur", "secondaryButton": "Hætta við" }, - "currentMileage": "Núverandi kílómetrastaða" + "currentMileage": "Núverandi kílómetrastaða", + "numberplate": { + "sectionTitle": "Upplýsingar um númeraplötur", + "alert": { + "info": { + "title": "Ökutækið er skráð úr umferð hjá Samgöngustofu", + "message": "Númeraplöturnar eru innlagðar hjá Samgöngustofu" + }, + "warning": { + "title": "Ökutækið er skráð úr umferð (miði) hjá Samgöngustofu", + "message": "Númeraplöturnar eiga að fylgja ökutækinu" + } + }, + "count": "Fjöldi númeraplatna skilað með ökutækinu", + "lost": "Númeraplötur eru týndar", + "missingInfo": "Ef að númeraplöturnar finnast skal skila þeim til Samgöngustofu" + } } }, "recyclingFundOverview": { diff --git a/apps/skilavottord/web/i18n/locales/translation.d.ts b/apps/skilavottord/web/i18n/locales/translation.d.ts index a43d1c7e44e9..28dd89be7eb0 100644 --- a/apps/skilavottord/web/i18n/locales/translation.d.ts +++ b/apps/skilavottord/web/i18n/locales/translation.d.ts @@ -273,6 +273,24 @@ export interface Deregister { success: string error: CompletedError currentMileage: string + numberplate: NumberPlate +} + +export interface NumberPlate { + sectionTitle: string + alert: DeregisteredMessages + count: string + lost: string + missingInfo: string +} + +export interface DeregisteredMessages { + info: Message + warning: Message +} +export interface Message { + title: string + message: string } export interface DeregisterButtons { diff --git a/apps/skilavottord/web/screens/DeregisterVehicle/Confirm/Confirm.tsx b/apps/skilavottord/web/screens/DeregisterVehicle/Confirm/Confirm.tsx index d89e2f6c7ea1..293ac53554ed 100644 --- a/apps/skilavottord/web/screens/DeregisterVehicle/Confirm/Confirm.tsx +++ b/apps/skilavottord/web/screens/DeregisterVehicle/Confirm/Confirm.tsx @@ -18,7 +18,7 @@ import { import { hasPermission } from '@island.is/skilavottord-web/auth/utils' import { - CarDetailsBox, + CarDetailsBox2, NotFound, OutlinedError, ProcessPageLayout, @@ -33,8 +33,9 @@ import { Role, } from '@island.is/skilavottord-web/graphql/schema' import { useI18n } from '@island.is/skilavottord-web/i18n' +import { OutInUsage } from '@island.is/skilavottord-web/utils/consts' import { getYear } from '@island.is/skilavottord-web/utils/dateUtils' -import { useForm } from 'react-hook-form' +import { FormProvider, useForm } from 'react-hook-form' const SkilavottordVehicleReadyToDeregisteredQuery = gql` query skilavottordVehicleReadyToDeregisteredQuery($permno: String!) { @@ -42,10 +43,22 @@ const SkilavottordVehicleReadyToDeregisteredQuery = gql` vehicleId vehicleType newregDate + vinNumber + mileage recyclingRequests { nameOfRequestor } - mileage + } + } +` + +const SkilavottordTrafficQuery = gql` + query skilavottordTrafficQuery($permno: String!) { + skilavottordTraffic(permno: $permno) { + permno + outInStatus + useStatus + useStatusName } } ` @@ -70,19 +83,27 @@ const SkilavottordRecyclingRequestMutation = gql` } ` -const UpdateSkilavottordVehicleMileageMutation = gql` - mutation updateSkilavottordVehicleMileage( +const UpdateSkilavottordVehicleInfoMutation = gql` + mutation updateSkilavottordVehicleInfo( $permno: String! $mileage: Float! + $plateCount: Float! + $plateLost: Boolean! ) { - updateSkilavottordVehicleMileage(permno: $permno, mileage: $mileage) + updateSkilavottordVehicleInfo( + permno: $permno + mileage: $mileage + plateCount: $plateCount + plateLost: $plateLost + ) } ` const Confirm: FC> = () => { - const { control, watch } = useForm({ + const methods = useForm({ mode: 'onChange', }) + const { watch } = methods const { user } = useContext(UserContext) const { @@ -95,6 +116,8 @@ const Confirm: FC> = () => { const { id } = router.query const mileageValue = watch('mileage') + const plateLost = watch('plateLost') + const plateCountValue = watch('plateCount') const { data, loading } = useQuery( SkilavottordVehicleReadyToDeregisteredQuery, @@ -105,6 +128,22 @@ const Confirm: FC> = () => { const vehicle = data?.skilavottordVehicleReadyToDeregistered + const { data: traffic, loading: loadingTraffic } = useQuery( + SkilavottordTrafficQuery, + { + variables: { permno: id }, + }, + ) + + const vehicleTrafficData = traffic?.skilavottordTraffic + + const outInStatus = + vehicleTrafficData?.outInStatus.toLocaleUpperCase() === 'OUT' + ? OutInUsage.OUT + : OutInUsage.IN + + const useStatus = vehicleTrafficData?.useStatus || '01' + const [ setRecyclingRequest, { data: mutationData, error: mutationError, loading: mutationLoading }, @@ -129,7 +168,7 @@ const Confirm: FC> = () => { error: vehicleMutationError, loading: vehicleMutationLoading, }, - ] = useMutation(UpdateSkilavottordVehicleMileageMutation, { + ] = useMutation(UpdateSkilavottordVehicleInfoMutation, { onError() { return vehicleMutationError }, @@ -144,28 +183,32 @@ const Confirm: FC> = () => { }, [vehicleMutationResponse, router, routes, t.success]) const handleConfirm = () => { - if (mileageValue !== undefined) { - const newMilage = +mileageValue.trim().replace(/\./g, '') + let newMileage = mileageValue - // If registered mileage is not the same as the one when vehicle is confirmed for de-registration we need to update it - if (vehicle?.mileage !== newMilage) { - setVehicleRequest({ - variables: { - permno: vehicle?.vehicleId, - mileage: newMilage, - }, - }) - } + if (mileageValue !== undefined) { + newMileage = +mileageValue.trim().replace(/\./g, '') + } else { + newMileage = vehicle?.mileage } - setRecyclingRequest({ + // Update vehicle table with latests information + setVehicleRequest({ variables: { - permno: id, - requestType: RecyclingRequestTypes.deregistered, + permno: vehicle?.vehicleId, + mileage: newMileage, + plateCount: plateCountValue === 0 ? 0 : plateCountValue, + plateLost: !!plateLost?.length, }, + }).then(() => { + // Send recycling request + setRecyclingRequest({ + variables: { + permno: id, + requestType: RecyclingRequestTypes.deregistered, + }, + }) }) } - const handleBack = () => { router.replace(routes.select) } @@ -221,22 +264,22 @@ const Confirm: FC> = () => { {t.titles.success} {t.info.success} - + + + ) : ( - {loading ? ( + {loading || loadingTraffic ? ( @@ -280,5 +323,4 @@ const Confirm: FC> = () => { ) } - export default Confirm diff --git a/apps/skilavottord/web/utils/consts.ts b/apps/skilavottord/web/utils/consts.ts index 675f1b64953d..3567ecffb468 100644 --- a/apps/skilavottord/web/utils/consts.ts +++ b/apps/skilavottord/web/utils/consts.ts @@ -1 +1,14 @@ export const ACCEPTED_TERMS_AND_CONDITION = 'acceptedTermsAndConditions' + +export enum PlateInfo { + PLATE_LOST = '0', +} + +export enum UseStatus { + OUT_TICKET = '16', // Úr umferð (miði) +} + +export enum OutInUsage { + OUT = 0, + IN = 1, +} diff --git a/apps/skilavottord/ws/migrations/20201008200611-vehicle_owner.js b/apps/skilavottord/ws/migrations/20201008200611-vehicle_owner.js index f4170a4404d7..f179c3da7af0 100644 --- a/apps/skilavottord/ws/migrations/20201008200611-vehicle_owner.js +++ b/apps/skilavottord/ws/migrations/20201008200611-vehicle_owner.js @@ -1,5 +1,3 @@ -'use strict' - module.exports.up = (queryInterface, DataTypes) => { return queryInterface.createTable( 'vehicle_owner', diff --git a/apps/skilavottord/ws/migrations/20201008210611-vehicle.js b/apps/skilavottord/ws/migrations/20201008210611-vehicle.js index 100f501066d6..7993f667c03f 100644 --- a/apps/skilavottord/ws/migrations/20201008210611-vehicle.js +++ b/apps/skilavottord/ws/migrations/20201008210611-vehicle.js @@ -1,4 +1,3 @@ -'use strict' // newreg_date TIMESTAMP WITH TIME ZONE DEFAULT now(), // vin_number VARCHAR, module.exports.up = (queryInterface, DataTypes) => { diff --git a/apps/skilavottord/ws/migrations/20201008210711-recycling-partners.js b/apps/skilavottord/ws/migrations/20201008210711-recycling-partners.js index b44b3ffe0fee..9823d0c2723e 100644 --- a/apps/skilavottord/ws/migrations/20201008210711-recycling-partners.js +++ b/apps/skilavottord/ws/migrations/20201008210711-recycling-partners.js @@ -1,5 +1,3 @@ -'use strict' - module.exports.up = (queryInterface, DataTypes) => { return queryInterface.createTable( 'recycling_partner', diff --git a/apps/skilavottord/ws/migrations/20201008220611-recycling_request.js b/apps/skilavottord/ws/migrations/20201008220611-recycling_request.js index d1eb3b08decc..d6c4e8e8e1f8 100644 --- a/apps/skilavottord/ws/migrations/20201008220611-recycling_request.js +++ b/apps/skilavottord/ws/migrations/20201008220611-recycling_request.js @@ -1,5 +1,3 @@ -'use strict' - module.exports.up = (queryInterface, DataTypes) => { return queryInterface.createTable( 'recycling_request', diff --git a/apps/skilavottord/ws/migrations/20201008230611-gdpr.js b/apps/skilavottord/ws/migrations/20201008230611-gdpr.js index 5af67b0129ea..cfd1cf8c7d14 100644 --- a/apps/skilavottord/ws/migrations/20201008230611-gdpr.js +++ b/apps/skilavottord/ws/migrations/20201008230611-gdpr.js @@ -1,5 +1,3 @@ -'use strict' - module.exports.up = (queryInterface, DataTypes) => { return queryInterface.createTable( 'gdpr', diff --git a/apps/skilavottord/ws/migrations/20211207210715-access-control.js b/apps/skilavottord/ws/migrations/20211207210715-access-control.js index 1ff3e3462013..3df1d4b2c6af 100644 --- a/apps/skilavottord/ws/migrations/20211207210715-access-control.js +++ b/apps/skilavottord/ws/migrations/20211207210715-access-control.js @@ -1,5 +1,3 @@ -'use strict' - module.exports.up = (queryInterface, DataTypes) => { return queryInterface.createTable( 'access_control', diff --git a/apps/skilavottord/ws/migrations/20220318210711-addcolumn-recycling-partners.js b/apps/skilavottord/ws/migrations/20220318210711-addcolumn-recycling-partners.js index f1b24e25d48c..90c9e7040264 100644 --- a/apps/skilavottord/ws/migrations/20220318210711-addcolumn-recycling-partners.js +++ b/apps/skilavottord/ws/migrations/20220318210711-addcolumn-recycling-partners.js @@ -1,5 +1,3 @@ -'use strict' - module.exports = { up: (queryInterface) => { return queryInterface.sequelize.query(` diff --git a/apps/skilavottord/ws/migrations/20220318210715-addcolumn-access-control.js b/apps/skilavottord/ws/migrations/20220318210715-addcolumn-access-control.js index 0e8d44c80476..63ce3c22fcbd 100644 --- a/apps/skilavottord/ws/migrations/20220318210715-addcolumn-access-control.js +++ b/apps/skilavottord/ws/migrations/20220318210715-addcolumn-access-control.js @@ -1,5 +1,3 @@ -'use strict' - module.exports = { up: (queryInterface) => { return queryInterface.sequelize.query(` diff --git a/apps/skilavottord/ws/migrations/20220318210715-alow-null-recycling-partners.js b/apps/skilavottord/ws/migrations/20220318210715-alow-null-recycling-partners.js index 1839c87917f4..111883805f16 100644 --- a/apps/skilavottord/ws/migrations/20220318210715-alow-null-recycling-partners.js +++ b/apps/skilavottord/ws/migrations/20220318210715-alow-null-recycling-partners.js @@ -1,5 +1,3 @@ -'use strict' - module.exports = { up: (queryInterface) => { return queryInterface.sequelize.query(` diff --git a/apps/skilavottord/ws/migrations/20231113210715-addcolumn-mileage.js b/apps/skilavottord/ws/migrations/20231113210715-addcolumn-mileage.js index 08959859c3af..8aa4b76bd3e2 100644 --- a/apps/skilavottord/ws/migrations/20231113210715-addcolumn-mileage.js +++ b/apps/skilavottord/ws/migrations/20231113210715-addcolumn-mileage.js @@ -1,5 +1,3 @@ -'use strict' - //add mileage change module.exports = { up: (queryInterface) => { diff --git a/apps/skilavottord/ws/migrations/20240307280301-add-indexes.js b/apps/skilavottord/ws/migrations/20240307280301-add-indexes.js index 68ec78a3044e..fced6fe46ab7 100644 --- a/apps/skilavottord/ws/migrations/20240307280301-add-indexes.js +++ b/apps/skilavottord/ws/migrations/20240307280301-add-indexes.js @@ -1,5 +1,3 @@ -'use strict' - //add mileage change module.exports = { up: (queryInterface) => { diff --git a/apps/skilavottord/ws/migrations/202409083210715-addcolumns-numberplate-info.js b/apps/skilavottord/ws/migrations/202409083210715-addcolumns-numberplate-info.js new file mode 100644 index 000000000000..4bfadb7ad42d --- /dev/null +++ b/apps/skilavottord/ws/migrations/202409083210715-addcolumns-numberplate-info.js @@ -0,0 +1,18 @@ +//add number plate & deregistered info change +module.exports = { + up: (queryInterface) => { + return queryInterface.sequelize.query(` + BEGIN; + ALTER TABLE vehicle ADD COLUMN plate_count INT; + ALTER TABLE vehicle ADD COLUMN plate_lost BOOLEAN; + COMMIT; + `) + }, + + down: (queryInterface) => { + return queryInterface.sequelize.query(` + ALTER TABLE vehicle DROP COLUMN plate_count; + ALTER TABLE vehicle DROP COLUMN plate_lost; + `) + }, +} diff --git a/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.resolver.ts b/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.resolver.ts index 0ba166e195d0..46d61f0d0a4c 100644 --- a/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.resolver.ts +++ b/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.resolver.ts @@ -76,11 +76,8 @@ export class RecyclingRequestResolver { @Args('recyclingPartner') station: string, @Args('mileage', { type: () => Int, nullable: true }) mileage: number, ): Promise { - return this.recyclingRequestService.deRegisterVehicle( - permno, - station, - mileage, - ) + //this.recyclingRequestService.deRegisterVehicle(permno, station, 0) + throw new Error('Not used anymore') } @Authorize({ diff --git a/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.service.ts b/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.service.ts index c72768a0509b..b84bedcff8ea 100644 --- a/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.service.ts +++ b/apps/skilavottord/ws/src/app/modules/recyclingRequest/recyclingRequest.service.ts @@ -21,6 +21,8 @@ import { } from './recyclingRequest.model' import { VehicleService, VehicleModel } from '../vehicle' import { IcelandicTransportAuthorityServices } from '../../services/icelandicTransportAuthority.services' +import { ApiVersion } from '../../utils/const' +import { getShortPermno } from '../../utils/skilavottordUtils' @Injectable() export class RecyclingRequestService { @@ -39,11 +41,8 @@ export class RecyclingRequestService { async deRegisterVehicle( vehiclePermno: string, disposalStation: string, - mileage = 0, + vehicle: VehicleModel, ) { - const apiVersion = '3.0' - const apiVersionParam = '?api-version=' + apiVersion - try { const { restAuthUrl, restDeRegUrl, restUsername, restPassword } = environment.samgongustofa @@ -54,12 +53,12 @@ export class RecyclingRequestService { const jsonAuthBody = JSON.stringify(jsonObj) const headerAuthRequest = { 'Content-Type': 'application/json', - 'Api-version': apiVersion, + 'Api-version': ApiVersion.REGISTRATIONS, } // TODO: saved jToken and use it in next 7 days ( until it expires ) const authRes = await lastValueFrom( - this.httpService.post(restAuthUrl + apiVersionParam, jsonAuthBody, { + this.httpService.post(restAuthUrl, jsonAuthBody, { headers: headerAuthRequest, }), ) @@ -68,26 +67,31 @@ export class RecyclingRequestService { this.logger.error(errorMessage) throw new Error(errorMessage) } + // DeRegisterd vehicle const jToken = authRes.data['jwtToken'] const jsonDeRegBody = JSON.stringify({ permno: vehiclePermno, deRegisterDate: format(new Date(), "yyyy-MM-dd'T'HH:mm:ss"), - disposalstation: disposalStation, + disposalStation: disposalStation, explanation: 'Rafrænt afskráning', - mileage: mileage, + mileage: vehicle.mileage ?? 0, + plateCount: vehicle.plateCount, + lost: vehicle.plateLost ? 1 : 0, }) + const headerDeRegRequest = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jToken, - 'Api-version': apiVersion, + 'Api-version': ApiVersion.REGISTRATIONS, } const deRegRes = await lastValueFrom( - this.httpService.post(restDeRegUrl + apiVersionParam, jsonDeRegBody, { + this.httpService.post(restDeRegUrl, jsonDeRegBody, { headers: headerDeRegRequest, }), ) + if (deRegRes.status < 300 && deRegRes.status >= 200) { return true } else { @@ -100,7 +104,9 @@ export class RecyclingRequestService { error.config.data = undefined } this.logger.error(`Failed to deregister vehicle`, { error }) - throw new Error(`Failed to deregister vehicle ${vehiclePermno.slice(-3)}`) + throw new Error( + `Failed to deregister vehicle ${getShortPermno(vehiclePermno)}`, + ) } } @@ -139,7 +145,7 @@ export class RecyclingRequestService { permno: string, ): Promise { // We are only logging the last 3 chars in the vehicle number - const loggedPermno = permno.slice(-3) + const loggedPermno = getShortPermno(permno) try { // Check 'pendingRecycle' status @@ -185,7 +191,7 @@ export class RecyclingRequestService { const errors = new RequestErrors() // We are only logging the last 3 chars in the vehicle number - const loggedPermno = permno.slice(-3) + const loggedPermno = getShortPermno(permno) this.logger.info(`car-recycling: Recycling request ${loggedPermno}`, { requestType: requestType, @@ -344,12 +350,14 @@ export class RecyclingRequestService { this.logger.info( `car-recycling: Degregistering vehicle ${loggedPermno} from Samgongustofa`, { - mileage: vehicle.mileage ?? 0, partnerId, + mileage: vehicle.mileage ?? 0, + plateCount: vehicle.plateCount, + lost: vehicle.plateLost ? 1 : 0, }, ) - await this.deRegisterVehicle(permno, partnerId, vehicle.mileage ?? 0) + await this.deRegisterVehicle(permno, partnerId, vehicle) } catch (err) { // Saved requestType back to 'pendingRecycle' const req = new RecyclingRequestModel() diff --git a/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.model.ts b/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.model.ts index 74e49c8d5db7..67ffc5b2c271 100644 --- a/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.model.ts +++ b/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.model.ts @@ -46,3 +46,57 @@ export class VehicleInformation { @Field() status: string } + +@ObjectType() +export class VehicleInformationMini { + constructor( + permno: string, + ownerSocialSecurityNumber: string, + vehicleStatus: string, + ) { + this.permno = permno + this.ownerSocialSecurityNumber = ownerSocialSecurityNumber + this.vehicleStatus = vehicleStatus + } + + @Field() + permno: string + + @Field() + ownerSocialSecurityNumber: string + + @Field() + vehicleStatus: string +} + +@ObjectType() +export class Traffic { + constructor( + permno: string, + outInStatus: string, + useStatus: string, + useStatusName: string, + useDate: string, + ) { + this.permno = permno + this.outInStatus = outInStatus + this.useStatus = useStatus + this.useStatusName = useStatusName + this.useDate = useDate + } + + @Field() + permno: string + + @Field() + outInStatus: string + + @Field() + useStatus: string + + @Field() + useStatusName: string + + @Field() + useDate: string +} diff --git a/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.module.ts b/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.module.ts index 5f4511c1a420..ccee791fd703 100644 --- a/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.module.ts +++ b/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.module.ts @@ -5,10 +5,11 @@ import { RecyclingRequestModule } from '../recyclingRequest/recyclingRequest.mod import { SamgongustofaService } from './samgongustofa.service' import { SamgongustofaResolver } from './samgongustofa.resolver' +import { TransportService } from './transport/transport.service' @Module({ imports: [HttpModule, forwardRef(() => RecyclingRequestModule)], - providers: [SamgongustofaResolver, SamgongustofaService], + providers: [SamgongustofaResolver, SamgongustofaService, TransportService], exports: [SamgongustofaService], }) export class SamgongustofaModule {} diff --git a/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.resolver.ts b/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.resolver.ts index 2c87339b8508..5e343fc54532 100644 --- a/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.resolver.ts +++ b/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.resolver.ts @@ -1,6 +1,6 @@ -import { Query, Resolver } from '@nestjs/graphql' +import { Args, Query, Resolver } from '@nestjs/graphql' import { Authorize, CurrentUser, User } from '../auth' -import { VehicleInformation } from './samgongustofa.model' +import { Traffic, VehicleInformation } from './samgongustofa.model' import { SamgongustofaService } from './samgongustofa.service' @Authorize() @@ -14,4 +14,12 @@ export class SamgongustofaResolver { ): Promise> { return this.samgongustofaService.getUserVehiclesInformation(user.nationalId) } + + @Query(() => Traffic) + async skilavottordTraffic( + @CurrentUser() user: User, + @Args('permno') permno: string, + ): Promise { + return this.samgongustofaService.getTraffic(permno) + } } diff --git a/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.service.ts b/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.service.ts index 87610c34a5d3..58bd1fc96afd 100644 --- a/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.service.ts +++ b/apps/skilavottord/ws/src/app/modules/samgongustofa/samgongustofa.service.ts @@ -1,12 +1,16 @@ -import { Injectable, Inject, forwardRef } from '@nestjs/common' import { HttpService } from '@nestjs/axios' +import { forwardRef, Inject, Injectable } from '@nestjs/common' import { lastValueFrom } from 'rxjs' import * as xml2js from 'xml2js' import { environment } from '../../../environments' import { RecyclingRequestService } from '../recyclingRequest' -import { VehicleInformation } from './samgongustofa.model' +import { Traffic, VehicleInformation } from './samgongustofa.model' + import { logger } from '@island.is/logging' +import { TransportService } from './transport/transport.service' +import { ApiVersion } from '../../utils/const' +import { getShortPermno } from '../../utils/skilavottordUtils' @Injectable() export class SamgongustofaService { @@ -14,8 +18,15 @@ export class SamgongustofaService { private httpService: HttpService, @Inject(forwardRef(() => RecyclingRequestService)) private recyclingRequestService: RecyclingRequestService, + private transportService: TransportService, ) {} + /** + * + * @param nationalId + * @deprecated + * @returns + */ async getUserVehiclesInformation( nationalId: string, ): Promise { @@ -306,6 +317,9 @@ export class SamgongustofaService { } } + /** + * @deprecated + */ async getUserVehicle( nationalId: string, permno: string, @@ -321,4 +335,65 @@ export class SamgongustofaService { } return car } + + async getTraffic(permno: string): Promise { + try { + const url = this.transportService.getRegistrationURL() + + const result = await this.transportService.doGet( + url, + url + 'traffic/' + permno, + ApiVersion.REGISTRATIONS, + ) + + if (result.status === 200) { + // Get the latest registered traffic data + const traffic = Object.values(result.data).reduce( + (prev: Traffic, current: Traffic) => + new Date(prev.useDate) > new Date(current.useDate) ? prev : current, + {} as Traffic, + ) as Traffic + + logger.info( + `car-recycling: Got traffic data for ${getShortPermno(permno)}`, + { + outInStatus: traffic.outInStatus, + useStatus: traffic.useStatus, + useStatusName: traffic.useStatusName, + }, + ) + + return traffic + } + + throw new Error( + `car-recycling: #1 Failed on getTraffic ${getShortPermno(permno)}`, + ) + } catch (err) { + throw new Error( + `car-recycling: #2 Failed on getTraffic ${getShortPermno( + permno, + )} because: ${err}`, + ) + } + } + + // Get the Vehicle information from Icelandic Transport Authority (Samgöngustofa) + async getVehicleInformation(permno: string) { + try { + const url = this.transportService.getInformationURL() + + return this.transportService.doGet( + url, + url + 'vehicleinformationmini/' + permno, + ApiVersion.INFORMATION, + ) + } catch (err) { + throw new Error( + `car-recycling: Failed on getVehicleInformation vehicle ${permno.slice( + -3, + )} because: ${err}`, + ) + } + } } diff --git a/apps/skilavottord/ws/src/app/modules/samgongustofa/test/samgongustofa.service.spec.ts b/apps/skilavottord/ws/src/app/modules/samgongustofa/test/samgongustofa.service.spec.ts index 45ff1cf5d3b8..9f0cfc08a388 100644 --- a/apps/skilavottord/ws/src/app/modules/samgongustofa/test/samgongustofa.service.spec.ts +++ b/apps/skilavottord/ws/src/app/modules/samgongustofa/test/samgongustofa.service.spec.ts @@ -10,6 +10,7 @@ import { import { SamgongustofaService } from '../samgongustofa.service' import { MockData } from './mock-data' +import { TransportService } from '../transport/transport.service' const recyclingRequestModel = { id: '1234', @@ -50,6 +51,12 @@ describe('skilavottordApiTest', () => { findAllWithPermno: () => ({}), })), }, + { + provide: TransportService, + useClass: jest.fn(() => ({ + getRegistrationURL: () => ({}), + })), + }, ], }).compile() samgongustofaService = diff --git a/apps/skilavottord/ws/src/app/modules/samgongustofa/transport/index.ts b/apps/skilavottord/ws/src/app/modules/samgongustofa/transport/index.ts new file mode 100644 index 000000000000..8d7c134af4ac --- /dev/null +++ b/apps/skilavottord/ws/src/app/modules/samgongustofa/transport/index.ts @@ -0,0 +1 @@ +export { TransportService } from './transport.service' diff --git a/apps/skilavottord/ws/src/app/modules/samgongustofa/transport/transport.service.ts b/apps/skilavottord/ws/src/app/modules/samgongustofa/transport/transport.service.ts new file mode 100644 index 000000000000..5ec134cb39f0 --- /dev/null +++ b/apps/skilavottord/ws/src/app/modules/samgongustofa/transport/transport.service.ts @@ -0,0 +1,111 @@ +import { Injectable } from '@nestjs/common' +import { HttpService } from '@nestjs/axios' +import { lastValueFrom } from 'rxjs' +import { environment } from '../../../../environments' +import { logger } from '@island.is/logging' + +@Injectable() +export class TransportService { + constructor(private httpService: HttpService) {} + + async authenticate(url: string, apiVersion = '1.0'): Promise { + try { + const { restUsername, restPassword } = environment.samgongustofa + + if (!restUsername || !restPassword) { + throw new Error('Missing environment variables for Samgöngustofa') + } + + const jsonObj = { + username: restUsername, + password: restPassword, + } + const jsonAuthBody = JSON.stringify(jsonObj) + + const headerAuthRequest = { + 'Content-Type': 'application/json', + 'Api-version': apiVersion, + } + + const authRes = await lastValueFrom( + this.httpService.post(url + 'authenticate', jsonAuthBody, { + headers: headerAuthRequest, + }), + ) + + if (authRes.status > 299 || authRes.status < 200) { + const errorMessage = `Authentication failed to Samgöngustofa services: ${authRes.statusText}` + logger.error(`car-recycling: ${errorMessage}`) + throw new Error(errorMessage) + } + + return authRes.data['jwtToken'] + } catch (error) { + if (error?.config) { + error.config.data = undefined + } + + logger.error('car-recycling: Authentication failed', error) + throw error + } + } + + /** + * + * @param restURL - the base Samgöngustofa API url + * @param fullUrl - the full url to the Samgöngustofa API endpoint + * @param apiVersion - Samgöngustofa API version + * @returns + */ + async doGet(restURL: string, fullUrl: string, apiVersion: string) { + const jwtToken = await this.authenticate(restURL, apiVersion) + + const headers = { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + jwtToken, + 'Api-version': apiVersion, + } + + const result = await lastValueFrom( + this.httpService.get(fullUrl, { + headers: headers, + }), + ) + + if (result.status < 300 && result.status >= 200) { + return result + } else { + throw new Error( + `car-recycling: Failed on doGet with status: ${result.statusText}`, + ) + } + } + + /** + * Get the Samgöngustofa's registration REST url + * @returns + */ + getRegistrationURL(): string { + const { restDeRegUrl } = environment.samgongustofa + + const positionOfChar = restDeRegUrl.lastIndexOf('/') + return restDeRegUrl.substring(0, positionOfChar) + '/' + } + + /** + * Get the Samgöngustofa's information REST url + * @returns + */ + getInformationURL(): string { + const { restDeRegUrl } = environment.samgongustofa + + // + // Small hack to get the information url + const restInformationURL = restDeRegUrl.replace( + '/registrations/', + '/information/', + ) + const positionOfChar = restInformationURL.lastIndexOf('/') + return restInformationURL.substring(0, positionOfChar) + '/' + } +} diff --git a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts index fb9a15d3b634..ad6e8bc929f3 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.model.ts @@ -80,6 +80,18 @@ export class VehicleModel extends Model { @Field(() => [RecyclingRequestModel], { nullable: true }) @HasMany(() => RecyclingRequestModel) recyclingRequests!: RecyclingRequestModel[] + + @Field({ nullable: true }) + @Column({ + type: DataType.BOOLEAN, + }) + plateLost?: boolean + + @Field({ nullable: true }) + @Column({ + type: DataType.INTEGER, + }) + plateCount?: number } @ObjectType() diff --git a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.resolver.ts b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.resolver.ts index b52c8fd483c2..a5f2af14ec32 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.resolver.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.resolver.ts @@ -104,11 +104,18 @@ export class VehicleResolver { } @Mutation(() => Boolean) - async updateSkilavottordVehicleMileage( + async updateSkilavottordVehicleInfo( @CurrentUser() user: User, @Args('permno') permno: string, @Args('mileage') mileage: number, + @Args('plateCount') plateCount: number, + @Args('plateLost') plateLost: boolean, ) { - return await this.vehicleService.updateMileage(permno, mileage) + return await this.vehicleService.updateVehicleInfo( + permno, + mileage, + plateCount, + plateLost, + ) } } diff --git a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.service.ts b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.service.ts index ce62cce725bb..3be43b3a65b8 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.service.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicle/vehicle.service.ts @@ -60,18 +60,27 @@ export class VehicleService { } } - async updateMileage(permno: string, mileage: number): Promise { + async updateVehicleInfo( + permno: string, + mileage: number, + plateCount: number, + plateLost: boolean, + ): Promise { const findVehicle = await this.findByVehicleId(permno) if (findVehicle) { findVehicle.mileage = mileage ?? 0 + findVehicle.plateCount = plateCount ?? 0 + findVehicle.plateLost = plateLost + await findVehicle.save() return true } else { - const errorMsg = `failed to update mileage: ${mileage} on vehicle: ${permno}` + const errorMsg = `Failed to update vehicleInfo for vehicle: ${permno}` this.logger.error( - `car-recycling: Failed to update mileage: ${mileage} on vehicle: ${permno.slice( + `car-recycling: Failed to update vehicleInfo for vehicle: ${permno.slice( -3, )}`, + { mileage, plateCount, plateLost }, ) throw new Error(errorMsg) } diff --git a/apps/skilavottord/ws/src/app/modules/vehicle/vehicleAppSys.resolver.ts b/apps/skilavottord/ws/src/app/modules/vehicle/vehicleAppSys.resolver.ts index ddf2133b3f9c..d15060f93a12 100644 --- a/apps/skilavottord/ws/src/app/modules/vehicle/vehicleAppSys.resolver.ts +++ b/apps/skilavottord/ws/src/app/modules/vehicle/vehicleAppSys.resolver.ts @@ -8,6 +8,7 @@ import { logger } from '@island.is/logging' import { CreateVehicleInput } from './dto/createVehicle.input' import { VehicleModel } from './vehicle.model' import { VehicleService } from './vehicle.service' +import { getShortPermno } from '../../utils/skilavottordUtils' @UseGuards(IdsUserGuard, ScopesGuard) @Resolver(() => VehicleModel) @@ -19,9 +20,12 @@ export class VehicleAppSysResolver { @CurrentUser() user: User, @Args('input') input: CreateVehicleInput, ) { - logger.info(`car-recycling: Creating Vehicle ${input.permno.slice(-3)}`, { - mileage: input.mileage, - }) + logger.info( + `car-recycling: Creating Vehicle ${getShortPermno(input.permno)}`, + { + mileage: input.mileage, + }, + ) const newVehicle = new VehicleModel() newVehicle.vinNumber = input.vin diff --git a/apps/skilavottord/ws/src/app/services/icelandicTransportAuthority.module.ts b/apps/skilavottord/ws/src/app/services/icelandicTransportAuthority.module.ts index 8207430e01de..e5fecff38762 100644 --- a/apps/skilavottord/ws/src/app/services/icelandicTransportAuthority.module.ts +++ b/apps/skilavottord/ws/src/app/services/icelandicTransportAuthority.module.ts @@ -1,13 +1,18 @@ import { Module, forwardRef } from '@nestjs/common' import { IcelandicTransportAuthorityServices } from './icelandicTransportAuthority.services' import { HttpModule } from '@nestjs/axios' -import { VehicleModule, VehicleOwnerModule } from '../modules' +import { + SamgongustofaModule, + VehicleModule, + VehicleOwnerModule, +} from '../modules' @Module({ imports: [ HttpModule, forwardRef(() => VehicleModule), forwardRef(() => VehicleOwnerModule), + forwardRef(() => SamgongustofaModule), ], providers: [IcelandicTransportAuthorityServices], exports: [IcelandicTransportAuthorityServices], diff --git a/apps/skilavottord/ws/src/app/services/icelandicTransportAuthority.services.ts b/apps/skilavottord/ws/src/app/services/icelandicTransportAuthority.services.ts index d56f9873585e..24254b673c06 100644 --- a/apps/skilavottord/ws/src/app/services/icelandicTransportAuthority.services.ts +++ b/apps/skilavottord/ws/src/app/services/icelandicTransportAuthority.services.ts @@ -1,8 +1,7 @@ -import { Injectable } from '@nestjs/common/decorators/core' -import { lastValueFrom } from 'rxjs' -import { HttpService } from '@nestjs/axios/dist' -import { environment } from '../../environments' import { logger } from '@island.is/logging' +import { Injectable } from '@nestjs/common/decorators/core' +import { SamgongustofaService } from '../modules/samgongustofa' +import { VehicleInformationMini } from '../modules/samgongustofa/samgongustofa.model' import { VehicleModel, VehicleService } from '../modules/vehicle' import { VehicleOwnerModel } from '../modules/vehicleOwner' import { VehicleOwnerService } from '../modules/vehicleOwner/vehicleOwner.service' @@ -10,124 +9,14 @@ import { VehicleOwnerService } from '../modules/vehicleOwner/vehicleOwner.servic @Injectable() export class IcelandicTransportAuthorityServices { constructor( - private http: HttpService, private vehicleService: VehicleService, private ownerService: VehicleOwnerService, + private samgongustofaService: SamgongustofaService, ) {} - // Hack to re-use the url from the secret - private getInformationURL(restURL: string): string { - const restInformationURL = restURL.replace( - '/registrations/', - '/information/', - ) - const positionOfChar = restInformationURL.lastIndexOf('/') - return restInformationURL.substring(0, positionOfChar) + '/' - } - - async authenticate(authURL: string, userName: string, password: string) { - try { - const jsonObj = { - username: userName, - password: password, - } - const jsonAuthBody = JSON.stringify(jsonObj) - - const headerAuthRequest = { - 'Content-Type': 'application/json', - } - - const authRes = await lastValueFrom( - this.http.post( - this.getInformationURL(authURL) + 'authenticate', - jsonAuthBody, - { - headers: headerAuthRequest, - }, - ), - ) - - if (authRes.status > 299 || authRes.status < 200) { - const errorMessage = `Authentication failed to information services: ${authRes.statusText}` - logger.error(`car-recycling: ${errorMessage}`) - throw new Error(errorMessage) - } - - return authRes.data['jwtToken'] - } catch (error) { - if (error?.config) { - error.config.data = undefined - } - logger.error('car-recycling: Authentication failed on information', error) - throw error - } - } - - async doGet( - restURL: string, - queryParams: { [key: string]: string } | undefined, - ) { - const { restAuthUrl, restUsername, restPassword } = - environment.samgongustofa - - const jwtToken = await this.authenticate( - restAuthUrl, - restUsername, - restPassword, - ) - - let fullUrl = restURL - - if (queryParams) { - const searchParams = new URLSearchParams(queryParams) - - // Concatenate the URL with the query string - fullUrl = `${restURL}?${searchParams.toString()}` - } - - const headers = { - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + jwtToken, - } - - const result = await lastValueFrom( - this.http.get(fullUrl, { - headers: headers, - }), - ) - - if (result.status < 300 && result.status >= 200) { - return result - } else { - throw new Error( - `car-recycling: Failed on doGet with status: ${result.statusText}`, - ) - } - } - - // Get the Vehicle information from Icelandic Transport Authority (Samgöngustofa) - async getVehicleInformation(permno: string) { - try { - const { restDeRegUrl } = environment.samgongustofa - - return this.doGet( - this.getInformationURL(restDeRegUrl) + - 'vehicleinformationmini/' + - permno, - undefined, - ) - } catch (err) { - throw new Error( - `car-recycling: Failed on getVehicleInformation vehicle ${permno.slice( - -3, - )} because: ${err}`, - ) - } - } - - async checkIfCurrentUser(permno: string): Promise { + async checkIfCurrentUser(permno: string): Promise { //Get the latest vehicle information from Samgongustofa - const result = await this.getVehicleInformation(permno) + const result = await this.samgongustofaService.getVehicleInformation(permno) if (result && result.data) { const ownerSocialSecurityNumber = result.data.ownerSocialSecurityNumber @@ -159,7 +48,7 @@ export class IcelandicTransportAuthorityServices { await this.vehicleService.create(vehicle) } - return true + return result.data } logger.error( diff --git a/apps/skilavottord/ws/src/app/utils/const.ts b/apps/skilavottord/ws/src/app/utils/const.ts new file mode 100644 index 000000000000..b4b53f7da574 --- /dev/null +++ b/apps/skilavottord/ws/src/app/utils/const.ts @@ -0,0 +1,5 @@ +/** Samgöngustofa API versions */ +export enum ApiVersion { + REGISTRATIONS = '3.0', + INFORMATION = '1.0', +} diff --git a/apps/skilavottord/ws/src/app/utils/index.ts b/apps/skilavottord/ws/src/app/utils/index.ts new file mode 100644 index 000000000000..05d610c5e491 --- /dev/null +++ b/apps/skilavottord/ws/src/app/utils/index.ts @@ -0,0 +1,2 @@ +export * from './const' +export * from './skilavottordUtils' diff --git a/apps/skilavottord/ws/src/app/utils/skilavottordUtils.ts b/apps/skilavottord/ws/src/app/utils/skilavottordUtils.ts new file mode 100644 index 000000000000..77120b29d8e4 --- /dev/null +++ b/apps/skilavottord/ws/src/app/utils/skilavottordUtils.ts @@ -0,0 +1,8 @@ +/** + * Shorten the permno for logging purpose + * @param permno + * @returns + */ +export const getShortPermno = (permno: string) => { + return permno.slice(-3) +} From f885cca1ec07bdd218410e7a95cb5feff5646e15 Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:05:15 +0000 Subject: [PATCH 09/13] fix(driving-license): 65 renewal updates (#15946) * fix(driving-license): 65 renewal updates * adding home delivery - 65+ * tweaks * cleanup * cleanup --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/forms/draft/subSectionDelivery.ts | 31 +++++++++--- .../draft/subSectionHealthDeclaration.ts | 47 +++++-------------- .../src/forms/draft/subSectionQualityPhoto.ts | 1 - .../src/forms/draft/subSectionSummary.ts | 16 ++++++- .../driving-license/src/lib/constants.ts | 5 ++ .../src/lib/drivingLicenseTemplate.ts | 5 ++ .../driving-license/src/lib/messages.ts | 30 +++++++++++- 7 files changed, 88 insertions(+), 47 deletions(-) diff --git a/libs/application/templates/driving-license/src/forms/draft/subSectionDelivery.ts b/libs/application/templates/driving-license/src/forms/draft/subSectionDelivery.ts index a6295a5344c5..0e78b42f4298 100644 --- a/libs/application/templates/driving-license/src/forms/draft/subSectionDelivery.ts +++ b/libs/application/templates/driving-license/src/forms/draft/subSectionDelivery.ts @@ -1,6 +1,8 @@ import { buildDescriptionField, + buildHiddenInputWithWatchedValue, buildMultiField, + buildRadioField, buildSelectField, buildSubSection, } from '@island.is/application/core' @@ -11,6 +13,7 @@ import { } from '../../lib/utils' import { Jurisdiction } from '@island.is/clients/driving-license' +import { B_FULL_RENEWAL_65, Pickup } from '../../lib/constants' export const subSectionDelivery = buildSubSection({ id: 'user', @@ -20,19 +23,17 @@ export const subSectionDelivery = buildSubSection({ buildMultiField({ id: 'info', title: m.pickupLocationTitle, - space: 1, children: [ buildDescriptionField({ - id: 'afhending', - title: m.districtCommisionerTitle, - titleVariant: 'h4', + id: 'jurisdictionHeader', + title: '', description: chooseDistrictCommissionerDescription, }), buildSelectField({ id: 'jurisdiction', - title: m.districtCommisionerPickup, - disabled: false, + title: m.districtCommissionerPickup, required: true, + placeholder: m.districtCommissionerPickupPlaceholder, options: ({ externalData: { jurisdictions: { data }, @@ -45,6 +46,24 @@ export const subSectionDelivery = buildSubSection({ })) }, }), + buildDescriptionField({ + id: 'pickupHeader', + title: '', + description: m.pickupLocationHeader, + titleVariant: 'h4', + space: 'containerGutter', + condition: (answers) => answers.applicationFor === B_FULL_RENEWAL_65, + }), + buildRadioField({ + id: 'pickup', + title: '', + defaultValue: Pickup.POST, + condition: (answers) => answers.applicationFor === B_FULL_RENEWAL_65, + options: [ + { value: Pickup.POST, label: m.overviewPickupPost }, + { value: Pickup.DISTRICT, label: m.overviewPickupDistrict }, + ], + }), ], }), ], diff --git a/libs/application/templates/driving-license/src/forms/draft/subSectionHealthDeclaration.ts b/libs/application/templates/driving-license/src/forms/draft/subSectionHealthDeclaration.ts index d55ada083818..b6c27eee3e1c 100644 --- a/libs/application/templates/driving-license/src/forms/draft/subSectionHealthDeclaration.ts +++ b/libs/application/templates/driving-license/src/forms/draft/subSectionHealthDeclaration.ts @@ -3,19 +3,16 @@ import { buildCustomField, buildSubSection, buildAlertMessageField, - hasYes, buildDescriptionField, YES, } from '@island.is/application/core' -import { NationalRegistryUser } from '@island.is/api/schema' -import { info } from 'kennitala' import { m } from '../../lib/messages' import { hasNoDrivingLicenseInOtherCountry } from '../../lib/utils' import { hasHealthRemarks, needsHealthCertificateCondition, } from '../../lib/utils/formUtils' -import { BE } from '../../lib/constants' +import { BE, B_FULL_RENEWAL_65 } from '../../lib/constants' export const subSectionHealthDeclaration = buildSubSection({ id: 'healthDeclaration', @@ -25,22 +22,15 @@ export const subSectionHealthDeclaration = buildSubSection({ buildMultiField({ id: 'overview', title: m.healthDeclarationMultiFieldTitle, - description: m.healthDeclarationSubTitle, + condition: (answers) => answers.applicationFor !== B_FULL_RENEWAL_65, space: 2, - condition: (answers, externalData) => { - if ((answers.fakeData as any).age) { - return (answers.fakeData as any).age < 65 - } - - return ( - !hasYes(answers?.drivingLicenseInOtherCountry) && - info( - (externalData.nationalRegistry.data as NationalRegistryUser) - .nationalId, - ).age < 65 - ) - }, children: [ + buildDescriptionField({ + id: 'healthDeclarationDescription', + title: '', + description: m.healthDeclarationSubTitle, + marginBottom: 2, + }), buildCustomField({ id: 'remarks', title: '', @@ -173,30 +163,15 @@ export const subSectionHealthDeclaration = buildSubSection({ }), ], }), - /* Different set of the Health Declaration screen for people over the age of 65 */ buildMultiField({ id: 'healthDeclarationAge65', title: m.healthDeclarationMultiFieldTitle, - description: m.healthDeclarationAge65MultiFieldSubTitle, - space: 1, - condition: (answers, externalData) => { - if ((answers.fakeData as any).age) { - return (answers.fakeData as any).age >= 65 - } - - return ( - !hasYes(answers?.drivingLicenseInOtherCountry) && - info( - (externalData.nationalRegistry.data as NationalRegistryUser) - .nationalId, - ).age >= 65 - ) - }, + condition: (answers) => answers.applicationFor === B_FULL_RENEWAL_65, children: [ buildDescriptionField({ - id: 'healthDeclarationAge65Description', + id: 'healthDeclarationDescription65', title: '', - description: 'Þetta view er í vinnslu', + description: m.healthDeclarationMultiField65Description, }), ], }), diff --git a/libs/application/templates/driving-license/src/forms/draft/subSectionQualityPhoto.ts b/libs/application/templates/driving-license/src/forms/draft/subSectionQualityPhoto.ts index 214f3d8ecc0f..0b598d74a8e9 100644 --- a/libs/application/templates/driving-license/src/forms/draft/subSectionQualityPhoto.ts +++ b/libs/application/templates/driving-license/src/forms/draft/subSectionQualityPhoto.ts @@ -5,7 +5,6 @@ import { buildRadioField, buildSubSection, getValueViaPath, - hasYes, buildDescriptionField, } from '@island.is/application/core' import { m } from '../../lib/messages' diff --git a/libs/application/templates/driving-license/src/forms/draft/subSectionSummary.ts b/libs/application/templates/driving-license/src/forms/draft/subSectionSummary.ts index 845d00cde105..8f6485980beb 100644 --- a/libs/application/templates/driving-license/src/forms/draft/subSectionSummary.ts +++ b/libs/application/templates/driving-license/src/forms/draft/subSectionSummary.ts @@ -13,7 +13,7 @@ import { NationalRegistryUser, TeacherV4 } from '../../types/schema' import { m } from '../../lib/messages' import { format as formatKennitala } from 'kennitala' import { StudentAssessment } from '@island.is/api/schema' -import { B_TEMP, BE, YES } from '../../lib/constants' +import { B_FULL_RENEWAL_65, B_TEMP, BE, Pickup, YES } from '../../lib/constants' import { hasNoDrivingLicenseInOtherCountry, isApplicationForCondition, @@ -23,7 +23,7 @@ import { formatPhoneNumber } from '@island.is/application/ui-components' export const subSectionSummary = buildSubSection({ id: 'overview', - title: m.overviewSectionTitle, + title: m.overviewMultiFieldTitle, condition: hasNoDrivingLicenseInOtherCountry, children: [ buildMultiField({ @@ -52,6 +52,8 @@ export const subSectionSummary = buildSubSection({ ? m.applicationForTempLicenseTitle : applicationFor === BE ? m.applicationForBELicenseTitle + : applicationFor === B_FULL_RENEWAL_65 + ? m.applicationForRenewalLicenseTitle : m.applicationForFullLicenseTitle, }), buildDividerField({}), @@ -145,6 +147,16 @@ export const subSectionSummary = buildSubSection({ condition: needsHealthCertificateCondition(YES), }), buildDividerField({}), + buildKeyValueField({ + label: m.pickupLocationTitle, + value: ({ answers }) => { + return answers.pickup === Pickup.POST + ? m.overviewPickupPost + : m.overviewPickupDistrict + }, + width: 'full', + }), + buildDividerField({}), buildKeyValueField({ label: m.overviewPaymentCharge, value: ({ externalData, answers }) => { diff --git a/libs/application/templates/driving-license/src/lib/constants.ts b/libs/application/templates/driving-license/src/lib/constants.ts index 9e10791fd19d..9bab845f6746 100644 --- a/libs/application/templates/driving-license/src/lib/constants.ts +++ b/libs/application/templates/driving-license/src/lib/constants.ts @@ -10,6 +10,11 @@ export const B_TEMP = 'B-temp' export const B_FULL_RENEWAL_65 = 'B-full-renewal-65' export const BE = 'BE' +export enum Pickup { + 'POST' = 'post', + 'DISTRICT' = 'district', +} + export const otherLicenseCategories = ['C', 'C1', 'CE', 'D', 'D1', 'DE'] export const codesRequiringHealthCertificate = ['400', '01.06'] diff --git a/libs/application/templates/driving-license/src/lib/drivingLicenseTemplate.ts b/libs/application/templates/driving-license/src/lib/drivingLicenseTemplate.ts index 5f773c52b519..1f13dbff725b 100644 --- a/libs/application/templates/driving-license/src/lib/drivingLicenseTemplate.ts +++ b/libs/application/templates/driving-license/src/lib/drivingLicenseTemplate.ts @@ -31,6 +31,7 @@ import { BE, B_TEMP, B_FULL, + B_FULL_RENEWAL_65, ApiActions, } from './constants' import { dataSchema } from './dataSchema' @@ -83,6 +84,10 @@ const template: ApplicationTemplate< ? m.applicationForDrivingLicense.defaultMessage + ' - ' + m.applicationForFullLicenseTitle.defaultMessage + : application.answers.applicationFor === B_FULL_RENEWAL_65 + ? m.applicationForDrivingLicense.defaultMessage + + ' - ' + + m.applicationForRenewalLicenseTitle.defaultMessage : m.applicationForDrivingLicense.defaultMessage, institution: m.nationalCommissionerOfPolice, dataSchema, diff --git a/libs/application/templates/driving-license/src/lib/messages.ts b/libs/application/templates/driving-license/src/lib/messages.ts index d487aa4eb58d..41f59615efb6 100644 --- a/libs/application/templates/driving-license/src/lib/messages.ts +++ b/libs/application/templates/driving-license/src/lib/messages.ts @@ -106,10 +106,15 @@ export const m = defineMessages({ description: 'Information', }, pickupLocationTitle: { - id: 'dl.application:pickuplocation', + id: 'dl.application:pickupLocationTitle', defaultMessage: 'Afhendingarstaður', description: 'location for pickup', }, + pickupLocationHeader: { + id: 'dl.application:pickupLocationHeader', + defaultMessage: 'Hvar viltu sækja/fá ökuskírteinið?', + description: 'Where do you want to pick up your driving license?', + }, informationApplicant: { id: 'dl.application:information.applicant', defaultMessage: 'Umsækjandi', @@ -125,6 +130,12 @@ export const m = defineMessages({ defaultMessage: 'Heilbrigðisyfirlýsing', description: 'Health declaration', }, + healthDeclarationMultiField65Description: { + id: 'dl.application:healthDeclarationMultiField65Description#markdown', + defaultMessage: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nunc nec ultricies ultricies, nunc nisl ultricies nunc, nec ultricies nunc nisl nec nunc. Nullam auctor, nunc nec ultricies ultricies, nunc nisl ultricies nunc, nec ultricies nunc nisl nec nunc.', + description: 'Health declaration', + }, healthDeclarationMultiFieldSubTitle: { id: 'dl.application:healthDeclarationMultiField.subTitle', defaultMessage: 'Yfirlýsing um líkamlegt og andlegt heilbrigði', @@ -346,6 +357,16 @@ export const m = defineMessages({ defaultMessage: 'Ég kem með vottorð frá lækni meðferðis', description: `I'll bring a certificate from a doctor`, }, + overviewPickupPost: { + id: 'dl.application:overview.pickupPost', + defaultMessage: 'Sent heim í pósti', + description: 'By mail', + }, + overviewPickupDistrict: { + id: 'dl.application:overview.pickupDistrict', + defaultMessage: 'Sækja á afhendingarstað', + description: 'Pickup location', + }, applicationDone: { id: 'dl.application:overview.done', defaultMessage: 'Umsókn móttekin', @@ -604,11 +625,16 @@ export const m = defineMessages({ defaultMessage: 'Sýslumannsembætti', description: 'Title for district commissioner', }, - districtCommisionerPickup: { + districtCommissionerPickup: { id: 'dl.application:districtCommisionerPickup', defaultMessage: 'Afhending', description: 'Pickup for district commissioner', }, + districtCommissionerPickupPlaceholder: { + id: 'dl.application:districtCommisionerPickupPlaceholder', + defaultMessage: 'Veldu sýslumannsembætti', + description: 'Choose district commissioner', + }, chooseDistrictCommisionerForFullLicense: { id: 'dl.application:chooseDistrictCommisionerForFullLicense', defaultMessage: From 0df72d2368fa1603eed64edf0e4db1dadddb754b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eir=C3=ADkur=20Hei=C3=B0ar=20Nilsson?= Date: Wed, 11 Sep 2024 16:21:35 +0000 Subject: [PATCH 10/13] feat: Configure universal links for the test app (#15963) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .github/CODEOWNERS | 1 + .../.well-known/apple-app-site-association | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0f1602b5aadb..9727df0d2bf6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -109,6 +109,7 @@ libs/clients/rsk/relationships/ /libs/service-portal/sessions @island-is/aranja /apps/native/app/ @island-is/aranja-app codemagic.yaml @island-is/aranja-app +/apps/web/public/.well-known/ @island-is/aranja-app /apps/judicial-system/ @island-is/kolibri-justice-league /libs/judicial-system/ @island-is/kolibri-justice-league diff --git a/apps/web/public/.well-known/apple-app-site-association b/apps/web/public/.well-known/apple-app-site-association index aee27712cdc5..fd6c7aeccefd 100644 --- a/apps/web/public/.well-known/apple-app-site-association +++ b/apps/web/public/.well-known/apple-app-site-association @@ -1,5 +1,24 @@ { - "applinks": {}, + "applinks": { + "details": [ + { + "appIDs": [ + "J3WWZR9JLF.is.island.app", + "J3WWZR9JLF.is.island.app.dev" + ], + "components": [ + { "/": "/minarsidur/postholf" }, + { "/": "/minarsidur/postholf/*" }, + { "/": "/minarsidur/skirteini" }, + { "/": "/minarsidur/eignir/fasteignir" }, + { "/": "/minarsidur/eignir/fasteignir/*" }, + { "/": "/minarsidur/eignir/okutaeki/min-okutaeki" }, + { "/": "/minarsidur/eignir/okutaeki/min-okutaeki/*" }, + { "/": "/minarsidur/loftbru" } + ] + } + ] + }, "webcredentials": { "apps": [ "J3WWZR9JLF.is.island.app", From 8eb83b68cad578f2497365dcad95832979301e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rarinn=20Gunnar=20=C3=81rnason?= Date: Wed, 11 Sep 2024 16:59:19 +0000 Subject: [PATCH 11/13] chore(app-sys): Convert conventional functions to arrow functions (#15966) * Update fieldBuilders.ts * Update conditionUtils.ts * Update formBuilders.ts * Update README.md * Update formUtils.ts * Update validators.ts * application/api * template-api-modules * template-loader * templates * lib/application/types * libs/application/ui-components * libs/application/ui-shell * libs/application/utils * apps/application-system --- .../application/utils/delegationUtils.ts | 4 +- .../modules/application/utils/tokenUtils.ts | 2 +- .../form/src/utils/index.ts | 4 +- .../api/files/src/lib/files.module.ts | 2 +- .../src/lib/pdfGenerators/residenceChange.ts | 4 +- .../api/payment/src/lib/types/Charge.ts | 4 +- libs/application/core/README.md | 4 +- .../core/src/lib/conditionUtils.ts | 8 +- .../application/core/src/lib/fieldBuilders.ts | 112 +++++++++--------- libs/application/core/src/lib/formBuilders.ts | 22 ++-- libs/application/core/src/lib/formUtils.ts | 36 +++--- .../core/src/validation/validators.ts | 8 +- .../templates/residenceChange.ts | 4 +- .../children-residence-change-v2/utils.ts | 2 +- .../pdfGenerators/templates/complaint.ts | 2 +- .../pdfGenerators/pdfGenerator.ts | 4 +- .../pdfGenerators/templates/complaintPdf.ts | 24 ++-- .../health-insurance/bucket/bucket.service.ts | 2 +- libs/application/template-loader/src/index.ts | 5 +- .../src/forms/ContractRejected.ts | 2 +- .../src/fields/ExampleCountryField.tsx | 2 +- .../application/types/src/lib/StateMachine.ts | 4 +- .../types/src/lib/application-types.ts | 2 +- .../utilities/FileUploadController/index.tsx | 2 +- .../src/utilities/handleServerError.ts | 4 +- .../src/components/DelegationsScreen.tsx | 2 +- .../ui-shell/src/components/FormRepeater.tsx | 4 +- .../ui-shell/src/components/ScreenFooter.tsx | 2 +- .../ui-shell/src/hooks/useDocumentTitle.ts | 5 +- .../ui-shell/src/hooks/useIsWindowSize.ts | 2 +- .../ui-shell/src/lib/ApplicationForm.tsx | 2 +- .../src/reducer/ApplicationFormReducer.ts | 10 +- .../ui-shell/src/reducer/reducerUtils.ts | 37 +++--- libs/application/ui-shell/src/utils.ts | 20 ++-- .../src/lib/builders/paymentStateBuilder.ts | 4 +- 35 files changed, 184 insertions(+), 173 deletions(-) diff --git a/apps/application-system/api/src/app/modules/application/utils/delegationUtils.ts b/apps/application-system/api/src/app/modules/application/utils/delegationUtils.ts index 015f6b99d4ae..76bfa4b0cd2e 100644 --- a/apps/application-system/api/src/app/modules/application/utils/delegationUtils.ts +++ b/apps/application-system/api/src/app/modules/application/utils/delegationUtils.ts @@ -1,10 +1,10 @@ import { User } from '@island.is/auth-nest-tools' import { Application } from '@island.is/application/types' -export function isNewActor( +export const isNewActor = ( application: Pick, user: User, -) { +) => { if (!user.actor) { return false } else if ( diff --git a/apps/application-system/api/src/app/modules/application/utils/tokenUtils.ts b/apps/application-system/api/src/app/modules/application/utils/tokenUtils.ts index fb8a6a1b8fdc..1f0bafa4a236 100644 --- a/apps/application-system/api/src/app/modules/application/utils/tokenUtils.ts +++ b/apps/application-system/api/src/app/modules/application/utils/tokenUtils.ts @@ -2,7 +2,7 @@ import jwt from 'jsonwebtoken' import { environment } from '../../../../environments' -export function verifyToken(token: string): T | null { +export const verifyToken = (token: string): T | null => { try { const decoded = jwt.verify( token, diff --git a/apps/application-system/form/src/utils/index.ts b/apps/application-system/form/src/utils/index.ts index 3acbefe05485..b16bdc57c94f 100644 --- a/apps/application-system/form/src/utils/index.ts +++ b/apps/application-system/form/src/utils/index.ts @@ -10,8 +10,8 @@ * - https://github.com/airbnb/lottie-web/issues/360#issuecomment-320243980 * */ -export function fixSvgUrls(_baseUrl: string) { - function fixForAttribute(attrib: string) { +export const fixSvgUrls = (_baseUrl: string) => { + const fixForAttribute = (attrib: string) => { const baseUrl = window.location.href /** diff --git a/libs/application/api/files/src/lib/files.module.ts b/libs/application/api/files/src/lib/files.module.ts index de597a52fbbe..c02b031a3ad3 100644 --- a/libs/application/api/files/src/lib/files.module.ts +++ b/libs/application/api/files/src/lib/files.module.ts @@ -25,7 +25,7 @@ import { ApplicationApiCoreModule } from '@island.is/application/api/core' }) export class ApplicationFilesModule {} -export function createBullModule() { +export const createBullModule = () => { if (process.env.INIT_SCHEMA === 'true') { return NestBullModule.registerQueueAsync() } else { diff --git a/libs/application/api/files/src/lib/pdfGenerators/residenceChange.ts b/libs/application/api/files/src/lib/pdfGenerators/residenceChange.ts index 9d064c55750f..a580ac0f98c2 100644 --- a/libs/application/api/files/src/lib/pdfGenerators/residenceChange.ts +++ b/libs/application/api/files/src/lib/pdfGenerators/residenceChange.ts @@ -24,7 +24,9 @@ import { addLegalEffect } from './familyMatters' const TEMPORARY = 'temporary' -export async function generateResidenceChangePdf(application: CRCApplication) { +export const generateResidenceChangePdf = async ( + application: CRCApplication, +) => { const { answers, externalData: { nationalRegistry }, diff --git a/libs/application/api/payment/src/lib/types/Charge.ts b/libs/application/api/payment/src/lib/types/Charge.ts index c3b7d9a6b46e..0e2dab15298b 100644 --- a/libs/application/api/payment/src/lib/types/Charge.ts +++ b/libs/application/api/payment/src/lib/types/Charge.ts @@ -2,13 +2,13 @@ import { User } from '@island.is/auth-nest-tools' import { Charge, ExtraData } from '@island.is/clients/charge-fjs-v2' import { Payment } from '../payment.model' -export function formatCharge( +export const formatCharge = ( payment: Payment, callbackBaseUrl: string, callbackAdditionUrl: string, extraData: ExtraData[] | undefined, user: User, -): Charge { +): Charge => { // TODO: island.is x-road service path for callback.. ?? // this can actually be a fixed url const callbackUrl = diff --git a/libs/application/core/README.md b/libs/application/core/README.md index e17d5f5b337c..832dd87a7852 100644 --- a/libs/application/core/README.md +++ b/libs/application/core/README.md @@ -405,9 +405,9 @@ export interface NewField extends BaseField { 4. Create a new function in `libs/application/core/src/lib/fieldBuilders.ts`. This function accepts a parameter of the new type we created, `NewField`, but we have to omit `type`, `component` and `children`. Then add the props as follows. ```typescript -export function buildNewField( +export const buildNewField = ( data: Omit, -): NewField { +): NewField => { const { myProp, myOtherProp } = data return { ...extractCommonFields(data), diff --git a/libs/application/core/src/lib/conditionUtils.ts b/libs/application/core/src/lib/conditionUtils.ts index f8b26d4c0440..a326cff89b6a 100644 --- a/libs/application/core/src/lib/conditionUtils.ts +++ b/libs/application/core/src/lib/conditionUtils.ts @@ -11,12 +11,12 @@ import { import { getValueViaPath } from './formUtils' import { User } from '@island.is/shared/types' -function applyStaticConditionalCheck( +const applyStaticConditionalCheck = ( formValue: FormValue, externalData: ExternalData, check: StaticCheck, user: User | null, -): boolean { +): boolean => { const { value, questionId, comparator, externalDataId, userPropId } = check let isValid = false let valueViaPath @@ -68,12 +68,12 @@ function applyStaticConditionalCheck( return isValid } -export function shouldShowFormItem( +export const shouldShowFormItem = ( formItem: FormItem, formValue: FormValue, externalData: ExternalData = {}, user: User | null, -): boolean { +): boolean => { const { condition } = formItem if (!condition) { return true diff --git a/libs/application/core/src/lib/fieldBuilders.ts b/libs/application/core/src/lib/fieldBuilders.ts index 049cba433e2f..fef014754762 100644 --- a/libs/application/core/src/lib/fieldBuilders.ts +++ b/libs/application/core/src/lib/fieldBuilders.ts @@ -82,9 +82,9 @@ const extractCommonFields = ( } } -export function buildCheckboxField( +export const buildCheckboxField = ( data: Omit, -): CheckboxField { +): CheckboxField => { const { options, strong = false, @@ -107,9 +107,9 @@ export function buildCheckboxField( } } -export function buildDateField( +export const buildDateField = ( data: Omit, -): DateField { +): DateField => { const { maxDate, minDate, @@ -134,9 +134,9 @@ export function buildDateField( } } -export function buildDescriptionField( +export const buildDescriptionField = ( data: Omit, -): DescriptionField { +): DescriptionField => { const { titleVariant = 'h2', description, @@ -163,9 +163,9 @@ export function buildDescriptionField( } } -export function buildRadioField( +export const buildRadioField = ( data: Omit, -): RadioField { +): RadioField => { const { options, largeButtons = true, @@ -191,9 +191,9 @@ export function buildRadioField( } } -export function buildSelectField( +export const buildSelectField = ( data: Omit, -): SelectField { +): SelectField => { const { options, placeholder, @@ -216,9 +216,9 @@ export function buildSelectField( } } -export function buildAsyncSelectField( +export const buildAsyncSelectField = ( data: Omit, -): AsyncSelectField { +): AsyncSelectField => { const { loadOptions, loadingError, @@ -244,9 +244,9 @@ export function buildAsyncSelectField( } } -export function buildCompanySearchField( +export const buildCompanySearchField = ( data: Omit, -): CompanySearchField { +): CompanySearchField => { const { placeholder, shouldIncludeIsatNumber, @@ -266,9 +266,9 @@ export function buildCompanySearchField( } } -export function buildTextField( +export const buildTextField = ( data: Omit, -): TextField { +): TextField => { const { backgroundColor = 'blue', placeholder, @@ -305,9 +305,9 @@ export function buildTextField( } } -export function buildPhoneField( +export const buildPhoneField = ( data: Omit, -): PhoneField { +): PhoneField => { const { backgroundColor = 'blue', placeholder, @@ -332,10 +332,10 @@ export function buildPhoneField( } } -export function buildCustomField( +export const buildCustomField = ( data: Omit, props?: RecordObject, -): CustomField { +): CustomField => { const { component, childInputIds } = data return { ...extractCommonFields(data), @@ -347,9 +347,9 @@ export function buildCustomField( } } -export function buildFileUploadField( +export const buildFileUploadField = ( data: Omit, -): FileUploadField { +): FileUploadField => { const { introduction, uploadHeader, @@ -385,11 +385,11 @@ export function buildFileUploadField( } } -export function buildDividerField(data: { +export const buildDividerField = (data: { condition?: Condition title?: FormText color?: Colors -}): DividerField { +}): DividerField => { const { title, color, condition } = data return { id: '', @@ -403,7 +403,7 @@ export function buildDividerField(data: { } } -export function buildKeyValueField(data: { +export const buildKeyValueField = (data: { label: FormText value: FormText | FormTextArray width?: FieldWidth @@ -414,7 +414,7 @@ export function buildKeyValueField(data: { paddingX?: BoxProps['padding'] paddingY?: BoxProps['padding'] paddingBottom?: BoxProps['padding'] -}): KeyValueField { +}): KeyValueField => { const { label, value, @@ -448,13 +448,13 @@ export function buildKeyValueField(data: { } } -export function buildSubmitField(data: { +export const buildSubmitField = (data: { id: string title: FormText placement?: 'footer' | 'screen' refetchApplicationAfterSubmit?: boolean actions: CallToAction[] -}): SubmitField { +}): SubmitField => { const { id, placement = 'footer', @@ -478,11 +478,11 @@ export function buildSubmitField(data: { } } -export function buildFieldOptions( +export const buildFieldOptions = ( maybeOptions: MaybeWithApplicationAndField, application: Application, field: Field, -): Option[] { +): Option[] => { if (typeof maybeOptions === 'function') { return maybeOptions(application, field) } @@ -490,10 +490,10 @@ export function buildFieldOptions( return maybeOptions } -export function buildRedirectToServicePortalField(data: { +export const buildRedirectToServicePortalField = (data: { id: string title: FormText -}): RedirectToServicePortalField { +}): RedirectToServicePortalField => { const { id, title } = data return { children: undefined, @@ -504,10 +504,10 @@ export function buildRedirectToServicePortalField(data: { } } -export function buildPaymentPendingField(data: { +export const buildPaymentPendingField = (data: { id: string title: FormText -}): PaymentPendingField { +}): PaymentPendingField => { const { id, title } = data return { children: undefined, @@ -518,9 +518,9 @@ export function buildPaymentPendingField(data: { } } -export function buildMessageWithLinkButtonField( +export const buildMessageWithLinkButtonField = ( data: Omit, -): MessageWithLinkButtonField { +): MessageWithLinkButtonField => { const { id, title, url, message, buttonTitle, marginBottom, marginTop } = data return { children: undefined, @@ -536,9 +536,9 @@ export function buildMessageWithLinkButtonField( } } -export function buildExpandableDescriptionField( +export const buildExpandableDescriptionField = ( data: Omit, -): ExpandableDescriptionField { +): ExpandableDescriptionField => { const { id, title, description, introText, startExpanded } = data return { children: undefined, @@ -551,9 +551,9 @@ export function buildExpandableDescriptionField( component: FieldComponents.EXPANDABLE_DESCRIPTION, } } -export function buildAlertMessageField( +export const buildAlertMessageField = ( data: Omit, -): AlertMessageField { +): AlertMessageField => { const { message, alertType, marginTop, marginBottom, links } = data return { ...extractCommonFields(data), @@ -568,9 +568,9 @@ export function buildAlertMessageField( } } -export function buildLinkField( +export const buildLinkField = ( data: Omit, -): LinkField { +): LinkField => { const { s3key, link, iconProps } = data return { ...extractCommonFields(data), @@ -583,9 +583,9 @@ export function buildLinkField( } } -export function buildPaymentChargeOverviewField( +export const buildPaymentChargeOverviewField = ( data: Omit, -): PaymentChargeOverviewField { +): PaymentChargeOverviewField => { const { id, title, forPaymentLabel, totalLabel, getSelectedChargeItems } = data return { @@ -600,9 +600,9 @@ export function buildPaymentChargeOverviewField( } } -export function buildImageField( +export const buildImageField = ( data: Omit, -): ImageField { +): ImageField => { const { id, title, @@ -633,9 +633,9 @@ export function buildImageField( } } -export function buildPdfLinkButtonField( +export const buildPdfLinkButtonField = ( data: Omit, -): PdfLinkButtonField { +): PdfLinkButtonField => { const { verificationDescription, verificationLinkTitle, @@ -714,9 +714,9 @@ export const buildHiddenInput = ( } } -export function buildNationalIdWithNameField( +export const buildNationalIdWithNameField = ( data: Omit, -): NationalIdWithNameField { +): NationalIdWithNameField => { const { disabled, required, @@ -747,9 +747,9 @@ export function buildNationalIdWithNameField( } } -export function buildActionCardListField( +export const buildActionCardListField = ( data: Omit, -): ActionCardListField { +): ActionCardListField => { const { items, space, marginTop, marginBottom } = data return { @@ -764,9 +764,9 @@ export function buildActionCardListField( } } -export function buildTableRepeaterField( +export const buildTableRepeaterField = ( data: Omit, -): TableRepeaterField { +): TableRepeaterField => { const { fields, table, @@ -804,7 +804,7 @@ export function buildTableRepeaterField( } } -export function buildStaticTableField( +export const buildStaticTableField = ( data: Omit< StaticTableField, | 'type' @@ -817,7 +817,7 @@ export function buildStaticTableField( | 'disabled' | 'width' >, -): StaticTableField { +): StaticTableField => { const { header, condition, diff --git a/libs/application/core/src/lib/formBuilders.ts b/libs/application/core/src/lib/formBuilders.ts index 65360368d31d..1334684288a3 100644 --- a/libs/application/core/src/lib/formBuilders.ts +++ b/libs/application/core/src/lib/formBuilders.ts @@ -11,29 +11,29 @@ import { DataProviderBuilderItem, } from '@island.is/application/types' -export function buildForm(data: Omit): Form { +export const buildForm = (data: Omit): Form => { return { ...data, type: FormItemTypes.FORM } } -export function buildMultiField(data: Omit): MultiField { +export const buildMultiField = (data: Omit): MultiField => { return { ...data, type: FormItemTypes.MULTI_FIELD } } -export function buildRepeater(data: Omit): Repeater { +export const buildRepeater = (data: Omit): Repeater => { return { ...data, type: FormItemTypes.REPEATER } } -export function buildSection(data: Omit): Section { +export const buildSection = (data: Omit): Section => { return { ...data, type: FormItemTypes.SECTION } } -export function buildSubSection(data: Omit): SubSection { +export const buildSubSection = (data: Omit): SubSection => { return { ...data, type: FormItemTypes.SUB_SECTION } } -export function buildExternalDataProvider( +export const buildExternalDataProvider = ( data: Omit, -): ExternalDataProvider { +): ExternalDataProvider => { return { ...data, isPartOfRepeater: false, @@ -42,9 +42,9 @@ export function buildExternalDataProvider( } } -export function buildDataProviderItem( +export const buildDataProviderItem = ( data: DataProviderBuilderItem, -): DataProviderItem { +): DataProviderItem => { return { id: data.provider?.externalDataId ?? data.provider?.action ?? '', action: data.provider?.actionId, @@ -56,8 +56,8 @@ export function buildDataProviderItem( } } -export function buildDataProviderPermissionItem( +export const buildDataProviderPermissionItem = ( data: DataProviderPermissionItem, -): DataProviderPermissionItem { +): DataProviderPermissionItem => { return data } diff --git a/libs/application/core/src/lib/formUtils.ts b/libs/application/core/src/lib/formUtils.ts index 6055233140ce..f4586e39971d 100644 --- a/libs/application/core/src/lib/formUtils.ts +++ b/libs/application/core/src/lib/formUtils.ts @@ -39,15 +39,15 @@ const containsArray = (obj: RecordObject) => { return contains } -export function getErrorViaPath(obj: RecordObject, path: string): string { +export const getErrorViaPath = (obj: RecordObject, path: string): string => { return get(obj, path) as string } -export function getValueViaPath( +export const getValueViaPath = ( obj: RecordObject, path: string, defaultValue?: T, -): T | undefined { +): T | undefined => { // Errors from dataSchema with array of object looks like e.g. `{ 'periods[1].startDate': 'error message' }` if (path.match(/.\[\d\]\../g) && !containsArray(obj)) { return (obj?.[path] ?? defaultValue) as T @@ -111,12 +111,12 @@ export const getFormNodeLeaves = (node: FormNode): FormLeaf[] => { return leaves } -export function getSectionsInForm( +export const getSectionsInForm = ( form: Form, answers: FormValue, externalData: ExternalData, user: User | null, -): Section[] { +): Section[] => { const sections: Section[] = [] form.children.forEach((child) => { const shouldShowSection = shouldShowFormItem( @@ -132,12 +132,12 @@ export function getSectionsInForm( return sections } -export function getSubSectionsInSection( +export const getSubSectionsInSection = ( section: Section, answers: FormValue, externalData: ExternalData, user: User | null, -): SubSection[] { +): SubSection[] => { const subSections: SubSection[] = [] section?.children.forEach((child) => { const shouldShowSection = shouldShowFormItem( @@ -153,10 +153,10 @@ export function getSubSectionsInSection( return subSections } -export function findSectionIndex( +export const findSectionIndex = ( sections: Section[], section: Section, -): number { +): number => { if (!sections.length) { return -1 } @@ -168,10 +168,10 @@ export function findSectionIndex( return -1 } -export function findSubSectionIndex( +export const findSubSectionIndex = ( subSections: SubSection[], subSection: SubSection, -): number { +): number => { for (let i = 0; i < subSections.length; i++) { if (subSections[i].id === subSection.id) { return i @@ -215,10 +215,10 @@ const overwriteArrayMerge = ( return destination } -export function mergeAnswers( +export const mergeAnswers = ( currentAnswers: RecordObject, newAnswers: RecordObject, -): FormValue { +): FormValue => { return deepmerge(currentAnswers, newAnswers, { arrayMerge: overwriteArrayMerge, }) @@ -242,11 +242,11 @@ const handleMessageFormatting = ( return formatMessage(descriptor, values) } -export function formatText( +export const formatText = ( text: T, application: Application, formatMessage: MessageFormatter, -): T extends FormTextArray ? string[] : string { +): T extends FormTextArray ? string[] : string => { if (typeof text === 'function') { const message = (text as (_: Application) => StaticText | StaticText[])( application, @@ -278,18 +278,18 @@ export function formatText( return formatMessage(text) as T extends FormTextArray ? string[] : string } -export function formatAndParseAsHTML( +export const formatAndParseAsHTML = ( text: FormText, application: Application, formatMessage: MessageFormatter, -) { +) => { return HtmlParser(formatText(text, application, formatMessage)) } // periods[3].startDate -> 3 // notPartOfRepeater -> -1 // periods[5ab33f1].id -> -1 -export function extractRepeaterIndexFromField(field: Field): number { +export const extractRepeaterIndexFromField = (field: Field): number => { if (!field?.isPartOfRepeater) { return -1 } diff --git a/libs/application/core/src/validation/validators.ts b/libs/application/core/src/validation/validators.ts index 1956f6225e50..8d347ccd7766 100644 --- a/libs/application/core/src/validation/validators.ts +++ b/libs/application/core/src/validation/validators.ts @@ -14,11 +14,11 @@ import { import { coreErrorMessages } from '../lib/messages' import { AnswerValidationError } from './AnswerValidator' -function populateError( +const populateError = ( error: ZodIssue[], pathToError: string | undefined, formatMessage: FormatMessage, -) { +) => { let errorObject = {} error.forEach((element) => { const defaultZodError = element.message === 'Invalid input' @@ -39,7 +39,7 @@ function populateError( return errorObject } -export function validateAnswers({ +export const validateAnswers = ({ dataSchema, answers, formatMessage, @@ -48,7 +48,7 @@ export function validateAnswers({ answers: FormValue isFullSchemaValidation?: boolean formatMessage: FormatMessage -}): ValidationRecord | undefined { +}): ValidationRecord | undefined => { try { if (dataSchema instanceof ZodEffects) { // cases where zod schema has a refinement on the schema object, needs to be defined partial diff --git a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change-v2/pdfGenerators/templates/residenceChange.ts b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change-v2/pdfGenerators/templates/residenceChange.ts index 2faf8f5ea364..0e5a3b42f410 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change-v2/pdfGenerators/templates/residenceChange.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change-v2/pdfGenerators/templates/residenceChange.ts @@ -24,7 +24,9 @@ import { DistrictCommissionerLogo } from '../assets/logo' const TEMPORARY = 'temporary' -export async function generateResidenceChangePdf(application: CRCApplication) { +export const generateResidenceChangePdf = async ( + application: CRCApplication, +) => { const { answers, externalData: { nationalRegistry }, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change-v2/utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change-v2/utils.ts index a812e261eda7..ea6d93eb248c 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change-v2/utils.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/children-residence-change-v2/utils.ts @@ -46,7 +46,7 @@ const syslumennOffices = { }, } -export function syslumennDataFromPostalCode(postalCode: string) { +export const syslumennDataFromPostalCode = (postalCode: string) => { for (const [, value] of Object.entries(syslumennOffices)) { if ( value.postalCodePrefixes.some((prefix) => postalCode.startsWith(prefix)) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/complaints-to-althingi-ombudsman/pdfGenerators/templates/complaint.ts b/libs/application/template-api-modules/src/lib/modules/templates/complaints-to-althingi-ombudsman/pdfGenerators/templates/complaint.ts index c306ee10a1b3..9c72d1b97f2f 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/complaints-to-althingi-ombudsman/pdfGenerators/templates/complaint.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/complaints-to-althingi-ombudsman/pdfGenerators/templates/complaint.ts @@ -9,7 +9,7 @@ import { addHeader, addSubheader, addValue, newDocument } from '../pdfUtils' import { format as formatNationalId } from 'kennitala' import { PdfConstants } from '../constants' -export async function generateComplaintPdf(application: Application) { +export const generateComplaintPdf = async (application: Application) => { const answers = application.answers as ComplaintsToAlthingiOmbudsmanAnswers const complainedFor = answers.complainedFor.decision === ComplainedForTypes.MYSELF diff --git a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/pdfGenerators/pdfGenerator.ts b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/pdfGenerators/pdfGenerator.ts index e3b9a465793c..36d0ac142e83 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/pdfGenerators/pdfGenerator.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/pdfGenerators/pdfGenerator.ts @@ -3,10 +3,10 @@ import { PdfConstants } from './constants' type generatePdfBody = (template: T, doc: PDFKit.PDFDocument) => void -export async function generatePdf( +export const generatePdf = async ( template: T, generatePdfBody: generatePdfBody, -): Promise { +): Promise => { const doc = new PDFDocument({ margins: { top: PdfConstants.VERTICAL_MARGIN, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/pdfGenerators/templates/complaintPdf.ts b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/pdfGenerators/templates/complaintPdf.ts index 63eebde8ced8..72e6cd470caa 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/pdfGenerators/templates/complaintPdf.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/data-protection-complaint/pdfGenerators/templates/complaintPdf.ts @@ -25,18 +25,18 @@ import parseISO from 'date-fns/parseISO' import is from 'date-fns/locale/is' import { DocumentInfo } from '@island.is/clients/data-protection-complaint' -export async function generateComplaintPdf( +export const generateComplaintPdf = async ( application: Application, attachedFiles: DocumentInfo[], -): Promise { +): Promise => { const dto = applicationToComplaintPDF(application, attachedFiles) return await generatePdf(dto, dpcApplicationPdf) } -function dpcApplicationPdf( +const dpcApplicationPdf = ( complaint: ComplaintPDF, doc: PDFKit.PDFDocument, -): void { +): void => { const timestamp = format( parseISO(complaint.submitDate.toISOString()), 'd. MMMM y', @@ -135,10 +135,10 @@ function dpcApplicationPdf( doc.end() } -function renderExternalDataMessages( +const renderExternalDataMessages = ( externalData: ExternalDataMessages, doc: PDFKit.PDFDocument, -): void { +): void => { addHeader(externalData.title, doc) addValue(externalData.subtitle, doc, PdfConstants.BOLD_FONT) addValue(externalData.description, doc) @@ -155,10 +155,10 @@ function renderExternalDataMessages( doc.moveDown() } -function renderInformationMessages( +const renderInformationMessages = ( information: Information, doc: PDFKit.PDFDocument, -): void { +): void => { addHeader(information.title, doc) const bulletsList = [ @@ -199,10 +199,10 @@ function renderInformationMessages( }) } -function renderContactsAndComplainees( +const renderContactsAndComplainees = ( complaint: ComplaintPDF, doc: PDFKit.PDFDocument, -): void { +): void => { const contactHeading = complaint.onBehalf === OnBehalf.MYSELF || complaint.onBehalf === OnBehalf.ORGANIZATION_OR_INSTITUTION || @@ -291,10 +291,10 @@ function renderContactsAndComplainees( } } -function renderAgencyComplainees( +const renderAgencyComplainees = ( complaint: ComplaintPDF, doc: PDFKit.PDFDocument, -): void { +): void => { if ( complaint.agency?.persons?.length && complaint.onBehalf !== OnBehalf.ORGANIZATION_OR_INSTITUTION && diff --git a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/bucket/bucket.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/bucket/bucket.service.ts index 0617c09837fe..dbad00ed1134 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/bucket/bucket.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/bucket/bucket.service.ts @@ -33,7 +33,7 @@ export class BucketService { Bucket: bucketName, Key: filename, } - this.s3.getObject(params, function (err, data) { + this.s3.getObject(params, (err, data) => { if (err) { reject(err) } else { diff --git a/libs/application/template-loader/src/index.ts b/libs/application/template-loader/src/index.ts index 995b3bc766fe..7fe9c2f17690 100644 --- a/libs/application/template-loader/src/index.ts +++ b/libs/application/template-loader/src/index.ts @@ -53,14 +53,13 @@ const loadTemplateLib = async ( } } -// eslint-disable-next-line func-style -export async function getApplicationTemplateByTypeId< +export const getApplicationTemplateByTypeId = async < TContext extends ApplicationContext, TStateSchema extends ApplicationStateSchema, TEvents extends EventObject, >( templateId: ApplicationTypes, -): Promise> { +): Promise> => { const templateLib = await loadTemplateLib(templateId) return templateLib.default as ApplicationTemplate< TContext, diff --git a/libs/application/templates/family-matters/children-residence-change-v2/src/forms/ContractRejected.ts b/libs/application/templates/family-matters/children-residence-change-v2/src/forms/ContractRejected.ts index eedc28cee6b4..238ec88ac7d4 100644 --- a/libs/application/templates/family-matters/children-residence-change-v2/src/forms/ContractRejected.ts +++ b/libs/application/templates/family-matters/children-residence-change-v2/src/forms/ContractRejected.ts @@ -7,7 +7,7 @@ import { Form, FormModes } from '@island.is/application/types' import Logo from '@island.is/application/templates/family-matters-core/assets/Logo' import * as m from '../lib/messages' -function rejectedForm(id: string): Form { +const rejectedForm = (id: string): Form => { return buildForm({ id: id, title: m.application.name, diff --git a/libs/application/templates/reference-template/src/fields/ExampleCountryField.tsx b/libs/application/templates/reference-template/src/fields/ExampleCountryField.tsx index bef92882e463..70d0e695371e 100644 --- a/libs/application/templates/reference-template/src/fields/ExampleCountryField.tsx +++ b/libs/application/templates/reference-template/src/fields/ExampleCountryField.tsx @@ -36,7 +36,7 @@ const ExampleCountryField: FC> = ({ // @ts-ignore const [age, setAge] = useState(formValue.person?.age || 0) - function fetchCountries(query = '') { + const fetchCountries = (query = '') => { if (query.length === 0) { return } diff --git a/libs/application/types/src/lib/StateMachine.ts b/libs/application/types/src/lib/StateMachine.ts index fd3c46b8596a..cad79844abe8 100644 --- a/libs/application/types/src/lib/StateMachine.ts +++ b/libs/application/types/src/lib/StateMachine.ts @@ -148,7 +148,7 @@ export type ApplicationStateMachine< > = StateMachine // manually overwrites the initial state for the template as well so the interpreter starts in the current application state -export function createApplicationMachine< +export const createApplicationMachine = < TContext extends ApplicationContext, TStateSchema extends ApplicationStateSchema, TEvent extends EventObject = AnyEventObject, @@ -157,7 +157,7 @@ export function createApplicationMachine< config: StateNodeConfig, options?: Partial>, initialContext?: TContext, -): ApplicationStateMachine { +): ApplicationStateMachine => { const context = initialContext ? { ...initialContext, application } : { application } diff --git a/libs/application/types/src/lib/application-types.ts b/libs/application/types/src/lib/application-types.ts index 243e1d2afa38..aecee156a270 100644 --- a/libs/application/types/src/lib/application-types.ts +++ b/libs/application/types/src/lib/application-types.ts @@ -1,3 +1,3 @@ -export function applicationTypes(): string { +export const applicationTypes = (): string => { return 'application-types' } diff --git a/libs/application/ui-components/src/utilities/FileUploadController/index.tsx b/libs/application/ui-components/src/utilities/FileUploadController/index.tsx index ddd5a75b0932..25d69e923d16 100644 --- a/libs/application/ui-components/src/utilities/FileUploadController/index.tsx +++ b/libs/application/ui-components/src/utilities/FileUploadController/index.tsx @@ -36,7 +36,7 @@ const answerToUploadFile = ({ name, key }: UploadFile): UploadFile => { return { name, key, status: 'done' } } -function reducer(state: UploadFile[], action: Action) { +const reducer = (state: UploadFile[], action: Action) => { switch (action.type) { case ActionTypes.ADD: return state.concat(action.payload.newFiles) diff --git a/libs/application/ui-components/src/utilities/handleServerError.ts b/libs/application/ui-components/src/utilities/handleServerError.ts index 79ccd95f4b83..3ade7b66c9f4 100644 --- a/libs/application/ui-components/src/utilities/handleServerError.ts +++ b/libs/application/ui-components/src/utilities/handleServerError.ts @@ -8,10 +8,10 @@ import { toast } from '@island.is/island-ui/core' import { FormatMessage } from '@island.is/localization' import { findProblemInApolloError } from '@island.is/shared/problem' -export function handleServerError( +export const handleServerError = ( error: ApolloError, formatMessage: FormatMessage, -): void { +): void => { const problem = findProblemInApolloError(error) const message = problem ? problem.detail ?? problem.title : error.message diff --git a/libs/application/ui-shell/src/components/DelegationsScreen.tsx b/libs/application/ui-shell/src/components/DelegationsScreen.tsx index 8eda93405a2d..37b76da98f6e 100644 --- a/libs/application/ui-shell/src/components/DelegationsScreen.tsx +++ b/libs/application/ui-shell/src/components/DelegationsScreen.tsx @@ -68,7 +68,7 @@ export const DelegationsScreen = ({ // Check whether application supports delegations useEffect(() => { - async function applicationSupportsDelegations() { + const applicationSupportsDelegations = async () => { if (type) { const template = await getApplicationTemplateByTypeId(type) const featureFlagEnabled = await featureFlagClient.getValue( diff --git a/libs/application/ui-shell/src/components/FormRepeater.tsx b/libs/application/ui-shell/src/components/FormRepeater.tsx index 0c3acb330a7a..93c71a84ff4a 100644 --- a/libs/application/ui-shell/src/components/FormRepeater.tsx +++ b/libs/application/ui-shell/src/components/FormRepeater.tsx @@ -50,7 +50,7 @@ const FormRepeater: FC< [], ) as RepeaterItems - async function removeRepeaterItem(index: number) { + const removeRepeaterItem = async (index: number) => { if (index >= 0 && index < repeaterItems.length) { const newRepeaterItems = [ ...repeaterItems.slice(0, index), @@ -60,7 +60,7 @@ const FormRepeater: FC< } } - async function setRepeaterItems(items: RepeaterItems) { + const setRepeaterItems = async (items: RepeaterItems) => { return await onUpdateRepeater(items) } diff --git a/libs/application/ui-shell/src/components/ScreenFooter.tsx b/libs/application/ui-shell/src/components/ScreenFooter.tsx index 276f7a2c781a..4e4ed12dd51a 100644 --- a/libs/application/ui-shell/src/components/ScreenFooter.tsx +++ b/libs/application/ui-shell/src/components/ScreenFooter.tsx @@ -85,7 +85,7 @@ export const ScreenFooter: FC> = ({ return null } - function renderSubmitButtons() { + const renderSubmitButtons = () => { if (!submitField || submitField.placement === 'screen') { return (