Skip to content

Commit

Permalink
Merge branch 'main' of github.com:island-is/island.is into j-s/revoke…
Browse files Browse the repository at this point in the history
…-send-to-prison-admin-modal
  • Loading branch information
oddsson committed Dec 9, 2024
2 parents 043e110 + 31b2fdb commit 17e0854
Show file tree
Hide file tree
Showing 661 changed files with 9,707 additions and 10,352 deletions.
6 changes: 6 additions & 0 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ import {
IdsClientConfig,
XRoadConfig,
} from '@island.is/nest/config'
import { CodeOwnerInterceptor } from '@island.is/nest/core'
import { DataLoaderInterceptor } from '@island.is/nest/dataloader'
import { FeatureFlagConfig } from '@island.is/nest/feature-flags'
import { ProblemModule } from '@island.is/nest/problem'
Expand All @@ -175,6 +176,7 @@ import { FormSystemModule } from '@island.is/api/domains/form-system'
import { HealthDirectorateModule } from '@island.is/api/domains/health-directorate'

import { VehiclesMileageClientConfig } from '@island.is/clients/vehicles-mileage'

import { getConfig } from './environments'
import { GraphqlOptionsFactory } from './graphql-options.factory'
import { GraphQLConfig } from './graphql.config'
Expand Down Expand Up @@ -212,6 +214,10 @@ const environment = getConfig
provide: APP_INTERCEPTOR,
useClass: DataLoaderInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: CodeOwnerInterceptor,
},
],
imports: [
GraphQLModule.forRootAsync({
Expand Down
158 changes: 84 additions & 74 deletions apps/application-system/api/infra/application-system-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ref, service, ServiceBuilder } from '../../../../infra/src/dsl/dsl'
import {
CodeOwners,
ref,
service,
ServiceBuilder,
} from '../../../../infra/src/dsl/dsl'
import {
Base,
ChargeFjsV2,
Expand Down Expand Up @@ -44,79 +49,83 @@ export const GRAPHQL_API_URL_ENV_VAR_NAME = 'GRAPHQL_API_URL' // This property i

const namespace = 'application-system'
const serviceAccount = 'application-system-api'
export const workerSetup =
(): ServiceBuilder<'application-system-api-worker'> =>
service('application-system-api-worker')
.namespace(namespace)
.image('application-system-api')
.db()
.serviceAccount('application-system-api-worker')
.redis()
.env({
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/application-system',
IDENTITY_SERVER_ISSUER_URL: {
dev: 'https://identity-server.dev01.devland.is',
staging: 'https://identity-server.staging01.devland.is',
prod: 'https://innskra.island.is',
},
XROAD_CHARGE_FJS_V2_PATH: {
dev: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2',
staging: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2',
prod: 'IS/GOV/5402697509/FJS-Public/chargeFJS_v2',
},
APPLICATION_ATTACHMENT_BUCKET: {
dev: 'island-is-dev-storage-application-system',
staging: 'island-is-staging-storage-application-system',
prod: 'island-is-prod-storage-application-system',
},
FILE_SERVICE_PRESIGN_BUCKET: {
dev: 'island-is-dev-fs-presign-bucket',
staging: 'island-is-staging-fs-presign-bucket',
prod: 'island-is-prod-fs-presign-bucket',
},
FILE_STORAGE_UPLOAD_BUCKET: {
dev: 'island-is-dev-upload-api',
staging: 'island-is-staging-upload-api',
prod: 'island-is-prod-upload-api',
},
CLIENT_LOCATION_ORIGIN: {
dev: 'https://beta.dev01.devland.is/umsoknir',
staging: 'https://beta.staging01.devland.is/umsoknir',
prod: 'https://island.is/umsoknir',
local: 'http://localhost:4200/umsoknir',
},
})
.xroad(Base, Client, Payment, Inna, EHIC, WorkMachines)
.secrets({
IDENTITY_SERVER_CLIENT_SECRET:
'/k8s/application-system/api/IDENTITY_SERVER_CLIENT_SECRET',
SYSLUMENN_HOST: '/k8s/application-system-api/SYSLUMENN_HOST',
SYSLUMENN_USERNAME: '/k8s/application-system/api/SYSLUMENN_USERNAME',
SYSLUMENN_PASSWORD: '/k8s/application-system/api/SYSLUMENN_PASSWORD',
DRIVING_LICENSE_BOOK_XROAD_PATH:
'/k8s/application-system-api/DRIVING_LICENSE_BOOK_XROAD_PATH',
DRIVING_LICENSE_BOOK_USERNAME:
'/k8s/application-system-api/DRIVING_LICENSE_BOOK_USERNAME',
DRIVING_LICENSE_BOOK_PASSWORD:
'/k8s/application-system-api/DRIVING_LICENSE_BOOK_PASSWORD',
DOKOBIT_ACCESS_TOKEN:
'/k8s/application-system/api/DOKOBIT_ACCESS_TOKEN',
DOKOBIT_URL: '/k8s/application-system-api/DOKOBIT_URL',
ARK_BASE_URL: '/k8s/application-system-api/ARK_BASE_URL',
DOMSYSLA_PASSWORD: '/k8s/application-system-api/DOMSYSLA_PASSWORD',
DOMSYSLA_USERNAME: '/k8s/application-system-api/DOMSYSLA_USERNAME',
})
.args('main.js', '--job', 'worker')
.command('node')
.extraAttributes({
dev: { schedule: '*/30 * * * *' },
staging: { schedule: '*/30 * * * *' },
prod: { schedule: '*/30 * * * *' },
})
.resources({
limits: { cpu: '400m', memory: '768Mi' },
requests: { cpu: '150m', memory: '384Mi' },
})
export const workerSetup = (services: {
userNotificationService: ServiceBuilder<'services-user-notification'>
}): ServiceBuilder<'application-system-api-worker'> =>
service('application-system-api-worker')
.namespace(namespace)
.image('application-system-api')
.db()
.serviceAccount('application-system-api-worker')
.redis()
.codeOwner(CodeOwners.NordaApplications)
.env({
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/application-system',
IDENTITY_SERVER_ISSUER_URL: {
dev: 'https://identity-server.dev01.devland.is',
staging: 'https://identity-server.staging01.devland.is',
prod: 'https://innskra.island.is',
},
XROAD_CHARGE_FJS_V2_PATH: {
dev: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2',
staging: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2',
prod: 'IS/GOV/5402697509/FJS-Public/chargeFJS_v2',
},
APPLICATION_ATTACHMENT_BUCKET: {
dev: 'island-is-dev-storage-application-system',
staging: 'island-is-staging-storage-application-system',
prod: 'island-is-prod-storage-application-system',
},
FILE_SERVICE_PRESIGN_BUCKET: {
dev: 'island-is-dev-fs-presign-bucket',
staging: 'island-is-staging-fs-presign-bucket',
prod: 'island-is-prod-fs-presign-bucket',
},
FILE_STORAGE_UPLOAD_BUCKET: {
dev: 'island-is-dev-upload-api',
staging: 'island-is-staging-upload-api',
prod: 'island-is-prod-upload-api',
},
CLIENT_LOCATION_ORIGIN: {
dev: 'https://beta.dev01.devland.is/umsoknir',
staging: 'https://beta.staging01.devland.is/umsoknir',
prod: 'https://island.is/umsoknir',
local: 'http://localhost:4200/umsoknir',
},
USER_NOTIFICATION_API_URL: ref(
(h) => `http://${h.svc(services.userNotificationService)}`,
),
})
.xroad(Base, Client, Payment, Inna, EHIC, WorkMachines)
.secrets({
IDENTITY_SERVER_CLIENT_SECRET:
'/k8s/application-system/api/IDENTITY_SERVER_CLIENT_SECRET',
SYSLUMENN_HOST: '/k8s/application-system-api/SYSLUMENN_HOST',
SYSLUMENN_USERNAME: '/k8s/application-system/api/SYSLUMENN_USERNAME',
SYSLUMENN_PASSWORD: '/k8s/application-system/api/SYSLUMENN_PASSWORD',
DRIVING_LICENSE_BOOK_XROAD_PATH:
'/k8s/application-system-api/DRIVING_LICENSE_BOOK_XROAD_PATH',
DRIVING_LICENSE_BOOK_USERNAME:
'/k8s/application-system-api/DRIVING_LICENSE_BOOK_USERNAME',
DRIVING_LICENSE_BOOK_PASSWORD:
'/k8s/application-system-api/DRIVING_LICENSE_BOOK_PASSWORD',
DOKOBIT_ACCESS_TOKEN: '/k8s/application-system/api/DOKOBIT_ACCESS_TOKEN',
DOKOBIT_URL: '/k8s/application-system-api/DOKOBIT_URL',
ARK_BASE_URL: '/k8s/application-system-api/ARK_BASE_URL',
DOMSYSLA_PASSWORD: '/k8s/application-system-api/DOMSYSLA_PASSWORD',
DOMSYSLA_USERNAME: '/k8s/application-system-api/DOMSYSLA_USERNAME',
})
.args('main.js', '--job', 'worker')
.command('node')
.extraAttributes({
dev: { schedule: '*/30 * * * *' },
staging: { schedule: '*/30 * * * *' },
prod: { schedule: '*/30 * * * *' },
})
.resources({
limits: { cpu: '400m', memory: '768Mi' },
requests: { cpu: '150m', memory: '384Mi' },
})

export const serviceSetup = (services: {
documentsService: ServiceBuilder<'services-documents'>
Expand All @@ -131,6 +140,7 @@ export const serviceSetup = (services: {
.serviceAccount(serviceAccount)
.command('node')
.redis()
.codeOwner(CodeOwners.NordaApplications)
.args('main.js')
.env({
EMAIL_REGION: 'eu-west-1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { environment } from '../../../../environments'
import { SequelizeConfigService } from '../../../sequelizeConfig.service'
import { ApplicationChargeModule } from '../charge/application-charge.module'
import { ApplicationLifeCycleService } from './application-lifecycle.service'
import {
UserNotificationClientConfig,
UserNotificationEagerClientModule,
} from '@island.is/clients/user-notification'

@Module({
imports: [
Expand All @@ -26,10 +30,16 @@ import { ApplicationLifeCycleService } from './application-lifecycle.service'
LoggingModule,
ApplicationChargeModule,
ApplicationFilesModule,
UserNotificationEagerClientModule,
AuditModule.forRoot(environment.audit),
ConfigModule.forRoot({
isGlobal: true,
load: [signingModuleConfig, ApplicationFilesConfig, FileStorageConfig],
load: [
UserNotificationClientConfig,
signingModuleConfig,
ApplicationFilesConfig,
FileStorageConfig,
],
}),
],
providers: [ApplicationLifeCycleService],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,34 @@ import { LOGGER_PROVIDER } from '@island.is/logging'
import type { Logger } from '@island.is/logging'
import { ApplicationChargeService } from '../charge/application-charge.service'
import { FileService } from '@island.is/application/api/files'
import {
CreateHnippNotificationDto,
NotificationsApi,
} from '@island.is/clients/user-notification'
import { getApplicationTemplateByTypeId } from '@island.is/application/template-loader'
import {
ApplicationWithAttachments,
PruningApplication,
} from '@island.is/application/types'

export interface ApplicationPruning {
pruned: boolean
application: Pick<
Application,
'id' | 'attachments' | 'answers' | 'externalData' | 'typeId' | 'state'
>
application: PruningApplication
failedAttachments: object
}

@Injectable()
export class ApplicationLifeCycleService {
private processingApplications: ApplicationPruning[] = []
private pruneNotifications = new Map<string, CreateHnippNotificationDto>()

constructor(
@Inject(LOGGER_PROVIDER)
private logger: Logger,
private applicationService: ApplicationService,
private fileService: FileService,
private applicationChargeService: ApplicationChargeService,
private readonly notificationApi: NotificationsApi,
) {
this.logger = logger.child({ context: 'ApplicationLifeCycleService' })
}
Expand All @@ -38,7 +46,7 @@ export class ApplicationLifeCycleService {
await this.pruneAttachments()
await this.pruneApplicationCharge()
await this.pruneApplicationData()
this.reportResults()
await this.reportResults()
this.logger.info(`Application pruning done.`)
}

Expand All @@ -48,10 +56,7 @@ export class ApplicationLifeCycleService {

private async fetchApplicationsToBePruned() {
const applications =
(await this.applicationService.findAllDueToBePruned()) as Pick<
Application,
'id' | 'attachments' | 'answers' | 'externalData' | 'typeId' | 'state'
>[]
(await this.applicationService.findAllDueToBePruned()) as PruningApplication[]

this.logger.info(`Found ${applications.length} applications to be pruned.`)

Expand All @@ -62,6 +67,13 @@ export class ApplicationLifeCycleService {
failedAttachments: {},
}
})

for (const { application } of this.processingApplications) {
const notification = await this.preparePrunedNotification(application)
if (notification) {
this.pruneNotifications.set(application.id, notification)
}
}
}

private async pruneAttachments() {
Expand Down Expand Up @@ -106,7 +118,7 @@ export class ApplicationLifeCycleService {
},
)

prune.application = updatedApplication
prune.application = updatedApplication as PruningApplication
} catch (error) {
prune.pruned = false
this.logger.error(
Expand All @@ -117,7 +129,7 @@ export class ApplicationLifeCycleService {
}
}

private reportResults() {
private async reportResults() {
const failed = this.processingApplications.filter(
(application) => !application.pruned,
)
Expand All @@ -126,6 +138,72 @@ export class ApplicationLifeCycleService {
(application) => application.pruned,
)

for (const { application } of success) {
const notification = this.pruneNotifications.get(application.id)
if (notification) {
await this.sendPrunedNotification(notification, application.id)
}
}

this.logger.info(`Successful: ${success.length}, Failed: ${failed.length}`)
}

private async preparePrunedNotification(
application: PruningApplication,
): Promise<CreateHnippNotificationDto | null> {
const template = await getApplicationTemplateByTypeId(application.typeId)
const stateConfig = template.stateMachineConfig.states[application.state]
const lifeCycle = stateConfig.meta?.lifecycle
if (lifeCycle && lifeCycle.shouldBePruned && lifeCycle.pruneMessage) {
try {
const pruneMessage =
typeof lifeCycle.pruneMessage === 'function'
? lifeCycle.pruneMessage(application as ApplicationWithAttachments)
: lifeCycle.pruneMessage
const notification = {
recipient: application.applicant,
templateId: pruneMessage.notificationTemplateId,
args: [
{
key: 'externalBody',
value: pruneMessage.externalBody || '',
},
{
key: 'internalBody',
value: pruneMessage.internalBody || '',
},
],
}
return notification
} catch (error) {
this.logger.error(
`Failed to prepare pruning notification for application ${application.id}`,
error,
)
return null
}
}
return null
}

private async sendPrunedNotification(
notification: CreateHnippNotificationDto,
applicationId: string,
) {
try {
const response =
await this.notificationApi.notificationsControllerCreateHnippNotification(
{
createHnippNotificationDto: notification,
},
)
this.logger.info(
`Prune notification sent with response: ${JSON.stringify(response)}`,
)
} catch (error) {
this.logger.error(
`Failed to send pruning notification with error: ${error} for application ${applicationId}`,
)
}
}
}
Loading

0 comments on commit 17e0854

Please sign in to comment.