From 1f60816fef5780f2dbb7c1a195cd48988cf6c218 Mon Sep 17 00:00:00 2001 From: berglindoma13 Date: Tue, 4 Jun 2024 08:37:00 +0000 Subject: [PATCH] feat(university-application): file uploads added to service (#15056) * adding function to blob * file upload test1 * small updates to application * optimization * optimization * refactoring * Update shared.service.ts refactoring --- .../application/dto/createApplicationDto.ts | 11 +- .../universityApplication.service.ts | 6 + .../src/lib/modules/shared/shared.service.ts | 24 +++- .../university/university.service.ts | 60 ++++++++-- .../src/components/SummaryBlock.tsx | 4 +- .../EducationDetails/DetailsRepeaterItem.tsx | 2 +- .../src/fields/Review/ProgramReview.tsx | 2 +- .../UniversityForm/FormerEducation/index.ts | 2 +- .../src/clientConfig.yaml | 103 +++++++++++++++--- .../lib/universityOfIcelandClient.service.ts | 16 ++- .../src/lib/model/application.ts | 7 ++ 11 files changed, 202 insertions(+), 35 deletions(-) diff --git a/apps/services/university-gateway/src/app/modules/application/dto/createApplicationDto.ts b/apps/services/university-gateway/src/app/modules/application/dto/createApplicationDto.ts index 9a9c7e42fbd6..72172ce99b76 100644 --- a/apps/services/university-gateway/src/app/modules/application/dto/createApplicationDto.ts +++ b/apps/services/university-gateway/src/app/modules/application/dto/createApplicationDto.ts @@ -106,10 +106,17 @@ class CreateApplicationFileDto { @IsString() @ApiProperty({ - description: 'Base 64 for file', + description: 'File type', + example: 'profskirteini', + }) + fileType!: string + + @IsString() + @ApiProperty({ + description: 'Blob for file', example: '', }) - base64!: string + blob!: Blob } class CreateApplicationEducationDto { diff --git a/apps/services/university-gateway/src/app/modules/application/universityApplication.service.ts b/apps/services/university-gateway/src/app/modules/application/universityApplication.service.ts index e21d52753ccc..127e9123f7e9 100644 --- a/apps/services/university-gateway/src/app/modules/application/universityApplication.service.ts +++ b/apps/services/university-gateway/src/app/modules/application/universityApplication.service.ts @@ -89,6 +89,11 @@ export class UniversityApplicationService { ) } + const allAttachments = applicationDto.educationList + .map((x) => x.degreeAttachments) + .filter((y) => !!y) + .flat() + // Wrap answers in obj that can be sent to libs/clients for universities const applicationObj: IApplication = { id: applicationDto.applicationId, @@ -116,6 +121,7 @@ export class UniversityApplicationService { educationOption: applicationDto.educationOption, workExperienceList: applicationDto.workExperienceList, extraFieldList: applicationDto.extraFieldList, + attachments: allAttachments, } // Create application in our DB diff --git a/libs/application/template-api-modules/src/lib/modules/shared/shared.service.ts b/libs/application/template-api-modules/src/lib/modules/shared/shared.service.ts index 2573811469ec..387a553e3757 100644 --- a/libs/application/template-api-modules/src/lib/modules/shared/shared.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/shared/shared.service.ts @@ -242,18 +242,16 @@ export class SharedTemplateApiService { ) } - async getAttachmentContentAsBase64( + async getS3File( application: ApplicationWithAttachments, attachmentKey: string, - ): Promise { + ) { const fileName = ( application.attachments as { [key: string]: string } )[attachmentKey] - const { bucket, key } = AmazonS3URI(fileName) - const uploadBucket = bucket const file = await this.s3 .getObject({ @@ -261,7 +259,23 @@ export class SharedTemplateApiService { Key: key, }) .promise() - const fileContent = file.Body as Buffer + return file.Body as Buffer + } + + async getAttachmentContentAsBase64( + application: ApplicationWithAttachments, + attachmentKey: string, + ): Promise { + const fileContent = await this.getS3File(application, attachmentKey) return fileContent?.toString('base64') || '' } + + async getAttachmentContentAsBlob( + application: ApplicationWithAttachments, + attachmentKey: string, + ): Promise { + const fileContent = await this.getS3File(application, attachmentKey) + const blob: Blob = new Blob([fileContent], { type: 'multipart/form-data' }) + return blob + } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts index 0af82ccaa355..2677fa205091 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/university/university.service.ts @@ -138,7 +138,16 @@ export class UniversityService extends BaseTemplateApiService { ? { degreeAttachments: await this.getFilesFromAttachment( application, - answers.educationDetails.exemptionDetails?.degreeAttachments, + answers.educationDetails.exemptionDetails?.degreeAttachments?.map( + (x, i) => { + const type = this.mapFileTypes(i) + return { + name: x.name, + key: x.key, + type: type, + } + }, + ), ), moreDetails: answers.educationDetails.exemptionDetails?.moreDetails, } @@ -160,7 +169,16 @@ export class UniversityService extends BaseTemplateApiService { answers.educationDetails.thirdLevelDetails?.moreDetails, degreeAttachments: await this.getFilesFromAttachment( application, - answers.educationDetails.thirdLevelDetails?.degreeAttachments, + answers.educationDetails.thirdLevelDetails?.degreeAttachments?.map( + (x, i) => { + const type = this.mapFileTypes(i) + return { + name: x.name, + key: x.key, + type: type, + } + }, + ), ), } : undefined @@ -171,7 +189,14 @@ export class UniversityService extends BaseTemplateApiService { ...item, degreeAttachments: await this.getFilesFromAttachment( application, - item.degreeAttachments, + item.degreeAttachments?.map((x, i) => { + const type = this.mapFileTypes(i) + return { + name: x.name, + key: x.key, + type: type, + } + }), ), } })) || @@ -233,20 +258,39 @@ export class UniversityService extends BaseTemplateApiService { private async getFilesFromAttachment( application: ApplicationWithAttachments, - attachments?: { name: string; key: string }[], - ): Promise<{ fileName: string; base64: string }[]> { + attachments?: { name: string; key: string; type: string }[], + ): Promise<{ fileName: string; fileType: string; blob: Blob }[]> { return await Promise.all( attachments?.map(async (file) => { - const base64 = - await this.sharedTemplateAPIService.getAttachmentContentAsBase64( + const blob = + await this.sharedTemplateAPIService.getAttachmentContentAsBlob( application, file.key, ) return { fileName: file.name, - base64, + fileType: file.type, + blob, } }) || [], ) } + + private mapFileTypes = (fileIndex: number): string => { + let type + switch (fileIndex) { + case 1: + type = 'profskirteini' + break + case 2: + type = 'profskirteini2' + break + case 3: + type = 'profskirteini3' + break + default: + type = '' + } + return type + } } diff --git a/libs/application/templates/university/src/components/SummaryBlock.tsx b/libs/application/templates/university/src/components/SummaryBlock.tsx index ec39c6675819..b055a16f42e6 100644 --- a/libs/application/templates/university/src/components/SummaryBlock.tsx +++ b/libs/application/templates/university/src/components/SummaryBlock.tsx @@ -21,14 +21,14 @@ const SummaryBlock = ({ children, editAction }: Props) => { borderColor="blue300" > {children} - + */} ) } diff --git a/libs/application/templates/university/src/fields/EducationDetails/DetailsRepeaterItem.tsx b/libs/application/templates/university/src/fields/EducationDetails/DetailsRepeaterItem.tsx index 7c945d07a9c6..a6ee43b897c7 100644 --- a/libs/application/templates/university/src/fields/EducationDetails/DetailsRepeaterItem.tsx +++ b/libs/application/templates/university/src/fields/EducationDetails/DetailsRepeaterItem.tsx @@ -402,7 +402,7 @@ export const DetailsRepeaterItem: FC = ({ {!readOnly && ( = ({ const answers = application.answers as UniversityApplication return ( - + diff --git a/libs/application/templates/university/src/forms/UniversityForm/FormerEducation/index.ts b/libs/application/templates/university/src/forms/UniversityForm/FormerEducation/index.ts index 802430a9e7d1..9d025f882acd 100644 --- a/libs/application/templates/university/src/forms/UniversityForm/FormerEducation/index.ts +++ b/libs/application/templates/university/src/forms/UniversityForm/FormerEducation/index.ts @@ -16,6 +16,6 @@ export const FormerEducationSection = buildSection({ NotFinishedEducationSubSection, ThirdLevelEducationSubSection, ExemptionSubSection, - OtherDocumentsSection, + // OtherDocumentsSection, //TODO replace again when there are some programs that request otherDocuments ], }) diff --git a/libs/clients/university-application/university-of-iceland/src/clientConfig.yaml b/libs/clients/university-application/university-of-iceland/src/clientConfig.yaml index 0fc66bf25b99..7b31526c75e5 100644 --- a/libs/clients/university-application/university-of-iceland/src/clientConfig.yaml +++ b/libs/clients/university-application/university-of-iceland/src/clientConfig.yaml @@ -173,21 +173,40 @@ paths: type: 'string' required: type: 'boolean' - extraApplicationTexts: + extraApplicationTextsIs: type: 'object' - properties: - toptextIs: - type: 'string' - sociallifeIs: - type: 'string' - forYouIs: - type: 'string' - exchangeStudyIs: - type: 'string' - workIs: - type: 'string' - aboutIs: - type: 'string' + items: + type: 'object' + properties: + toptext: + type: 'string' + sociallife: + type: 'string' + forYou: + type: 'string' + exchangeStudy: + type: 'string' + work: + type: 'string' + about: + type: 'string' + extraApplicationTextsEn: + type: 'object' + items: + type: 'object' + properties: + toptext: + type: 'string' + sociallife: + type: 'string' + forYou: + type: 'string' + exchangeStudy: + type: 'string' + work: + type: 'string' + about: + type: 'string' kjorsvid: type: 'array' items: @@ -494,6 +513,45 @@ paths: description: 'Application ID' tags: - 'Application' + /applications/attachments/{guid}: + post: + parameters: + - name: guid + required: true + in: path + allowEmptyValue: false + description: Application ID + schema: + type: string + format: uuid + summary: Submit an applications accompanying documents + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + attachment: + type: string + format: binary + attachmentKey: + $ref: '#/components/schemas/AttachmentKey' + responses: + '200': + description: Successful submission + content: + application/json: + schema: + type: object + properties: + id: + type: string + description: Attachment ID + '400': + description: Bad Request - No application with given ID + tags: + - Application /openapi.yaml: get: operationId: 'InfraController_openapi' @@ -590,6 +648,23 @@ components: - 'universityId' - 'programId' - 'modeOfDelivery' + AttachmentKey: + type: string + enum: + - cv + - markmid + - rannsoknaraaetlun + - namsaaetlun + - rannsoknarverkefni + - profskirteini + - profskirteini2 + - profskirteini3 + - onnur_skjol + - kynningarbref + - ferilsskra + - leyfisbref + - portofolio + - sakavottord UpdateApplicationDto: type: 'object' properties: diff --git a/libs/clients/university-application/university-of-iceland/src/lib/universityOfIcelandClient.service.ts b/libs/clients/university-application/university-of-iceland/src/lib/universityOfIcelandClient.service.ts index bf4dee029fc9..7e589ea8989b 100644 --- a/libs/clients/university-application/university-of-iceland/src/lib/universityOfIcelandClient.service.ts +++ b/libs/clients/university-application/university-of-iceland/src/lib/universityOfIcelandClient.service.ts @@ -17,7 +17,7 @@ import { logger } from '@island.is/logging' import { mapUglaPrograms } from './utils/mapUglaPrograms' import { mapUglaCourses } from './utils/mapUglaCourses' import { mapUglaApplication } from './utils/mapUglaApplication' -import { InlineResponse2004 } from '../../gen/fetch' +import { AttachmentKey, InlineResponse2004 } from '../../gen/fetch' @Injectable() export class UniversityOfIcelandApplicationClient { @@ -73,6 +73,20 @@ export class UniversityOfIcelandApplicationClient { const response = await this.applicationApi.applicationsPost( mappedApplication, ) + + application.attachments?.filter(Boolean).forEach(async (attachment) => { + const attachmentKey = attachment?.fileType + ? AttachmentKey[attachment.fileType as keyof typeof AttachmentKey] || + undefined + : undefined + const requestParams = { + guid: application.id, + attachment: attachment?.blob, + attachmentKey, + } + await this.applicationApi.applicationsAttachmentsGuidPost(requestParams) + }) + return response } diff --git a/libs/university-gateway/src/lib/model/application.ts b/libs/university-gateway/src/lib/model/application.ts index 7855d9f0fa8e..3b927eab4870 100644 --- a/libs/university-gateway/src/lib/model/application.ts +++ b/libs/university-gateway/src/lib/model/application.ts @@ -13,6 +13,7 @@ export interface IApplication { workExperienceList: IApplicationWorkExperience[] extraFieldList: IApplicationExtraFields[] educationOption?: string + attachments?: Array } export interface IApplicationApplicant { @@ -47,6 +48,12 @@ export interface IApplicationWorkExperience { jobTitle: string } +export interface IApplicationAttachment { + fileName: string + fileType: string + blob: Blob +} + export interface IApplicationExtraFields { key: string value: object