From d8665eb54e09f60e10a30dc9984148bd49d80632 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: Mon, 10 Jun 2024 21:57:42 +0000 Subject: [PATCH] chore(user-profile): Remove migration worker and make user profile strict (#14791) * Remove worker and make user profile strict * Fix dependant type errors. --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../app/user-profile/user-profile.service.ts | 6 +- .../notificationsWorker.service.ts | 2 +- .../user-profile/infra/service-portal-api.ts | 36 - .../20240513221742-remove-migration-table.js | 15 + apps/services/user-profile/project.json | 9 +- .../app/user-profile/dto/confirmEmailDto.ts | 6 +- .../src/app/user-profile/dto/confirmSmsDto.ts | 6 +- .../user-profile/emailVerification.model.ts | 18 +- .../pipes/userProfileByNationalId.pipe.ts | 29 - .../app/user-profile/smsVerification.model.ts | 18 +- .../user-profile/userDeviceTokens.model.ts | 16 +- .../user-profile/userProfile.controller.ts | 7 +- .../app/user-profile/userProfile.service.ts | 9 +- .../app/user-profile/userToken.controller.ts | 2 +- .../src/app/utils/format-phone-number.ts | 2 +- .../src/app/v2/dto/actor-profile.dto.ts | 11 +- .../src/app/v2/dto/islyklar-upsert.dto.ts | 2 +- .../src/app/v2/dto/post-nudge.dto.ts | 2 +- .../src/app/v2/dto/user-profile.dto.ts | 25 +- .../src/app/v2/user-profile.module.ts | 4 +- .../src/app/v2/user-profile.service.ts | 49 +- .../src/app/worker/test/worker.spec.ts | 777 ------------------ .../user-profile/src/app/worker/types.ts | 5 - .../app/worker/userProfileAdvania.model.ts | 84 -- .../src/app/worker/worker.module.ts | 31 - .../src/app/worker/worker.service.ts | 222 ----- .../src/app/worker/worker.utils.ts | 89 -- apps/services/user-profile/src/config.ts | 5 - .../src/environments/environment.ts | 24 +- apps/services/user-profile/src/main.ts | 25 +- apps/services/user-profile/src/worker.ts | 13 - apps/services/user-profile/tsconfig.json | 5 - charts/islandis/values.dev.yaml | 86 -- charts/islandis/values.prod.yaml | 86 -- charts/islandis/values.staging.yaml | 86 -- infra/src/uber-charts/islandis.ts | 9 +- .../user-profile/src/lib/userProfile.model.ts | 6 +- 37 files changed, 152 insertions(+), 1675 deletions(-) create mode 100644 apps/services/user-profile/migrations/20240513221742-remove-migration-table.js delete mode 100644 apps/services/user-profile/src/app/user-profile/pipes/userProfileByNationalId.pipe.ts delete mode 100644 apps/services/user-profile/src/app/worker/test/worker.spec.ts delete mode 100644 apps/services/user-profile/src/app/worker/types.ts delete mode 100644 apps/services/user-profile/src/app/worker/userProfileAdvania.model.ts delete mode 100644 apps/services/user-profile/src/app/worker/worker.module.ts delete mode 100644 apps/services/user-profile/src/app/worker/worker.service.ts delete mode 100644 apps/services/user-profile/src/app/worker/worker.utils.ts delete mode 100644 apps/services/user-profile/src/worker.ts diff --git a/apps/services/auth/ids-api/src/app/user-profile/user-profile.service.ts b/apps/services/auth/ids-api/src/app/user-profile/user-profile.service.ts index 5c06dd9c7e40..9e310e1513c9 100644 --- a/apps/services/auth/ids-api/src/app/user-profile/user-profile.service.ts +++ b/apps/services/auth/ids-api/src/app/user-profile/user-profile.service.ts @@ -174,13 +174,15 @@ export class UserProfileService { auth, ).meUserProfileControllerFindUserProfile() return { - email: userProfile.isRestricted ? undefined : userProfile.email, + email: userProfile.isRestricted + ? undefined + : userProfile.email ?? undefined, emailVerified: userProfile.isRestricted ? undefined : userProfile.emailVerified, phoneNumber: userProfile.isRestricted ? undefined - : userProfile.mobilePhoneNumber, + : userProfile.mobilePhoneNumber ?? undefined, phoneNumberVerified: userProfile.isRestricted ? undefined : userProfile.mobilePhoneNumberVerified, diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts index 54c91166cd96..6b925bb8804d 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts @@ -39,7 +39,7 @@ const WORK_ENDING_HOUR = 23 // 11 PM type HandleNotification = { profile: { nationalId: string - email?: string + email?: string | null documentNotifications: boolean emailNotifications: boolean locale?: string diff --git a/apps/services/user-profile/infra/service-portal-api.ts b/apps/services/user-profile/infra/service-portal-api.ts index 414784d80f59..d996908099bb 100644 --- a/apps/services/user-profile/infra/service-portal-api.ts +++ b/apps/services/user-profile/infra/service-portal-api.ts @@ -9,13 +9,8 @@ import { NationalRegistryB2C, } from '../../../../infra/src/dsl/xroad' -// We basically don't want it to run in a cron job -// but manually, so set it to run once a year on dec 31st -const schedule = '0 0 31 12 *' - const namespace = 'service-portal' const serviceId = `${namespace}-api` -const workerId = `${serviceId}-worker` const imageId = 'services-user-profile' const envVariables: EnvironmentVariables = { @@ -36,11 +31,6 @@ const envVariables: EnvironmentVariables = { staging: 'false', prod: 'false', }, - USER_PROFILE_WORKER_PAGE_SIZE: { - dev: '3000', - staging: '3000', - prod: '3000', - }, AUTH_DELEGATION_API_URL: { dev: 'http://web-services-auth-delegation-api.identity-server-delegation.svc.cluster.local', staging: @@ -68,32 +58,6 @@ const secrets: Secrets = { '/k8s/api/NATIONAL_REGISTRY_B2C_CLIENT_SECRET', } -export const workerSetup = (): ServiceBuilder => - service(workerId) - .namespace(namespace) - .image(imageId) - .env(envVariables) - .secrets(secrets) - .files({ filename: 'islyklar.p12', env: 'ISLYKILL_CERT' }) - .command('node') - .args('main.js', '--job=worker') - .resources({ - limits: { cpu: '800m', memory: '1024Mi' }, - requests: { cpu: '400m', memory: '512Mi' }, - }) - .db() - .extraAttributes({ - dev: { - schedule, - }, - staging: { - schedule, - }, - prod: { - schedule, - }, - }) - export const serviceSetup = (): ServiceBuilder => service(serviceId) .namespace(namespace) diff --git a/apps/services/user-profile/migrations/20240513221742-remove-migration-table.js b/apps/services/user-profile/migrations/20240513221742-remove-migration-table.js new file mode 100644 index 000000000000..b1cd6ac98725 --- /dev/null +++ b/apps/services/user-profile/migrations/20240513221742-remove-migration-table.js @@ -0,0 +1,15 @@ +'use strict' + +module.exports = { + async up(queryInterface) { + return queryInterface.dropTable('user_profile_advania') + }, + + async down(queryInterface, Sequelize) { + /** + * This migration is cleaning up the migration table used when we migrated from the old user profile database. + * + * There is no down action as we cannot reinstate the data that was dropped along with the table. + */ + }, +} diff --git a/apps/services/user-profile/project.json b/apps/services/user-profile/project.json index 669fc0e8a066..e60101613a3b 100644 --- a/apps/services/user-profile/project.json +++ b/apps/services/user-profile/project.json @@ -52,13 +52,6 @@ "buildTarget": "services-user-profile:build" } }, - "worker": { - "executor": "@nx/js:node", - "options": { - "buildTarget": "services-user-profile:build", - "args": ["--job", "worker"] - } - }, "lint": { "executor": "@nx/linter:eslint", "options": { @@ -98,7 +91,7 @@ "migrate/generate": { "executor": "nx:run-commands", "options": { - "command": "../../../node_modules/.bin/sequelize-cli migration:generate --name $(whoami)", + "command": "../../../node_modules/.bin/sequelize-cli migration:generate", "cwd": "apps/services/user-profile/" } }, diff --git a/apps/services/user-profile/src/app/user-profile/dto/confirmEmailDto.ts b/apps/services/user-profile/src/app/user-profile/dto/confirmEmailDto.ts index 662468d34ed1..6ff5a1dcc693 100644 --- a/apps/services/user-profile/src/app/user-profile/dto/confirmEmailDto.ts +++ b/apps/services/user-profile/src/app/user-profile/dto/confirmEmailDto.ts @@ -1,11 +1,11 @@ import { IsNotEmpty, IsString } from 'class-validator' -import { ApiProperty } from '@nestjs/swagger' +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' export class ConfirmEmailDto { @IsNotEmpty() @IsString() - @ApiProperty() - readonly hash!: string + @ApiPropertyOptional() + readonly hash?: string @IsNotEmpty() @IsString() diff --git a/apps/services/user-profile/src/app/user-profile/dto/confirmSmsDto.ts b/apps/services/user-profile/src/app/user-profile/dto/confirmSmsDto.ts index 84f75c8b8eb9..496e49fcb410 100644 --- a/apps/services/user-profile/src/app/user-profile/dto/confirmSmsDto.ts +++ b/apps/services/user-profile/src/app/user-profile/dto/confirmSmsDto.ts @@ -1,11 +1,11 @@ import { IsNotEmpty, IsString } from 'class-validator' -import { ApiProperty } from '@nestjs/swagger' +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' export class ConfirmSmsDto { @IsNotEmpty() @IsString() - @ApiProperty() - readonly code!: string + @ApiPropertyOptional() + readonly code?: string @IsNotEmpty() @IsString() diff --git a/apps/services/user-profile/src/app/user-profile/emailVerification.model.ts b/apps/services/user-profile/src/app/user-profile/emailVerification.model.ts index f05af3e187cf..8bed2e7b9ea9 100644 --- a/apps/services/user-profile/src/app/user-profile/emailVerification.model.ts +++ b/apps/services/user-profile/src/app/user-profile/emailVerification.model.ts @@ -7,6 +7,11 @@ import { UpdatedAt, } from 'sequelize-typescript' import { ApiProperty } from '@nestjs/swagger' +import type { + CreationOptional, + InferAttributes, + InferCreationAttributes, +} from 'sequelize' @Table({ tableName: 'email_verification', @@ -17,7 +22,10 @@ import { ApiProperty } from '@nestjs/swagger' }, ], }) -export class EmailVerification extends Model { +export class EmailVerification extends Model< + InferAttributes, + InferCreationAttributes +> { @Column({ type: DataType.UUID, primaryKey: true, @@ -25,15 +33,15 @@ export class EmailVerification extends Model { defaultValue: DataType.UUIDV4, }) @ApiProperty() - id!: string + id!: CreationOptional @CreatedAt @ApiProperty() - created!: Date + created!: CreationOptional @UpdatedAt @ApiProperty() - modified!: Date + modified!: CreationOptional @Column({ type: DataType.STRING, @@ -47,7 +55,7 @@ export class EmailVerification extends Model { type: DataType.BOOLEAN, }) @ApiProperty() - confirmed!: boolean + confirmed!: CreationOptional @Column({ type: DataType.STRING, diff --git a/apps/services/user-profile/src/app/user-profile/pipes/userProfileByNationalId.pipe.ts b/apps/services/user-profile/src/app/user-profile/pipes/userProfileByNationalId.pipe.ts deleted file mode 100644 index 254978471c40..000000000000 --- a/apps/services/user-profile/src/app/user-profile/pipes/userProfileByNationalId.pipe.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { LOGGER_PROVIDER, Logger } from '@island.is/logging' -import { Injectable, Inject, PipeTransform } from '@nestjs/common' -import { UserProfile } from '../userProfile.model' -import { UserProfileService } from '../userProfile.service' - -@Injectable() -export class UserProfileByNationalIdPipe - implements PipeTransform> -{ - constructor( - @Inject(LOGGER_PROVIDER) - private logger: Logger, - private readonly userProfileService: UserProfileService, - ) {} - - async transform(nationalId: string): Promise { - const userProfile = await this.userProfileService.findByNationalId( - nationalId, - ) - if (!userProfile) { - this.logger.info( - `A user profile with nationalId ${nationalId} does not exist`, - ) - } else { - this.logger.info(`Found user profile with nationalId ${nationalId}`) - } - return userProfile - } -} diff --git a/apps/services/user-profile/src/app/user-profile/smsVerification.model.ts b/apps/services/user-profile/src/app/user-profile/smsVerification.model.ts index 1ae8096b113d..713b92284773 100644 --- a/apps/services/user-profile/src/app/user-profile/smsVerification.model.ts +++ b/apps/services/user-profile/src/app/user-profile/smsVerification.model.ts @@ -7,6 +7,11 @@ import { UpdatedAt, } from 'sequelize-typescript' import { ApiProperty } from '@nestjs/swagger' +import type { + CreationOptional, + InferAttributes, + InferCreationAttributes, +} from 'sequelize' @Table({ tableName: 'sms_verification', @@ -17,7 +22,10 @@ import { ApiProperty } from '@nestjs/swagger' }, ], }) -export class SmsVerification extends Model { +export class SmsVerification extends Model< + InferAttributes, + InferCreationAttributes +> { @Column({ type: DataType.UUID, primaryKey: true, @@ -25,15 +33,15 @@ export class SmsVerification extends Model { defaultValue: DataType.UUIDV4, }) @ApiProperty() - id!: string + id!: CreationOptional @CreatedAt @ApiProperty() - created!: Date + created!: CreationOptional @UpdatedAt @ApiProperty() - modified!: Date + modified!: CreationOptional @Column({ type: DataType.STRING, @@ -53,7 +61,7 @@ export class SmsVerification extends Model { type: DataType.BOOLEAN, }) @ApiProperty() - confirmed!: boolean + confirmed!: CreationOptional @Column({ type: DataType.STRING, diff --git a/apps/services/user-profile/src/app/user-profile/userDeviceTokens.model.ts b/apps/services/user-profile/src/app/user-profile/userDeviceTokens.model.ts index 2dbb2aceefa7..1043a4203686 100644 --- a/apps/services/user-profile/src/app/user-profile/userDeviceTokens.model.ts +++ b/apps/services/user-profile/src/app/user-profile/userDeviceTokens.model.ts @@ -6,6 +6,11 @@ import { CreatedAt, UpdatedAt, } from 'sequelize-typescript' +import type { + CreationOptional, + InferAttributes, + InferCreationAttributes, +} from 'sequelize' @Table({ tableName: 'user_device_tokens', @@ -16,20 +21,23 @@ import { }, ], }) -export class UserDeviceTokens extends Model { +export class UserDeviceTokens extends Model< + InferAttributes, + InferCreationAttributes +> { @Column({ type: DataType.UUID, primaryKey: true, allowNull: false, defaultValue: DataType.UUIDV4, }) - id!: string + id!: CreationOptional @CreatedAt - created!: Date + created!: CreationOptional @UpdatedAt - modified!: Date + modified!: CreationOptional @Column({ type: DataType.STRING, diff --git a/apps/services/user-profile/src/app/user-profile/userProfile.controller.ts b/apps/services/user-profile/src/app/user-profile/userProfile.controller.ts index 326fa215f27b..cb39b058a4eb 100644 --- a/apps/services/user-profile/src/app/user-profile/userProfile.controller.ts +++ b/apps/services/user-profile/src/app/user-profile/userProfile.controller.ts @@ -21,7 +21,6 @@ import { HttpCode, Delete, Patch, - NotFoundException, } from '@nestjs/common' import { NoContentException } from '@island.is/nest/problem' import { @@ -49,6 +48,7 @@ import { UserProfileService } from './userProfile.service' import { VerificationService } from './verification.service' import { DataStatus } from './types/dataStatusTypes' import { ActorLocale } from './dto/actorLocale' +import { Locale } from './types/localeTypes' @UseGuards(IdsUserGuard, ScopesGuard) @ApiTags('User Profile') @@ -113,7 +113,7 @@ export class UserProfileController { return { nationalId: userProfile.nationalId, - locale: userProfile.locale, + locale: userProfile.locale ?? Locale.ICELANDIC, } } @@ -201,8 +201,7 @@ export class UserProfileController { try { return await this.findOneByNationalId(nationalId, user) } catch (error) { - const ret = await this.create({ nationalId }, user) - return ret + return this.create({ nationalId }, user) } } diff --git a/apps/services/user-profile/src/app/user-profile/userProfile.service.ts b/apps/services/user-profile/src/app/user-profile/userProfile.service.ts index 22cef210d8a7..32abceae163e 100644 --- a/apps/services/user-profile/src/app/user-profile/userProfile.service.ts +++ b/apps/services/user-profile/src/app/user-profile/userProfile.service.ts @@ -25,13 +25,6 @@ export class UserProfileService { private readonly userDeviceTokensModel: typeof UserDeviceTokens, ) {} - async findById(id: string): Promise { - this.logger.debug(`Finding user profile by id "${id}"`) - return this.userProfileModel.findOne({ - where: { id }, - }) - } - async create(userProfileDto: CreateUserProfileDto): Promise { return this.userProfileModel.create({ ...userProfileDto }) } @@ -74,7 +67,7 @@ export class UserProfileService { ...body, nationalId: user.nationalId, }) - } catch (e: any) { + } catch (e) { throw new BadRequestException(e.errors) } } diff --git a/apps/services/user-profile/src/app/user-profile/userToken.controller.ts b/apps/services/user-profile/src/app/user-profile/userToken.controller.ts index 1d9716dc08f7..e7ce2f8f5d84 100644 --- a/apps/services/user-profile/src/app/user-profile/userToken.controller.ts +++ b/apps/services/user-profile/src/app/user-profile/userToken.controller.ts @@ -46,7 +46,7 @@ export class UserTokenController { async findOneByNationalId( @Param('nationalId') nationalId: string, - ): Promise { + ): Promise { const userProfile = await this.userProfileService.findByNationalId( nationalId, ) diff --git a/apps/services/user-profile/src/app/utils/format-phone-number.ts b/apps/services/user-profile/src/app/utils/format-phone-number.ts index 60b121ef61de..c55d21c8fe76 100644 --- a/apps/services/user-profile/src/app/utils/format-phone-number.ts +++ b/apps/services/user-profile/src/app/utils/format-phone-number.ts @@ -7,7 +7,7 @@ import { parsePhoneNumber } from 'libphonenumber-js' * * @param phoneNumber */ -export const formatPhoneNumber = (phoneNumber: string): string => { +export const formatPhoneNumber = (phoneNumber?: string): string => { if (!phoneNumber) { return '' } diff --git a/apps/services/user-profile/src/app/v2/dto/actor-profile.dto.ts b/apps/services/user-profile/src/app/v2/dto/actor-profile.dto.ts index d2df6343cbc2..8a44f4fadb2b 100644 --- a/apps/services/user-profile/src/app/v2/dto/actor-profile.dto.ts +++ b/apps/services/user-profile/src/app/v2/dto/actor-profile.dto.ts @@ -29,10 +29,13 @@ export class ActorProfileDto { @IsBoolean() emailNotifications!: boolean - @ApiPropertyOptional() + @ApiPropertyOptional({ + type: () => String, + nullable: true, + }) @IsOptional() @IsEmail() - readonly email?: string + readonly email?: string | null @ApiProperty() @IsBoolean() @@ -42,10 +45,10 @@ export class ActorProfileDto { @IsBoolean() readonly documentNotifications!: boolean - @ApiPropertyOptional() + @ApiPropertyOptional({ enum: Locale, type: () => Locale, nullable: true }) @IsOptional() @IsEnum(Locale) - readonly locale?: Locale + readonly locale?: Locale | null } export class PatchActorProfileDto { diff --git a/apps/services/user-profile/src/app/v2/dto/islyklar-upsert.dto.ts b/apps/services/user-profile/src/app/v2/dto/islyklar-upsert.dto.ts index a5f7eb7930e8..bdca044e89cd 100644 --- a/apps/services/user-profile/src/app/v2/dto/islyklar-upsert.dto.ts +++ b/apps/services/user-profile/src/app/v2/dto/islyklar-upsert.dto.ts @@ -1,5 +1,5 @@ export class IslyklarUpsertDto { - nationalId: string + nationalId!: string email?: string diff --git a/apps/services/user-profile/src/app/v2/dto/post-nudge.dto.ts b/apps/services/user-profile/src/app/v2/dto/post-nudge.dto.ts index d63eb94d9141..0fdb0e6a5a91 100644 --- a/apps/services/user-profile/src/app/v2/dto/post-nudge.dto.ts +++ b/apps/services/user-profile/src/app/v2/dto/post-nudge.dto.ts @@ -6,5 +6,5 @@ import { NudgeType } from '../../types/nudge-type' export class PostNudgeDto { @IsEnum(NudgeType) @ApiProperty({ enum: NudgeType }) - nudgeType: NudgeType + nudgeType!: NudgeType } diff --git a/apps/services/user-profile/src/app/v2/dto/user-profile.dto.ts b/apps/services/user-profile/src/app/v2/dto/user-profile.dto.ts index 97e9b9b2ef1f..561d8e8e96dd 100644 --- a/apps/services/user-profile/src/app/v2/dto/user-profile.dto.ts +++ b/apps/services/user-profile/src/app/v2/dto/user-profile.dto.ts @@ -13,22 +13,28 @@ import { Locale } from '../../user-profile/types/localeTypes' export class UserProfileDto { @ApiProperty() @IsString() - readonly nationalId: string + readonly nationalId!: string - @ApiPropertyOptional() + @ApiPropertyOptional({ + type: () => String, + nullable: true, + }) @IsOptional() @IsEmail() - readonly email?: string + readonly email?: string | null - @ApiPropertyOptional() + @ApiPropertyOptional({ + type: () => String, + nullable: true, + }) @IsOptional() @IsString() - readonly mobilePhoneNumber?: string + readonly mobilePhoneNumber?: string | null - @ApiPropertyOptional() + @ApiPropertyOptional({ enum: Locale, type: () => Locale, nullable: true }) @IsOptional() @IsEnum(Locale) - readonly locale?: Locale + readonly locale?: Locale | null @ApiProperty() @IsBoolean() @@ -47,7 +53,10 @@ export class UserProfileDto { @IsString() readonly profileImageUrl?: string - @ApiPropertyOptional() + @ApiPropertyOptional({ + type: () => Boolean, + nullable: true, + }) @IsOptional() @IsBoolean() readonly needsNudge?: boolean | null diff --git a/apps/services/user-profile/src/app/v2/user-profile.module.ts b/apps/services/user-profile/src/app/v2/user-profile.module.ts index 3596571f5236..17ac4e074138 100644 --- a/apps/services/user-profile/src/app/v2/user-profile.module.ts +++ b/apps/services/user-profile/src/app/v2/user-profile.module.ts @@ -32,9 +32,7 @@ import { AuthDelegationApiClientModule } from '@island.is/clients/auth/delegatio EmailModule.register(environment.emailOptions), SmsModule.register(environment.smsOptions), IslykillApiModule.register({ - basePath: environment.islykillConfig.basePath, - cert: environment.islykillConfig.cert, - passphrase: environment.islykillConfig.passphrase, + ...environment.islykillConfig, }), AuthDelegationApiClientModule, ], diff --git a/apps/services/user-profile/src/app/v2/user-profile.service.ts b/apps/services/user-profile/src/app/v2/user-profile.service.ts index 6a5e9e3e0e76..04c1b508a847 100644 --- a/apps/services/user-profile/src/app/v2/user-profile.service.ts +++ b/apps/services/user-profile/src/app/v2/user-profile.service.ts @@ -73,8 +73,8 @@ export class UserProfileService { email: userProfile.email, mobilePhoneNumber: userProfile.mobilePhoneNumber, locale: userProfile.locale, - mobilePhoneNumberVerified: userProfile.mobilePhoneNumberVerified, - emailVerified: userProfile.emailVerified, + mobilePhoneNumberVerified: userProfile.mobilePhoneNumberVerified ?? false, + emailVerified: userProfile.emailVerified ?? false, documentNotifications: userProfile.documentNotifications, emailNotifications: userProfile.emailNotifications, lastNudge: userProfile.lastNudge, @@ -158,15 +158,15 @@ export class UserProfileService { const { confirmed, message, remainingAttempts } = await this.verificationService.confirmEmail( { - email: userProfile.email, - hash: userProfile.emailVerificationCode, + email: userProfile.email!, + hash: userProfile.emailVerificationCode!, }, ...commonArgs, ) if (!confirmed) { // Check if we should throw a BadRequest or an AttemptFailed error - if (remainingAttempts >= 0) { + if (remainingAttempts && remainingAttempts >= 0) { throw new AttemptFailed(remainingAttempts, { emailVerificationCode: 'Verification code does not match.', }) @@ -180,15 +180,15 @@ export class UserProfileService { const { confirmed, message, remainingAttempts } = await this.verificationService.confirmSms( { - mobilePhoneNumber: userProfile.mobilePhoneNumber, - code: userProfile.mobilePhoneNumberVerificationCode, + mobilePhoneNumber: userProfile.mobilePhoneNumber!, + code: userProfile.mobilePhoneNumberVerificationCode!, }, ...commonArgs, ) if (confirmed === false) { // Check if we should throw a BadRequest or an AttemptFailed error - if (remainingAttempts >= 0) { + if (remainingAttempts && remainingAttempts >= 0) { throw new AttemptFailed(remainingAttempts, { smsVerificationCode: 'Verification code does not match.', }) @@ -500,10 +500,10 @@ export class UserProfileService { mobileStatus, emailStatus, }: { - email: string - mobilePhoneNumber: string - emailVerified: boolean - mobilePhoneNumberVerified: boolean + email?: string | null + mobilePhoneNumber?: string | null + emailVerified?: boolean + mobilePhoneNumberVerified?: boolean mobileStatus: DataStatus emailStatus: DataStatus }): boolean { @@ -525,8 +525,8 @@ export class UserProfileService { * @param mobilePhoneNumber */ private checkAudkenniSameAsMobilePhoneNumber( - audkenniSimNumber: string, - mobilePhoneNumber: string, + audkenniSimNumber?: string, + mobilePhoneNumber?: string, ): boolean { if (!audkenniSimNumber || !mobilePhoneNumber) { return false @@ -552,8 +552,8 @@ export class UserProfileService { email: userProfile.email, mobilePhoneNumber: userProfile.mobilePhoneNumber, locale: userProfile.locale, - mobilePhoneNumberVerified: userProfile.mobilePhoneNumberVerified, - emailVerified: userProfile.emailVerified, + mobilePhoneNumberVerified: userProfile.mobilePhoneNumberVerified ?? false, + emailVerified: userProfile.emailVerified ?? false, documentNotifications: userProfile.documentNotifications, needsNudge: this.checkNeedsNudge(userProfile), emailNotifications: userProfile.emailNotifications, @@ -562,15 +562,22 @@ export class UserProfileService { isRestricted: false, } - if ((this.config.migrationDate ?? new Date()) > userProfile.lastNudge) { + if ( + !userProfile.lastNudge || + (this.config.migrationDate ?? new Date()) > userProfile.lastNudge + ) { filteredUserProfile = { ...filteredUserProfile, email: isFirstParty ? userProfile.email : null, mobilePhoneNumber: isFirstParty ? userProfile.mobilePhoneNumber : null, - emailVerified: isFirstParty ? userProfile.emailVerified : false, - mobilePhoneNumberVerified: isFirstParty - ? userProfile.mobilePhoneNumberVerified - : false, + emailVerified: + isFirstParty && userProfile.emailVerified + ? userProfile.emailVerified + : false, + mobilePhoneNumberVerified: + isFirstParty && userProfile.mobilePhoneNumberVerified + ? userProfile.mobilePhoneNumberVerified + : false, isRestricted: true, } } diff --git a/apps/services/user-profile/src/app/worker/test/worker.spec.ts b/apps/services/user-profile/src/app/worker/test/worker.spec.ts deleted file mode 100644 index e8349f374958..000000000000 --- a/apps/services/user-profile/src/app/worker/test/worker.spec.ts +++ /dev/null @@ -1,777 +0,0 @@ -import pick from 'lodash/pick' -import addDays from 'date-fns/addDays' -import faker from 'faker' -import { getModelToken } from '@nestjs/sequelize' - -import { TestApp, testServer, useDatabase } from '@island.is/testing/nest' - -import { SequelizeConfigService } from '../../sequelizeConfig.service' - -import { UserProfileWorkerModule } from '../worker.module' -import { UserProfileWorkerService } from '../worker.service' -import { UserProfileAdvania } from '../userProfileAdvania.model' - -import { UserProfile } from '../../user-profile/userProfile.model' - -import { ProcessedStatus } from '../types' -import { stringsHaveMatchingValue } from '../worker.utils' -import addMonths from 'date-fns/addMonths' -import { NUDGE_INTERVAL } from '../../v2/user-profile.service' - -describe('UserProfileWorker', () => { - jest.setTimeout(30000) - let app: TestApp - let workerService: UserProfileWorkerService - let userProfileModel: typeof UserProfile - let userProfileAdvaniaModel: typeof UserProfileAdvania - - beforeEach(async () => { - app = await testServer({ - appModule: UserProfileWorkerModule, - hooks: [ - useDatabase({ type: 'postgres', provider: SequelizeConfigService }), - ], - }) - workerService = app.get(UserProfileWorkerService) - userProfileModel = app.get(getModelToken(UserProfile)) - userProfileAdvaniaModel = app.get(getModelToken(UserProfileAdvania)) - }) - - it('should import new profiles', async () => { - // Arrange - const advaniaProfiles = new Array(10).fill(null).map((_, index) => ({ - ssn: `${index}`, - email: `test${index}@test.local`, - mobilePhoneNumber: `888111${index}`, - exported: new Date(2023, 10, 1), - })) - - // Act - await userProfileAdvaniaModel.bulkCreate(advaniaProfiles) - const advaniaProfilesBeforeMigration = - await userProfileAdvaniaModel.findAll() - const userProfilesBeforeMigration = await userProfileModel.findAll() - await workerService.run() - const advaniaProfilesAfterMigration = - await userProfileAdvaniaModel.findAll() - const userProfilesAfterMigration = await userProfileModel.findAll() - - // Assert - expect( - advaniaProfilesBeforeMigration.every( - (p) => p.status === ProcessedStatus.PENDING, - ), - ).toBe(true) - expect(userProfilesBeforeMigration.length).toBe(0) - expect( - advaniaProfilesAfterMigration.every( - (p) => p.status === ProcessedStatus.DONE, - ), - ).toBe(true) - expect( - userProfilesAfterMigration.every((p) => { - const matchingProfile = advaniaProfilesAfterMigration.find( - (ap) => ap.ssn === p.nationalId, - ) - - return ( - stringsHaveMatchingValue(matchingProfile.email, p.email, true) && - matchingProfile.mobilePhoneNumber === p.mobilePhoneNumber - ) - }), - ).toBe(true) - }) - - describe('profiles being migrated exist in user_profile', () => { - it('should keep profiles as is if email and phone numbers match but set nudge to nudgeLastAsked', async () => { - // Arrange - const advaniaProfiles = new Array(10).fill(null).map((_, index) => ({ - ssn: `${index}`, - email: `test${index}@test.local`, - mobilePhoneNumber: `888111${index}`, - exported: new Date(2023, 10, 1), - nudgeLastAsked: new Date(2023, 10, 5), - })) - - const currentDate = new Date() - - const existingProfiles = advaniaProfiles.map((p) => ({ - nationalId: p.ssn, - email: p.email, - mobilePhoneNumber: p.mobilePhoneNumber, - modified: new Date(2023, 0, 1), - lastNudge: currentDate, // not null - nextNudge: addMonths(currentDate, NUDGE_INTERVAL), - })) - - // Act - await userProfileAdvaniaModel.bulkCreate(advaniaProfiles) - await userProfileModel.bulkCreate(existingProfiles) - - const userProfilesBeforeMigration = await userProfileModel.findAll() - - await workerService.run() - - const userProfilesAfterMigration = await userProfileModel.findAll() - - // Assert - const fieldsToCompare = ['nationalId', 'email', 'mobilePhoneNumber'] - - expect(userProfilesBeforeMigration.length).toBe( - userProfilesAfterMigration.length, - ) - - for (let i = 0; i < userProfilesBeforeMigration.length; i += 1) { - const profileBefore = userProfilesBeforeMigration[i] - const profileAfter = userProfilesAfterMigration[i] - const advaniaProfile = advaniaProfiles[i] - - expect(pick(profileBefore, fieldsToCompare)).toEqual( - pick(profileAfter, fieldsToCompare), - ) - expect(profileAfter.lastNudge).toEqual(advaniaProfile.nudgeLastAsked) - expect(profileAfter.nextNudge).toEqual( - addMonths(advaniaProfile.nudgeLastAsked, NUDGE_INTERVAL), - ) - } - }) - - describe('email and phone numbers do not match', () => { - it('should override user profile when modified date is older than export date', async () => { - // Arrange - const advaniaProfiles = new Array(10).fill(null).map((_, index) => ({ - ssn: `${index}`, - email: `test${index}@test.local`, - mobilePhoneNumber: `888111${index}`, - exported: new Date(2023, 10, 1), - })) - - const existingProfiles = advaniaProfiles.map((p) => ({ - nationalId: p.ssn, - // Modified date is older than the exported date - modified: addDays(p.exported, -1), - // Email and phone random, different from advania profile - email: faker.internet.email(), - mobilePhoneNumber: faker.phone.phoneNumber('+354-#######'), - })) - - // Act - await userProfileAdvaniaModel.bulkCreate(advaniaProfiles) - await userProfileModel.bulkCreate(existingProfiles) - - const userProfilesBeforeMigration = await userProfileModel.findAll() - - await workerService.run() - - const userProfilesAfterMigration = await userProfileModel.findAll() - - // Assert - for (let i = 0; i < userProfilesBeforeMigration.length; i += 1) { - const profileBefore = userProfilesBeforeMigration[i] - const profileAfter = userProfilesAfterMigration[i] - - expect( - stringsHaveMatchingValue( - profileBefore.email, - profileAfter.email, - true, - ), - ).toBe(false) - expect(profileBefore.mobilePhoneNumber).not.toBe( - profileAfter.mobilePhoneNumber, - ) - expect(profileAfter.lastNudge).toBe(null) - expect(profileAfter.nextNudge).toBe(null) - } - }) - - describe('existing user profile was modified after the export occured', () => { - it('should only change lastNudge=null if modified date is more recent than export date and neither email nor phone were verified', async () => { - // Arrange - const advaniaProfiles = new Array(10).fill(null).map((_, index) => ({ - ssn: `${index}`, - email: `test${index}@test.local`, - mobilePhoneNumber: `888111${index}`, - // In the future to be more recent than modified date - exported: addDays(new Date(), -1), - })) - - const lastNudge = new Date(2023, 10, 1) - - const existingProfiles = advaniaProfiles.map((p) => ({ - nationalId: p.ssn, - // Email and phone random, different from advania profile - email: faker.internet.email(), - emailVerified: false, - mobilePhoneNumber: faker.phone.phoneNumber('+354-#######'), - mobilePhoneNumberVerified: false, - lastNudge, - })) - - // Act - await userProfileAdvaniaModel.bulkCreate(advaniaProfiles) - await userProfileModel.bulkCreate(existingProfiles) - - const userProfilesBeforeMigration = await userProfileModel.findAll() - - await workerService.run() - - const userProfilesAfterMigration = await userProfileModel.findAll() - - // Assert - for (let i = 0; i < userProfilesBeforeMigration.length; i += 1) { - const profileBefore = userProfilesBeforeMigration[i] - const profileAfter = userProfilesAfterMigration[i] - - expect( - stringsHaveMatchingValue( - profileBefore.email, - profileAfter.email, - true, - ), - ).toBe(true) - expect(profileBefore.mobilePhoneNumber).toBe( - profileAfter.mobilePhoneNumber, - ) - expect(profileAfter.lastNudge).toEqual(null) - expect(profileAfter.nextNudge).toEqual(null) - } - }) - - it.each([ - { - mobilePhoneNumberVerified: true, - }, - { - emailVerified: true, - }, - { - mobilePhoneNumberVerified: true, - emailVerified: true, - }, - ])( - 'should only change lastNudge=modified if modified date is more recent than export date and either email or phone were verified', - async (verifiedPart) => { - // Arrange - const advaniaProfiles = new Array(10) - .fill(null) - .map((_, index) => ({ - ssn: `${index}`, - email: `test${index}@test.local`, - mobilePhoneNumber: `888111${index}`, - // In the future to be more recent than modified date - exported: addDays(new Date(), -1), - })) - - const lastNudge = new Date(2023, 10, 1) - - const existingProfiles = advaniaProfiles.map((p) => ({ - nationalId: p.ssn, - // Email and phone random, different from advania profile - email: faker.internet.email(), - mobilePhoneNumber: faker.phone.phoneNumber('+354-#######'), - ...verifiedPart, - lastNudge, - })) - - // Act - await userProfileAdvaniaModel.bulkCreate(advaniaProfiles) - await userProfileModel.bulkCreate(existingProfiles) - - const userProfilesBeforeMigration = await userProfileModel.findAll() - - await workerService.run() - - const userProfilesAfterMigration = await userProfileModel.findAll() - - // Assert - for (let i = 0; i < userProfilesBeforeMigration.length; i += 1) { - const profileBefore = userProfilesBeforeMigration[i] - const profileAfter = userProfilesAfterMigration[i] - - expect( - stringsHaveMatchingValue( - profileBefore.email, - profileAfter.email, - true, - ), - ).toBe(true) - expect(profileBefore.mobilePhoneNumber).toBe( - profileAfter.mobilePhoneNumber, - ) - expect(profileAfter.lastNudge).toEqual(profileBefore.modified) - expect(profileAfter.nextNudge).toEqual( - addMonths(profileBefore.modified, NUDGE_INTERVAL), - ) - } - }, - ) - }) - - describe('existing user profile email or phone number edge cases', () => { - const exported = new Date(2023, 10, 1) - const modified = new Date(2023, 9, 1) - - const advaniaProfiles = new Array(4).fill(null).map((_, index) => ({ - ssn: `${index}`, - // Email and mobile phone number from advania is null - email: null, - mobilePhoneNumber: null, - exported, - })) - - const pickFields = (p: Partial) => - pick(p, [ - 'email', - 'emailVerified', - 'emailStatus', - 'mobilePhoneNumber', - 'mobilePhoneNumberVerified', - 'mobileStatus', - ]) - - it('should not replace an existing email with a null value during migration', async () => { - // Arrange - const existingProfiles: Partial[] = advaniaProfiles.map( - (p, index) => ({ - nationalId: p.ssn, - modified, - email: `test${index}@test.local`, // Existing profile has a defined email - emailVerified: true, - emailStatus: 'VERIFIED', - mobilePhoneNumber: null, - mobilePhoneNumberVerified: false, - mobileStatus: 'NOT_VERIFIED', - }), - ) - - // Act - await userProfileAdvaniaModel.bulkCreate(advaniaProfiles.slice()) - await userProfileModel.bulkCreate(existingProfiles) - - const userProfilesBeforeMigration = await userProfileModel.findAll() - - await workerService.run() - - const userProfilesAfterMigration = await userProfileModel.findAll() - - const existingProfileFields = existingProfiles.map(pickFields) - - // Assert - expect(userProfilesBeforeMigration.map(pickFields)).toEqual( - existingProfileFields, - ) - expect(userProfilesAfterMigration.map(pickFields)).toEqual( - existingProfileFields, - ) - }) - - it('should not replace an existing phone number with a null value during migration', async () => { - // Arrange - const existingProfiles: Partial[] = advaniaProfiles.map( - (p, index) => ({ - nationalId: p.ssn, - modified, - email: null, - emailVerified: false, - emailStatus: 'NOT_VERIFIED', - mobilePhoneNumber: `888111${index}`, // Existing profile has a defined phone number - mobilePhoneNumberVerified: true, - mobileStatus: 'VERIFIED', - }), - ) - - // Act - await userProfileAdvaniaModel.bulkCreate(advaniaProfiles.slice()) - await userProfileModel.bulkCreate(existingProfiles) - - const userProfilesBeforeMigration = await userProfileModel.findAll() - - await workerService.run() - - const userProfilesAfterMigration = await userProfileModel.findAll() - - const existingProfileFields = existingProfiles.map(pickFields) - - // Assert - expect(userProfilesBeforeMigration.map(pickFields)).toEqual( - existingProfileFields, - ) - expect(userProfilesAfterMigration.map(pickFields)).toEqual( - existingProfileFields, - ) - }) - }) - }) - }) - - describe('processProfiles', () => { - it('should create a new profile with lastNudge=null if advania profile does not exist', async () => { - // Arrange - const nationalId = '1' - - // Act - const existingUserProfileBefore = await userProfileModel.findOne({ - where: { - nationalId, - }, - }) - const migratedUserProfile = await userProfileAdvaniaModel.create({ - ssn: nationalId, - email: faker.internet.email(), - mobilePhoneNumber: faker.phone.phoneNumber('+354-#######'), - }) - - await workerService['processProfiles']( - migratedUserProfile, - existingUserProfileBefore, - ) - - const existingUserProfileAfter = await userProfileModel.findOne({ - where: { - nationalId, - }, - }) - - // Assert - expect(existingUserProfileBefore).toBe(null) - expect(existingUserProfileAfter.nationalId).toEqual( - migratedUserProfile.ssn, - ) - expect( - stringsHaveMatchingValue( - existingUserProfileAfter.email, - migratedUserProfile.email, - true, - ), - ).toBe(true) - expect(existingUserProfileAfter.mobilePhoneNumber).toEqual( - migratedUserProfile.mobilePhoneNumber, - ) - expect(existingUserProfileAfter.mobilePhoneNumber).toEqual( - migratedUserProfile.mobilePhoneNumber, - ) - expect(existingUserProfileAfter.lastNudge).toEqual(null) - expect(existingUserProfileAfter.nextNudge).toEqual(null) - }) - - describe('profile being migrated exists in user_profile', () => { - it('should keep user profile as is when email and phone number match and set lastNudge=nudgeLastAsked', async () => { - // Arrange - const nationalId = '1' - const nudgeLastAsked = addDays(new Date(), -10) - - // Act - const existingUserProfileBefore = await userProfileModel.create({ - nationalId, - email: 'email1@email.local', - mobilePhoneNumber: '01', - }) - const migratedUserProfile = await userProfileAdvaniaModel.create({ - ssn: nationalId, - email: existingUserProfileBefore.email, - mobilePhoneNumber: existingUserProfileBefore.mobilePhoneNumber, - nudgeLastAsked, - }) - - await workerService['processProfiles']( - migratedUserProfile, - existingUserProfileBefore, - ) - - const existingUserProfileAfter = await userProfileModel.findOne({ - where: { - nationalId, - }, - }) - - // Assert - expect(existingUserProfileBefore).not.toEqual(existingUserProfileAfter) - expect(existingUserProfileBefore.lastNudge).not.toEqual(nudgeLastAsked) - expect(existingUserProfileAfter.nextNudge).toEqual( - addMonths(nudgeLastAsked, NUDGE_INTERVAL), - ) - expect(existingUserProfileAfter.nationalId).toEqual( - migratedUserProfile.ssn, - ) - expect( - stringsHaveMatchingValue( - existingUserProfileAfter.email, - migratedUserProfile.email, - true, - ), - ).toBe(true) - expect(existingUserProfileAfter.mobilePhoneNumber).toEqual( - migratedUserProfile.mobilePhoneNumber, - ) - expect(existingUserProfileAfter.lastNudge).toEqual( - migratedUserProfile.nudgeLastAsked, - ) - }) - - describe('email and phone numbers do not match', () => { - it('should replace existing profile data when new profile exported date > existing profile modified date', async () => { - // Arrange - const nationalId = '1' - const nudgeLastAsked = addDays(new Date(), -10) - const existingProfileEmail = 'email1@email.local' - const existingProfilePhoneNumber = '01' - const migratedProfileEmail = 'email2@email.local' // different email - const migratedProfilePhoneNumber = '02' // and phone number - const migratedProfileCanNudge = true - // migrated in the future to be more recent than modified - const migratedProfileDate = addDays(new Date(), 1) - - // Act - const existingUserProfileBefore = await userProfileModel.create({ - nationalId, - email: existingProfileEmail, - mobilePhoneNumber: existingProfilePhoneNumber, - documentNotifications: !migratedProfileCanNudge, - }) - const migratedUserProfile = await userProfileAdvaniaModel.create({ - ssn: nationalId, - email: migratedProfileEmail, - mobilePhoneNumber: migratedProfilePhoneNumber, - nudgeLastAsked, - canNudge: migratedProfileCanNudge, - exported: migratedProfileDate, - }) - - await workerService['processProfiles']( - migratedUserProfile, - existingUserProfileBefore, - ) - - const existingUserProfileAfter = await userProfileModel.findOne({ - where: { - nationalId, - }, - }) - - // Assert - expect(existingUserProfileBefore).not.toEqual( - existingUserProfileAfter, - ) - expect( - stringsHaveMatchingValue( - existingUserProfileBefore.email, - migratedUserProfile.email, - true, - ), - ).toBe(false) - expect(existingUserProfileBefore.mobilePhoneNumber).not.toEqual( - migratedUserProfile.mobilePhoneNumber, - ) - expect(existingUserProfileAfter.nationalId).toEqual( - migratedUserProfile.ssn, - ) - // Expect email and phone to match after migration - expect( - stringsHaveMatchingValue( - existingUserProfileAfter.email, - migratedUserProfile.email, - true, - ), - ).toBe(true) - expect(existingUserProfileAfter.mobilePhoneNumber).toEqual( - migratedUserProfile.mobilePhoneNumber, - ) - // With last nudge set to null - expect(existingUserProfileAfter.lastNudge).toEqual(null) - expect(existingUserProfileAfter.nextNudge).toEqual(null) - - expect(existingUserProfileAfter.documentNotifications).toEqual( - migratedProfileCanNudge, - ) - }) - - describe('existing user profile was modified after the export occured', () => { - it.each([ - { - mobilePhoneNumberVerified: true, - }, - { - emailVerified: true, - }, - { - mobilePhoneNumberVerified: true, - emailVerified: true, - }, - ])( - 'should set lastNudge=modified when existing user profile has verified email or phone number', - async (verifiedPart) => { - // Arrange - const nationalId = '1' - const nudgeLastAsked = addDays(new Date(), -10) - const existingProfileEmail = 'email1@email.local' - const existingProfilePhoneNumber = '01' - const migratedProfileEmail = 'email2@email.local' // different email - const migratedProfilePhoneNumber = '02' // and phone number - const migratedProfileCanNudge = true - const migratedProfileDate = addDays(new Date(), -1) // yesterday - - // Act - const existingUserProfileBefore = await userProfileModel.create({ - nationalId, - email: existingProfileEmail, - mobilePhoneNumber: existingProfilePhoneNumber, - ...verifiedPart, - documentNotifications: !migratedProfileCanNudge, - }) - const migratedUserProfile = await userProfileAdvaniaModel.create({ - ssn: nationalId, - email: migratedProfileEmail, - mobilePhoneNumber: migratedProfilePhoneNumber, - nudgeLastAsked, - canNudge: migratedProfileCanNudge, - exported: migratedProfileDate, - }) - - await workerService['processProfiles']( - migratedUserProfile, - existingUserProfileBefore, - ) - - const existingUserProfileAfter = await userProfileModel.findOne({ - where: { - nationalId, - }, - }) - - // Assert - expect( - stringsHaveMatchingValue( - existingUserProfileAfter.email, - migratedUserProfile.email, - true, - ), - ).toBe(false) - expect(existingUserProfileAfter.mobilePhoneNumber).not.toEqual( - migratedUserProfile.mobilePhoneNumber, - ) - // With last nudge set to null - expect(existingUserProfileAfter.lastNudge).toEqual( - existingUserProfileBefore.modified, - ) - expect(existingUserProfileAfter.nextNudge).toEqual( - addMonths(existingUserProfileBefore.modified, NUDGE_INTERVAL), - ) - expect(existingUserProfileAfter.documentNotifications).toEqual( - existingUserProfileBefore.documentNotifications, - ) - }, - ) - - it('should set lastNudge=null if neither email nor phone is verified', async () => { - // Arrange - const nationalId = '1' - const nudgeLastAsked = addDays(new Date(), -10) - const existingProfileEmail = 'email1@email.local' - const existingProfilePhoneNumber = '01' - const migratedProfileEmail = 'email2@email.local' // different email - const migratedProfilePhoneNumber = '02' // and phone number - const migratedProfileCanNudge = true - const migratedProfileDate = addDays(new Date(), -1) // yesterday - - // Act - const existingUserProfileBefore = await userProfileModel.create({ - nationalId, - email: existingProfileEmail, - mobilePhoneNumber: existingProfilePhoneNumber, - mobilePhoneNumberVerified: false, - emailVerified: false, - documentNotifications: !migratedProfileCanNudge, - }) - const migratedUserProfile = await userProfileAdvaniaModel.create({ - ssn: nationalId, - email: migratedProfileEmail, - mobilePhoneNumber: migratedProfilePhoneNumber, - nudgeLastAsked, - canNudge: migratedProfileCanNudge, - exported: migratedProfileDate, - }) - - await workerService['processProfiles']( - migratedUserProfile, - existingUserProfileBefore, - ) - - const existingUserProfileAfter = await userProfileModel.findOne({ - where: { - nationalId, - }, - }) - - // Assert - expect( - stringsHaveMatchingValue( - existingUserProfileAfter.email, - migratedUserProfile.email, - true, - ), - ).toBe(false) - expect(existingUserProfileAfter.mobilePhoneNumber).not.toEqual( - migratedUserProfile.mobilePhoneNumber, - ) - // With last nudge set to null - expect(existingUserProfileAfter.lastNudge).toEqual(null) - expect(existingUserProfileAfter.nextNudge).toEqual(null) - expect(existingUserProfileAfter.documentNotifications).toEqual( - existingUserProfileBefore.documentNotifications, - ) - }) - }) - }) - }) - }) - - it('should lower case emails coming from advania', async () => { - // Arrange - const advaniaProfiles = new Array(10).fill(null).map((_, index) => ({ - ssn: `${index}`, - email: `Test${index}@test.Local`, - mobilePhoneNumber: `888111${index}`, - exported: new Date(2023, 10, 1), - })) - - // Act - await userProfileAdvaniaModel.bulkCreate(advaniaProfiles) - const advaniaProfilesBeforeMigration = - await userProfileAdvaniaModel.findAll() - const userProfilesBeforeMigration = await userProfileModel.findAll() - await workerService.run() - const advaniaProfilesAfterMigration = - await userProfileAdvaniaModel.findAll() - const userProfilesAfterMigration = await userProfileModel.findAll() - - // Assert - expect( - advaniaProfilesBeforeMigration.every( - (p) => p.status === ProcessedStatus.PENDING, - ), - ).toBe(true) - expect(userProfilesBeforeMigration.length).toBe(0) - expect( - advaniaProfilesAfterMigration.every( - (p) => p.status === ProcessedStatus.DONE, - ), - ).toBe(true) - expect( - userProfilesAfterMigration.every((p) => { - const matchingProfile = advaniaProfilesAfterMigration.find( - (ap) => ap.ssn === p.nationalId, - ) - - return ( - stringsHaveMatchingValue(matchingProfile.email, p.email, true) && - matchingProfile.mobilePhoneNumber === p.mobilePhoneNumber - ) - }), - ).toBe(true) - }) - - afterEach(async () => { - await app.cleanUp() - }) -}) diff --git a/apps/services/user-profile/src/app/worker/types.ts b/apps/services/user-profile/src/app/worker/types.ts deleted file mode 100644 index 3bdbffc2e8d7..000000000000 --- a/apps/services/user-profile/src/app/worker/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum ProcessedStatus { - PENDING = 'PENDING', - DONE = 'DONE', - ERROR = 'ERROR', -} diff --git a/apps/services/user-profile/src/app/worker/userProfileAdvania.model.ts b/apps/services/user-profile/src/app/worker/userProfileAdvania.model.ts deleted file mode 100644 index 3893b2dd6768..000000000000 --- a/apps/services/user-profile/src/app/worker/userProfileAdvania.model.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - Column, - DataType, - Model, - Table, - CreatedAt, - UpdatedAt, -} from 'sequelize-typescript' -import { ApiProperty } from '@nestjs/swagger' - -import { ProcessedStatus } from './types' - -@Table({ - tableName: 'user_profile_advania', - timestamps: true, - indexes: [ - { - fields: ['ssn'], - }, - ], -}) -export class UserProfileAdvania extends Model { - @CreatedAt - @ApiProperty() - created!: Date - - @UpdatedAt - @ApiProperty() - modified!: Date - - @Column({ - type: DataType.STRING, - allowNull: false, - unique: true, - primaryKey: true, - }) - @ApiProperty() - ssn!: string - - @Column({ - type: DataType.STRING, - allowNull: true, - }) - @ApiProperty() - email?: string - - @Column({ - type: DataType.STRING, - allowNull: true, - }) - @ApiProperty() - mobilePhoneNumber?: string - - @Column({ - type: DataType.BOOLEAN, - allowNull: true, - }) - @ApiProperty() - canNudge?: boolean - - @Column({ - type: DataType.DATE, - allowNull: true, - }) - @ApiProperty() - nudgeLastAsked?: Date - - @Column({ - type: DataType.DATE, - allowNull: true, - }) - @ApiProperty() - exported?: Date - - @Column({ - type: DataType.ENUM('PENDING', 'DONE', 'ERROR'), - defaultValue: 'PENDING', - }) - @ApiProperty({ - description: 'Indicates processed status', - enum: ProcessedStatus, - }) - status?: ProcessedStatus -} diff --git a/apps/services/user-profile/src/app/worker/worker.module.ts b/apps/services/user-profile/src/app/worker/worker.module.ts deleted file mode 100644 index b58e54a66566..000000000000 --- a/apps/services/user-profile/src/app/worker/worker.module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Module } from '@nestjs/common' -import { SequelizeModule } from '@nestjs/sequelize' - -import { AuditModule } from '@island.is/nest/audit' -import { LoggingModule } from '@island.is/logging' - -import { SequelizeConfigService } from '../sequelizeConfig.service' - -import { environment } from '../../environments' -import { UserProfileWorkerService } from './worker.service' -import { UserProfile } from '../user-profile/userProfile.model' -import { UserProfileAdvania } from './userProfileAdvania.model' -import { UserProfileConfig } from '../../config' -import { ConfigModule } from '@island.is/nest/config' - -@Module({ - imports: [ - AuditModule.forRoot(environment.audit), - ConfigModule.forRoot({ - isGlobal: true, - load: [UserProfileConfig], - }), - LoggingModule, - SequelizeModule.forRootAsync({ - useClass: SequelizeConfigService, - }), - SequelizeModule.forFeature([UserProfile, UserProfileAdvania]), - ], - providers: [UserProfileWorkerService], -}) -export class UserProfileWorkerModule {} diff --git a/apps/services/user-profile/src/app/worker/worker.service.ts b/apps/services/user-profile/src/app/worker/worker.service.ts deleted file mode 100644 index 8236ee4e9cd1..000000000000 --- a/apps/services/user-profile/src/app/worker/worker.service.ts +++ /dev/null @@ -1,222 +0,0 @@ -import formatDistance from 'date-fns/formatDistance' - -import { Inject, Injectable } from '@nestjs/common' -import addMonths from 'date-fns/addMonths' - -import { logger } from '@island.is/logging' -import type { ConfigType } from '@island.is/nest/config' - -import { InjectModel } from '@nestjs/sequelize' -import { UserProfile } from '../user-profile/userProfile.model' -import { UserProfileAdvania } from './userProfileAdvania.model' -import { ProcessedStatus } from './types' -import { - chooseEmailAndPhoneNumberFields, - hasMatchingContactInfo, -} from './worker.utils' -import { NUDGE_INTERVAL } from '../v2/user-profile.service' -import { UserProfileConfig } from '../../config' - -/** - * The purpose of this worker is to import user profiles from Advania - */ -@Injectable() -export class UserProfileWorkerService { - constructor( - @InjectModel(UserProfile) - private readonly userProfileModel: typeof UserProfile, - @InjectModel(UserProfileAdvania) - private readonly userProfileAdvaniaModel: typeof UserProfileAdvania, - @Inject(UserProfileConfig.KEY) - private config: ConfigType, - ) {} - - public async run() { - const timer = logger.startTimer() - logger.info('Worker starting...') - - await this.migrateUserProfiles() - - logger.info('Worker finished.') - timer.done() - } - - private async processProfiles( - advaniaProfile: UserProfileAdvania, - existingUserProfile?: UserProfile, - ) { - if (!existingUserProfile) { - return this.userProfileModel.create({ - nationalId: advaniaProfile.ssn, - email: advaniaProfile.email?.toLowerCase?.(), - mobilePhoneNumber: advaniaProfile.mobilePhoneNumber, - lastNudge: null, - nextNudge: null, - documentNotifications: advaniaProfile.canNudge === true, - }) - } - - if (hasMatchingContactInfo(advaniaProfile, existingUserProfile)) { - return this.userProfileModel.upsert({ - nationalId: advaniaProfile.ssn, - lastNudge: advaniaProfile.nudgeLastAsked, - nextNudge: addMonths(advaniaProfile.nudgeLastAsked, NUDGE_INTERVAL), - }) - } - - if (existingUserProfile.modified <= advaniaProfile.exported) { - const emailAndPhoneFields = chooseEmailAndPhoneNumberFields( - advaniaProfile, - existingUserProfile, - ) - - return this.userProfileModel.upsert({ - nationalId: advaniaProfile.ssn, - ...emailAndPhoneFields, - documentNotifications: advaniaProfile.canNudge === true, - lastNudge: null, - nextNudge: null, - }) - } - - const { nationalId, emailVerified, mobilePhoneNumberVerified, modified } = - existingUserProfile - - return this.userProfileModel.upsert({ - nationalId, - lastNudge: emailVerified || mobilePhoneNumberVerified ? modified : null, - nextNudge: - emailVerified || mobilePhoneNumberVerified - ? addMonths(modified, NUDGE_INTERVAL) - : null, - }) - } - - private async migrateUserProfiles() { - logger.info('migrateUserProfiles') - - const numberOfProfilesToProcess = await this.userProfileAdvaniaModel.count({ - where: { - status: ProcessedStatus.PENDING, - }, - }) - - logger.info(`${numberOfProfilesToProcess} profiles to process`) - - const numberOfPagesToProcess = Math.ceil( - numberOfProfilesToProcess / this.config.workerProcessPageSize, - ) - logger.info( - `splitting work into ${numberOfPagesToProcess} pages with page_size=${this.config.workerProcessPageSize}`, - ) - - const startTime = Date.now() - - for ( - let pageIndex = 0; - pageIndex < numberOfPagesToProcess; - pageIndex += 1 - ) { - const nationalIdsProcessed: string[] = [] - const nationalIdsWithError: string[] = [] - - logger.info( - `processing page ${pageIndex + 1} / ${numberOfPagesToProcess} ...`, - ) - - const advaniaProfiles = await this.userProfileAdvaniaModel.findAll({ - where: { - status: ProcessedStatus.PENDING, - }, - limit: this.config.workerProcessPageSize, - }) - - const userProfiles = await this.userProfileModel.findAll({ - where: { - nationalId: advaniaProfiles.map((p) => p.ssn), - }, - }) - - for (const advaniaProfile of advaniaProfiles) { - const existingUserProfile = userProfiles.find( - (p) => p.nationalId === advaniaProfile.ssn, - ) - - try { - await this.processProfiles(advaniaProfile, existingUserProfile) - nationalIdsProcessed.push(advaniaProfile.ssn) - } catch (e) { - logger.error(`processProfiles.error: ${e?.message}`) - nationalIdsWithError.push(advaniaProfile.ssn) - } - } - - logger.info( - `processing for page ${ - pageIndex + 1 - } / ${numberOfPagesToProcess} done, updating status ...`, - ) - - if (nationalIdsProcessed.length > 0) { - logger.info( - `updating status to ${ProcessedStatus.DONE} for ${nationalIdsProcessed.length} profiles`, - ) - await this.userProfileAdvaniaModel.update( - { - status: ProcessedStatus.DONE, - }, - { - where: { - ssn: nationalIdsProcessed, - }, - }, - ) - } - - if (nationalIdsWithError.length > 0) { - logger.info( - `updating status to ${ProcessedStatus.ERROR} for ${nationalIdsWithError.length} profiles`, - ) - await this.userProfileAdvaniaModel.update( - { - status: ProcessedStatus.ERROR, - }, - { - where: { - ssn: nationalIdsWithError, - }, - }, - ) - } - - if (numberOfPagesToProcess > 1) { - this.logTimeRemaining(startTime, pageIndex, numberOfProfilesToProcess) - } - } - } - - private logTimeRemaining( - startTime: number, - currentPageIndex: number, - numberOfProfilesToProcess: number, - ) { - const timeElapsed = Date.now() - startTime - const profilesProcessed = - (currentPageIndex + 1) * this.config.workerProcessPageSize - const msPerProfile = timeElapsed / profilesProcessed - const timeRemaining = - (numberOfProfilesToProcess - profilesProcessed) * msPerProfile - const estimatedDateWhenFinished = new Date(Date.now() + timeRemaining) - - logger.info( - `the migration should finish ${formatDistance( - estimatedDateWhenFinished, - new Date(), - { - addSuffix: true, - includeSeconds: true, - }, - )} (based on average processing time)`, - ) - } -} diff --git a/apps/services/user-profile/src/app/worker/worker.utils.ts b/apps/services/user-profile/src/app/worker/worker.utils.ts deleted file mode 100644 index 01dbcb1c6301..000000000000 --- a/apps/services/user-profile/src/app/worker/worker.utils.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { UserProfile } from '../user-profile/userProfile.model' -import { UserProfileAdvania } from './userProfileAdvania.model' - -const stringHasValue = (value?: string) => - typeof value === 'string' && value.length > 0 - -export const stringsHaveMatchingValue = ( - a?: string, - b?: string, - forceLowerCase = false, -) => { - const valueOfA = forceLowerCase ? a?.toLowerCase?.() : a - const valueOfB = forceLowerCase ? b?.toLowerCase?.() : b - - return stringHasValue(a) && valueOfA === valueOfB -} - -export const hasMatchingContactInfo = ( - migratedProfile: UserProfileAdvania, - existingProfile?: UserProfile, -) => { - const migratedPhoneNumber = migratedProfile.mobilePhoneNumber - const existingPhoneNumber = existingProfile.mobilePhoneNumber - - const migratedEmail = migratedProfile.email - const existingEmail = existingProfile.email - - const matchingPhoneNumbers = - stringHasValue(existingPhoneNumber) && - existingPhoneNumber === migratedPhoneNumber - - const matchingEmails = stringsHaveMatchingValue( - existingEmail, - migratedEmail, - true, - ) - - return matchingPhoneNumbers && matchingEmails -} - -export const chooseEmailFields = ( - migratedProfile: UserProfileAdvania, - existingProfile: UserProfile, -): Pick => { - if (stringHasValue(migratedProfile.email)) { - return { - email: migratedProfile.email, - emailVerified: false, - emailStatus: 'NOT_VERIFIED', - } - } - - return { - email: existingProfile.email, - emailVerified: existingProfile.emailVerified, - emailStatus: existingProfile.emailStatus, - } -} - -export const choosePhoneNumberFields = ( - migratedProfile: UserProfileAdvania, - existingProfile: UserProfile, -): Pick< - UserProfile, - 'mobilePhoneNumber' | 'mobilePhoneNumberVerified' | 'mobileStatus' -> => { - if (stringHasValue(migratedProfile.mobilePhoneNumber)) { - return { - mobilePhoneNumber: migratedProfile.mobilePhoneNumber, - mobilePhoneNumberVerified: false, - mobileStatus: 'NOT_VERIFIED', - } - } - - return { - mobilePhoneNumber: existingProfile.mobilePhoneNumber, - mobilePhoneNumberVerified: existingProfile.mobilePhoneNumberVerified, - mobileStatus: existingProfile.mobileStatus, - } -} - -export const chooseEmailAndPhoneNumberFields = ( - migratedProfile: UserProfileAdvania, - existingProfile: UserProfile, -): ReturnType & - ReturnType => ({ - ...chooseEmailFields(migratedProfile, existingProfile), - ...choosePhoneNumberFields(migratedProfile, existingProfile), -}) diff --git a/apps/services/user-profile/src/config.ts b/apps/services/user-profile/src/config.ts index 7b8e2caefebf..e9bcd2ac1900 100644 --- a/apps/services/user-profile/src/config.ts +++ b/apps/services/user-profile/src/config.ts @@ -3,7 +3,6 @@ import { z } from 'zod' const schema = z.object({ migrationDate: z.date(), - workerProcessPageSize: z.number(), email: z.object({ fromEmail: z.string(), fromName: z.string(), @@ -19,10 +18,6 @@ export const UserProfileConfig = defineConfig({ migrationDate: new Date( env.optional('USER_PROFILE_MIGRATION_DATE') ?? '2024-04-12', ), - workerProcessPageSize: env.optionalJSON( - 'USER_PROFILE_WORKER_PAGE_SIZE', - 3000, - ), email: { fromEmail: env.required('EMAIL_FROM', 'noreply@island.is'), fromName: env.required('EMAIL_FROM_NAME', 'island.is'), diff --git a/apps/services/user-profile/src/environments/environment.ts b/apps/services/user-profile/src/environments/environment.ts index 2d77bb5eade6..7d35b9b3ef5d 100644 --- a/apps/services/user-profile/src/environments/environment.ts +++ b/apps/services/user-profile/src/environments/environment.ts @@ -4,13 +4,13 @@ const devConfig = { smsOptions: { url: 'https://smsapi.devnova.is', username: 'IslandIs_User_Development', - password: process.env.NOVA_PASSWORD, + password: process.env.NOVA_PASSWORD ?? '', acceptUnauthorized: true, }, islykillConfig: { - cert: process.env.ISLYKILL_CERT, - basePath: process.env.ISLYKILL_SERVICE_BASEPATH, - passphrase: process.env.ISLYKILL_SERVICE_PASSPHRASE, + cert: process.env.ISLYKILL_CERT ?? '', + basePath: process.env.ISLYKILL_SERVICE_BASEPATH ?? '', + passphrase: process.env.ISLYKILL_SERVICE_PASSPHRASE ?? '', }, emailOptions: { useTestAccount: true, @@ -31,20 +31,20 @@ const prodConfig = { production: true, port: 3333, smsOptions: { - url: process.env.NOVA_URL, - username: process.env.NOVA_USERNAME, - password: process.env.NOVA_PASSWORD, + url: process.env.NOVA_URL ?? '', + username: process.env.NOVA_USERNAME ?? '', + password: process.env.NOVA_PASSWORD ?? '', acceptUnauthorized: process.env.NOVA_ACCEPT_UNAUTHORIZED === 'true', }, islykillConfig: { - cert: process.env.ISLYKILL_CERT, - basePath: process.env.ISLYKILL_SERVICE_BASEPATH, - passphrase: process.env.ISLYKILL_SERVICE_PASSPHRASE, + cert: process.env.ISLYKILL_CERT ?? '', + basePath: process.env.ISLYKILL_SERVICE_BASEPATH ?? '', + passphrase: process.env.ISLYKILL_SERVICE_PASSPHRASE ?? '', }, emailOptions: { useTestAccount: false, options: { - region: process.env.EMAIL_REGION, + region: process.env.EMAIL_REGION ?? '', }, }, audit: { @@ -53,7 +53,7 @@ const prodConfig = { serviceName: 'services-user-profile', }, auth: { - issuer: process.env.IDENTITY_SERVER_ISSUER_URL, + issuer: process.env.IDENTITY_SERVER_ISSUER_URL ?? '', audience: ['@island.is', '@admin.island.is'], }, } diff --git a/apps/services/user-profile/src/main.ts b/apps/services/user-profile/src/main.ts index 1af0d6c5a735..b4909915d594 100644 --- a/apps/services/user-profile/src/main.ts +++ b/apps/services/user-profile/src/main.ts @@ -1,18 +1,13 @@ -import { bootstrap, processJob } from '@island.is/infra-nest-server' +import { bootstrap } from '@island.is/infra-nest-server' + import { AppModule } from './app/app.module' -import { openApi } from './openApi' import { environment } from './environments' +import { openApi } from './openApi' -const job = processJob() - -if (job === 'worker') { - import('./worker').then((app) => app.worker()) -} else { - bootstrap({ - appModule: AppModule, - name: 'services-user-profile', - openApi, - port: environment.port, - enableVersioning: true, - }) -} +bootstrap({ + appModule: AppModule, + name: 'services-user-profile', + openApi, + port: environment.port, + enableVersioning: true, +}) diff --git a/apps/services/user-profile/src/worker.ts b/apps/services/user-profile/src/worker.ts deleted file mode 100644 index a2c9161cd9de..000000000000 --- a/apps/services/user-profile/src/worker.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NestFactory } from '@nestjs/core' -import { UserProfileWorkerModule } from './app/worker/worker.module' -import { UserProfileWorkerService } from './app/worker/worker.service' - -export const worker = async () => { - const app = await NestFactory.createApplicationContext( - UserProfileWorkerModule, - ) - app.enableShutdownHooks() - await app.get(UserProfileWorkerService).run() - await app.close() - process.exit(0) -} diff --git a/apps/services/user-profile/tsconfig.json b/apps/services/user-profile/tsconfig.json index b39f67a829dc..25873177db3f 100644 --- a/apps/services/user-profile/tsconfig.json +++ b/apps/services/user-profile/tsconfig.json @@ -1,12 +1,7 @@ { "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "strict": false - }, "files": [], "include": [], - "experimentalDecorators": true, - "allowJs": true, "references": [ { "path": "./tsconfig.app.json" diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index be779edfb337..67453cc014a4 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -2155,7 +2155,6 @@ service-portal-api: NOVA_ACCEPT_UNAUTHORIZED: 'true' SERVERSIDE_FEATURES_ON: '' SERVICE_PORTAL_BASE_URL: 'https://beta.dev01.devland.is/minarsidur' - USER_PROFILE_WORKER_PAGE_SIZE: '3000' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.dev01.devland.is/r1/IS-DEV' XROAD_CLIENT_ID: 'IS-DEV/GOV/10000/island-is-client' @@ -2260,91 +2259,6 @@ service-portal-api: eks.amazonaws.com/role-arn: 'arn:aws:iam::013313053092:role/service-portal-api' create: true name: 'service-portal-api' -service-portal-api-worker: - args: - - 'main.js' - - '--job=worker' - command: - - 'node' - enabled: true - env: - AUTH_DELEGATION_API_URL: 'http://web-services-auth-delegation-api.identity-server-delegation.svc.cluster.local' - AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' - DB_HOST: 'postgres-applications.internal' - DB_NAME: 'service_portal_api' - DB_REPLICAS_HOST: 'postgres-applications-reader.internal' - DB_USER: 'service_portal_api' - EMAIL_REGION: 'eu-west-1' - IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is' - ISLYKILL_CERT: '/etc/config/islyklar.p12' - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=921 -r dd-trace/init' - NOVA_ACCEPT_UNAUTHORIZED: 'true' - SERVERSIDE_FEATURES_ON: '' - SERVICE_PORTAL_BASE_URL: 'https://beta.dev01.devland.is/minarsidur' - USER_PROFILE_WORKER_PAGE_SIZE: '3000' - files: - - 'islyklar.p12' - grantNamespaces: - - 'nginx-ingress-internal' - - 'nginx-ingress-external' - - 'islandis' - - 'user-notification' - - 'identity-server' - grantNamespacesEnabled: true - healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/' - timeoutSeconds: 3 - hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 3 - min: 1 - image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/services-user-profile' - namespace: 'service-portal' - podDisruptionBudget: - maxUnavailable: 1 - pvcs: [] - replicaCount: - default: 1 - max: 3 - min: 1 - resources: - limits: - cpu: '800m' - memory: '1024Mi' - requests: - cpu: '400m' - memory: '512Mi' - schedule: '0 0 31 12 *' - secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' - DB_PASS: '/k8s/service-portal-api/DB_PASSWORD' - EMAIL_FROM: '/k8s/service-portal/api/EMAIL_FROM' - EMAIL_FROM_NAME: '/k8s/service-portal/api/EMAIL_FROM_NAME' - EMAIL_REPLY_TO: '/k8s/service-portal/api/EMAIL_REPLY_TO' - EMAIL_REPLY_TO_NAME: '/k8s/service-portal/api/EMAIL_REPLY_TO_NAME' - IDENTITY_SERVER_CLIENT_ID: '/k8s/service-portal/api/SERVICE_PORTAL_API_CLIENT_ID' - IDENTITY_SERVER_CLIENT_SECRET: '/k8s/service-portal/api/SERVICE_PORTAL_API_CLIENT_SECRET' - ISLYKILL_SERVICE_BASEPATH: '/k8s/api/ISLYKILL_SERVICE_BASEPATH' - ISLYKILL_SERVICE_PASSPHRASE: '/k8s/api/ISLYKILL_SERVICE_PASSPHRASE' - NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/api/NATIONAL_REGISTRY_B2C_CLIENT_SECRET' - NOVA_PASSWORD: '/k8s/gjafakort/NOVA_PASSWORD' - NOVA_URL: '/k8s/service-portal-api/NOVA_URL' - NOVA_USERNAME: '/k8s/gjafakort/NOVA_USERNAME' - securityContext: - allowPrivilegeEscalation: false - privileged: false services-documents: enabled: true env: diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 7b8bb1a307de..4232bb9e9609 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -2026,7 +2026,6 @@ service-portal-api: NOVA_ACCEPT_UNAUTHORIZED: 'false' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' SERVICE_PORTAL_BASE_URL: 'https://island.is/minarsidur' - USER_PROFILE_WORKER_PAGE_SIZE: '3000' XROAD_BASE_PATH: 'http://securityserver.island.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.island.is/r1/IS' XROAD_CLIENT_ID: 'IS/GOV/5501692829/island-is-client' @@ -2131,91 +2130,6 @@ service-portal-api: eks.amazonaws.com/role-arn: 'arn:aws:iam::251502586493:role/service-portal-api' create: true name: 'service-portal-api' -service-portal-api-worker: - args: - - 'main.js' - - '--job=worker' - command: - - 'node' - enabled: true - env: - AUTH_DELEGATION_API_URL: 'https://auth-delegation-api.internal.innskra.island.is' - AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' - DB_HOST: 'postgres-applications.internal' - DB_NAME: 'service_portal_api' - DB_REPLICAS_HOST: 'postgres-applications.internal' - DB_USER: 'service_portal_api' - EMAIL_REGION: 'eu-west-1' - IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is' - ISLYKILL_CERT: '/etc/config/islyklar.p12' - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=921 -r dd-trace/init' - NOVA_ACCEPT_UNAUTHORIZED: 'false' - SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' - SERVICE_PORTAL_BASE_URL: 'https://island.is/minarsidur' - USER_PROFILE_WORKER_PAGE_SIZE: '3000' - files: - - 'islyklar.p12' - grantNamespaces: - - 'nginx-ingress-internal' - - 'nginx-ingress-external' - - 'islandis' - - 'user-notification' - - 'identity-server' - grantNamespacesEnabled: true - healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/' - timeoutSeconds: 3 - hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 10 - min: 3 - image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/services-user-profile' - namespace: 'service-portal' - podDisruptionBudget: - maxUnavailable: 1 - pvcs: [] - replicaCount: - default: 3 - max: 10 - min: 3 - resources: - limits: - cpu: '800m' - memory: '1024Mi' - requests: - cpu: '400m' - memory: '512Mi' - schedule: '0 0 31 12 *' - secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' - DB_PASS: '/k8s/service-portal-api/DB_PASSWORD' - EMAIL_FROM: '/k8s/service-portal/api/EMAIL_FROM' - EMAIL_FROM_NAME: '/k8s/service-portal/api/EMAIL_FROM_NAME' - EMAIL_REPLY_TO: '/k8s/service-portal/api/EMAIL_REPLY_TO' - EMAIL_REPLY_TO_NAME: '/k8s/service-portal/api/EMAIL_REPLY_TO_NAME' - IDENTITY_SERVER_CLIENT_ID: '/k8s/service-portal/api/SERVICE_PORTAL_API_CLIENT_ID' - IDENTITY_SERVER_CLIENT_SECRET: '/k8s/service-portal/api/SERVICE_PORTAL_API_CLIENT_SECRET' - ISLYKILL_SERVICE_BASEPATH: '/k8s/api/ISLYKILL_SERVICE_BASEPATH' - ISLYKILL_SERVICE_PASSPHRASE: '/k8s/api/ISLYKILL_SERVICE_PASSPHRASE' - NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/api/NATIONAL_REGISTRY_B2C_CLIENT_SECRET' - NOVA_PASSWORD: '/k8s/gjafakort/NOVA_PASSWORD' - NOVA_URL: '/k8s/service-portal-api/NOVA_URL' - NOVA_USERNAME: '/k8s/gjafakort/NOVA_USERNAME' - securityContext: - allowPrivilegeEscalation: false - privileged: false services-documents: enabled: true env: diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index 252551794274..955f0860998f 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -1898,7 +1898,6 @@ service-portal-api: NOVA_ACCEPT_UNAUTHORIZED: 'false' SERVERSIDE_FEATURES_ON: '' SERVICE_PORTAL_BASE_URL: 'https://beta.staging01.devland.is/minarsidur' - USER_PROFILE_WORKER_PAGE_SIZE: '3000' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.staging01.devland.is/r1/IS-TEST' XROAD_CLIENT_ID: 'IS-TEST/GOV/5501692829/island-is-client' @@ -2003,91 +2002,6 @@ service-portal-api: eks.amazonaws.com/role-arn: 'arn:aws:iam::261174024191:role/service-portal-api' create: true name: 'service-portal-api' -service-portal-api-worker: - args: - - 'main.js' - - '--job=worker' - command: - - 'node' - enabled: true - env: - AUTH_DELEGATION_API_URL: 'http://web-services-auth-delegation-api.identity-server-delegation.svc.cluster.local' - AUTH_DELEGATION_MACHINE_CLIENT_SCOPE: '["@island.is/auth/delegations/index:system"]' - DB_HOST: 'postgres-applications.internal' - DB_NAME: 'service_portal_api' - DB_REPLICAS_HOST: 'postgres-applications.internal' - DB_USER: 'service_portal_api' - EMAIL_REGION: 'eu-west-1' - IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is' - ISLYKILL_CERT: '/etc/config/islyklar.p12' - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=921 -r dd-trace/init' - NOVA_ACCEPT_UNAUTHORIZED: 'false' - SERVERSIDE_FEATURES_ON: '' - SERVICE_PORTAL_BASE_URL: 'https://beta.staging01.devland.is/minarsidur' - USER_PROFILE_WORKER_PAGE_SIZE: '3000' - files: - - 'islyklar.p12' - grantNamespaces: - - 'nginx-ingress-internal' - - 'nginx-ingress-external' - - 'islandis' - - 'user-notification' - - 'identity-server' - grantNamespacesEnabled: true - healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/' - timeoutSeconds: 3 - hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 3 - min: 1 - image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/services-user-profile' - namespace: 'service-portal' - podDisruptionBudget: - maxUnavailable: 1 - pvcs: [] - replicaCount: - default: 1 - max: 3 - min: 1 - resources: - limits: - cpu: '800m' - memory: '1024Mi' - requests: - cpu: '400m' - memory: '512Mi' - schedule: '0 0 31 12 *' - secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' - DB_PASS: '/k8s/service-portal-api/DB_PASSWORD' - EMAIL_FROM: '/k8s/service-portal/api/EMAIL_FROM' - EMAIL_FROM_NAME: '/k8s/service-portal/api/EMAIL_FROM_NAME' - EMAIL_REPLY_TO: '/k8s/service-portal/api/EMAIL_REPLY_TO' - EMAIL_REPLY_TO_NAME: '/k8s/service-portal/api/EMAIL_REPLY_TO_NAME' - IDENTITY_SERVER_CLIENT_ID: '/k8s/service-portal/api/SERVICE_PORTAL_API_CLIENT_ID' - IDENTITY_SERVER_CLIENT_SECRET: '/k8s/service-portal/api/SERVICE_PORTAL_API_CLIENT_SECRET' - ISLYKILL_SERVICE_BASEPATH: '/k8s/api/ISLYKILL_SERVICE_BASEPATH' - ISLYKILL_SERVICE_PASSPHRASE: '/k8s/api/ISLYKILL_SERVICE_PASSPHRASE' - NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/api/NATIONAL_REGISTRY_B2C_CLIENT_SECRET' - NOVA_PASSWORD: '/k8s/gjafakort/NOVA_PASSWORD' - NOVA_URL: '/k8s/service-portal-api/NOVA_URL' - NOVA_USERNAME: '/k8s/gjafakort/NOVA_USERNAME' - securityContext: - allowPrivilegeEscalation: false - privileged: false services-documents: enabled: true env: diff --git a/infra/src/uber-charts/islandis.ts b/infra/src/uber-charts/islandis.ts index 03a711520629..0124521b7447 100644 --- a/infra/src/uber-charts/islandis.ts +++ b/infra/src/uber-charts/islandis.ts @@ -10,10 +10,7 @@ import { } from '../../../apps/application-system/api/infra/application-system-api' import { serviceSetup as appSystemFormSetup } from '../../../apps/application-system/form/infra/application-system-form' -import { - serviceSetup as servicePortalApiSetup, - workerSetup as servicePortalUserProfileWorker, -} from '../../../apps/services/user-profile/infra/service-portal-api' +import { serviceSetup as servicePortalApiSetup } from '../../../apps/services/user-profile/infra/service-portal-api' import { serviceSetup as servicePortalSetup } from '../../../apps/service-portal/infra/service-portal' import { serviceSetup as adminPortalSetup } from '../../../apps/portals/admin/infra/portals-admin' @@ -78,7 +75,6 @@ const appSystemApi = appSystemApiSetup({ const appSystemApiWorker = appSystemApiWorkerSetup() const servicePortalApi = servicePortalApiSetup() -const servicePortalWorker = servicePortalUserProfileWorker() const adminPortal = adminPortalSetup() const nameRegistryBackend = serviceNameRegistryBackendSetup() @@ -146,7 +142,6 @@ export const Services: EnvironmentServices = { appSystemForm, servicePortal, servicePortalApi, - servicePortalWorker, adminPortal, api, consultationPortal, @@ -182,7 +177,6 @@ export const Services: EnvironmentServices = { appSystemForm, servicePortal, servicePortalApi, - servicePortalWorker, adminPortal, api, consultationPortal, @@ -216,7 +210,6 @@ export const Services: EnvironmentServices = { appSystemForm, servicePortal, servicePortalApi, - servicePortalWorker, adminPortal, consultationPortal, api, diff --git a/libs/api/domains/user-profile/src/lib/userProfile.model.ts b/libs/api/domains/user-profile/src/lib/userProfile.model.ts index 2e732fc7ff89..534499fc5091 100644 --- a/libs/api/domains/user-profile/src/lib/userProfile.model.ts +++ b/libs/api/domains/user-profile/src/lib/userProfile.model.ts @@ -6,13 +6,13 @@ export class UserProfile { nationalId?: string @Field(() => String, { nullable: true }) - mobilePhoneNumber?: string + mobilePhoneNumber?: string | null @Field(() => String, { nullable: true }) locale?: string @Field(() => String, { nullable: true }) - email?: string + email?: string | null @Field(() => Boolean) emailVerified!: boolean @@ -24,7 +24,7 @@ export class UserProfile { documentNotifications!: boolean @Field(() => Boolean, { nullable: true }) - needsNudge?: boolean + needsNudge?: boolean | null @Field(() => String, { nullable: true }) emailStatus?: string