Skip to content

Commit

Permalink
Refactor alive status check into separate service. Add check to legal…
Browse files Browse the repository at this point in the history
… repr.
  • Loading branch information
valurefugl committed Oct 7, 2024
1 parent 8594e56 commit 4c3279d
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 202 deletions.
109 changes: 109 additions & 0 deletions libs/auth-api-lib/src/lib/delegations/alive-status.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Inject, Logger } from '@nestjs/common'
import * as kennitala from 'kennitala'

import {
IndividualDto,
NationalRegistryClientService,
} from '@island.is/clients/national-registry-v2'
import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry'
import { LOGGER_PROVIDER } from '@island.is/logging'
import { isDefined } from '@island.is/shared/utils'

import { UNKNOWN_NAME } from './constants/names'
import { partitionWithIndex } from './utils/partitionWithIndex'

export type NameInfo = {
nationalId: string
name: string
}

export class AliveStatusService {
constructor(
private nationalRegistryClient: NationalRegistryClientService,
private companyRegistryClient: CompanyRegistryClientService,
@Inject(LOGGER_PROVIDER)
private logger: Logger,
) {}

/**
* Divides nationalIds into alive and deceased
* - Makes calls for every nationalId to NationalRegistry to check if the person exists.
* - Divides the nationalIds into alive and deceased, based on
* 1. All companies will be divided into alive.
* 2. If the person exists in NationalRegistry, then the person is alive.
*/
public async getStatus(nationalIds: string[]): Promise<{
aliveNationalIds: string[]
deceasedNationalIds: string[]
aliveNameInfo: NameInfo[]
}> {
if (nationalIds.length === 0) {
return {
aliveNationalIds: [],
deceasedNationalIds: [],
aliveNameInfo: [],
}
}

const promises = nationalIds.map((nationalId) =>
kennitala.isCompany(nationalId)
? this.companyRegistryClient
.getCompany(nationalId)
.catch(this.handlerGetError)
: this.nationalRegistryClient
.getIndividual(nationalId)
.catch(this.handlerGetError),
)

try {
// Check if nationalId is linked to a person, i.e. not deceased
const identities = await Promise.all(promises)
const identitiesValuesNoError = identities
.filter(this.isNotError)
.filter(isDefined)
.map((identity) => ({
nationalId: identity.nationalId,
name: identity.name ?? UNKNOWN_NAME,
}))

// Divide nationalIds into alive or deceased.
const [aliveNationalIds, deceasedNationalIds] = partitionWithIndex(
nationalIds,
(nationalId, index) =>
// All companies will be divided into alive
kennitala.isCompany(nationalId) ||
// Pass through although Þjóðskrá API throws an error
identities[index] instanceof Error ||
// Make sure we can match the person to the nationalId, i.e. not deceased
(identities[index] as IndividualDto)?.nationalId === nationalId,
)

return {
aliveNationalIds,
deceasedNationalIds,
aliveNameInfo: identitiesValuesNoError,
}
} catch (error) {
this.logger.error(`Error getting live status.}`, error)

// We do not want to fail the whole request if we cannot get the live status.
// Therefore, we return all nationalIds as alive.
return {
aliveNationalIds: nationalIds,
deceasedNationalIds: [],
aliveNameInfo: [],
}
}
}

private handlerGetError(error: null | Error) {
return error
}

/**
* Checks if item is not an instance of Error
*/
private isNotError<T>(item: T | Error): item is T {
return item instanceof Error === false
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,37 @@
import { Inject, Injectable, Logger } from '@nestjs/common'
import { InjectModel } from '@nestjs/sequelize'
import { Inject, Injectable } from '@nestjs/common'
import { ConfigType } from '@nestjs/config'
import { InjectModel } from '@nestjs/sequelize'
import startOfDay from 'date-fns/startOfDay'
import * as kennitala from 'kennitala'
import uniqBy from 'lodash/uniqBy'
import { Op } from 'sequelize'
import startOfDay from 'date-fns/startOfDay'

import { User } from '@island.is/auth-nest-tools'
import {
IndividualDto,
NationalRegistryClientService,
} from '@island.is/clients/national-registry-v2'
import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry'
import { LOGGER_PROVIDER } from '@island.is/logging'
import { AuditService } from '@island.is/nest/audit'
import { AuthDelegationType } from '@island.is/shared/types'
import { isDefined } from '@island.is/shared/utils'

import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation-type.model'
import { ApiScopeUserAccess } from '../resources/models/api-scope-user-access.model'
import { ApiScope } from '../resources/models/api-scope.model'
import { AliveStatusService, NameInfo } from './alive-status.service'
import { UNKNOWN_NAME } from './constants/names'
import { DelegationConfig } from './DelegationConfig'
import { ApiScopeInfo } from './delegations-incoming.service'
import { DelegationDTO } from './dto/delegation.dto'
import { MergedDelegationDTO } from './dto/merged-delegation.dto'
import { DelegationDelegationType } from './models/delegation-delegation-type.model'
import { DelegationScope } from './models/delegation-scope.model'
import { Delegation } from './models/delegation.model'
import { DelegationValidity } from './types/delegationValidity'
import { partitionWithIndex } from './utils/partitionWithIndex'
import { getScopeValidityWhereClause } from './utils/scopes'
import { DelegationDelegationType } from './models/delegation-delegation-type.model'
import { DelegationConfig } from './DelegationConfig'

type FindAllValidIncomingOptions = {
nationalId: string
domainName?: string
validity?: DelegationValidity
}

type FromNameInfo = {
nationalId: string
name: string
}

/**
* Service class for incoming delegations.
* This class supports domain based delegations.
Expand All @@ -54,10 +43,7 @@ export class DelegationsIncomingCustomService {
private delegationModel: typeof Delegation,
@InjectModel(ApiScopeUserAccess)
private apiScopeUserAccessModel: typeof ApiScopeUserAccess,
private nationalRegistryClient: NationalRegistryClientService,
private companyRegistryClient: CompanyRegistryClientService,
@Inject(LOGGER_PROVIDER)
private logger: Logger,
private aliveStatusService: AliveStatusService,
@Inject(DelegationConfig.KEY)
private delegationConfig: ConfigType<typeof DelegationConfig>,
private auditService: AuditService,
Expand Down Expand Up @@ -243,7 +229,7 @@ export class DelegationsIncomingCustomService {
private async findAllIncomingGeneralMandates(
{ nationalId }: FindAllValidIncomingOptions,
useMaster = false,
): Promise<{ delegations: Delegation[]; fromNameInfo: FromNameInfo[] }> {
): Promise<{ delegations: Delegation[]; fromNameInfo: NameInfo[] }> {
const startOfToday = startOfDay(new Date())

const delegations = await this.delegationModel.findAll({
Expand All @@ -268,10 +254,15 @@ export class DelegationsIncomingCustomService {
})

// Check live status, i.e. dead or alive for delegations
const { aliveDelegations, deceasedDelegations, fromNameInfo } =
await this.getLiveStatusFromDelegations(delegations)
const { aliveNationalIds, deceasedNationalIds, aliveNameInfo } =
await this.aliveStatusService.getStatus(
delegations.map((d) => d.fromNationalId),
)

if (deceasedDelegations.length > 0) {
if (deceasedNationalIds.length > 0) {
const deceasedDelegations = delegations.filter((d) =>
deceasedNationalIds.includes(d.fromNationalId),
)
// Delete all deceased delegations by deleting them and their scopes.
const deletePromises = deceasedDelegations.map((delegation) =>
delegation.destroy(),
Expand All @@ -286,7 +277,12 @@ export class DelegationsIncomingCustomService {
})
}

return { delegations: aliveDelegations, fromNameInfo }
return {
delegations: delegations.filter((d) =>
aliveNationalIds.includes(d.fromNationalId),
),
fromNameInfo: aliveNameInfo,
}
}

private async findAllIncoming(
Expand All @@ -298,7 +294,7 @@ export class DelegationsIncomingCustomService {
validity: DelegationValidity
},
useMaster = false,
): Promise<{ delegations: Delegation[]; fromNameInfo: FromNameInfo[] }> {
): Promise<{ delegations: Delegation[]; fromNameInfo: NameInfo[] }> {
let whereOptions = getScopeValidityWhereClause(validity)
if (domainName) whereOptions = { ...whereOptions, domainName: domainName }

Expand Down Expand Up @@ -336,10 +332,15 @@ export class DelegationsIncomingCustomService {
})

// Check live status, i.e. dead or alive for delegations
const { aliveDelegations, deceasedDelegations, fromNameInfo } =
await this.getLiveStatusFromDelegations(delegations)
const { aliveNationalIds, deceasedNationalIds, aliveNameInfo } =
await this.aliveStatusService.getStatus(
delegations.map((d) => d.fromNationalId),
)

if (deceasedDelegations.length > 0) {
if (deceasedNationalIds.length > 0) {
const deceasedDelegations = delegations.filter((d) =>
deceasedNationalIds.includes(d.fromNationalId),
)
// Delete all deceased delegations by deleting them and their scopes.
const deletePromises = deceasedDelegations.map((delegation) =>
delegation.destroy(),
Expand All @@ -354,7 +355,12 @@ export class DelegationsIncomingCustomService {
})
}

return { delegations: aliveDelegations, fromNameInfo }
return {
delegations: delegations.filter((d) =>
aliveNationalIds.includes(d.fromNationalId),
),
fromNameInfo: aliveNameInfo,
}
}

private checkIfScopeIsValid(
Expand Down Expand Up @@ -389,100 +395,11 @@ export class DelegationsIncomingCustomService {
return false
}

/**
* Divides delegations into alive and deceased delegations
* - Makes calls for every delegation to NationalRegistry to check if the person exists.
* - Divides the delegations into alive and deceased delegations, based on
* 1. All companies will be divided into alive delegations.
* 2. If the person exists in NationalRegistry, then the delegation is alive.
*/
private async getLiveStatusFromDelegations(
delegations: Delegation[],
): Promise<{
aliveDelegations: Delegation[]
deceasedDelegations: Delegation[]
fromNameInfo: FromNameInfo[]
}> {
if (delegations.length === 0) {
return {
aliveDelegations: [],
deceasedDelegations: [],
fromNameInfo: [],
}
}

const delegationsPromises = delegations.map(({ fromNationalId }) =>
kennitala.isCompany(fromNationalId)
? this.companyRegistryClient
.getCompany(fromNationalId)
.catch(this.handlerGetError)
: this.nationalRegistryClient
.getIndividual(fromNationalId)
.catch(this.handlerGetError),
)

try {
// Check if delegations is linked to a person, i.e. not deceased
const identities = await Promise.all(delegationsPromises)
const identitiesValuesNoError = identities
.filter(this.isNotError)
.filter(isDefined)
.map((identity) => ({
nationalId: identity.nationalId,
name: identity.name ?? UNKNOWN_NAME,
}))

// Divide delegations into alive or deceased delegations.
const [aliveDelegations, deceasedDelegations] = partitionWithIndex(
delegations,
({ fromNationalId }, index) =>
// All companies will be divided into aliveDelegations
kennitala.isCompany(fromNationalId) ||
// Pass through although Þjóðskrá API throws an error since it is not required to view the delegation.
identities[index] instanceof Error ||
// Make sure we can match the person to the delegation, i.e. not deceased
(identities[index] as IndividualDto)?.nationalId === fromNationalId,
)

return {
aliveDelegations,
deceasedDelegations,
fromNameInfo: identitiesValuesNoError,
}
} catch (error) {
this.logger.error(
`Error getting live status from delegations. Delegations: ${delegations.map(
(d) => d.id,
)}`,
error,
)

// We do not want to fail the whole request if we cannot get the live status from delegations.
// Therefore, we return all delegations as alive delegations.
return {
aliveDelegations: delegations,
deceasedDelegations: [],
fromNameInfo: [],
}
}
}

private handlerGetError(error: null | Error) {
return error
}

/**
* Checks if item is not an instance of Error
*/
private isNotError<T>(item: T | Error): item is T {
return item instanceof Error === false
}

/**
* Finds person by nationalId.
*/
private getPersonByNationalId(
identities: Array<FromNameInfo | null>,
identities: Array<NameInfo | null>,
nationalId: string,
) {
return identities.find((identity) => identity?.nationalId === nationalId)
Expand Down
Loading

0 comments on commit 4c3279d

Please sign in to comment.