diff --git a/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.service.ts b/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.service.ts index 610dd873837e..27cc24ce9131 100644 --- a/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.service.ts +++ b/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.service.ts @@ -3,6 +3,7 @@ import { Injectable, NotFoundException, BadRequestException, + InternalServerErrorException, } from '@nestjs/common' import { InjectModel } from '@nestjs/sequelize' import { Op } from 'sequelize' @@ -192,11 +193,21 @@ export class EndorsementListService { } async lock(endorsementList: EndorsementList): Promise { - this.logger.info(`Locking endorsement list: ${endorsementList.id}`) - if (process.env.NODE_ENV === 'production') { - await this.emailLock(endorsementList) + try { + this.logger.info(`Locking endorsement list: ${endorsementList.id}`) + if (process.env.NODE_ENV === 'production') { + await this.emailLock(endorsementList) + } + return await endorsementList.update({ adminLock: true }) + } catch (error) { + this.logger.error('Failed to lock endorsement list', { + error: error.message, + listId: endorsementList.id, + }) + throw new InternalServerErrorException( + `Failed to lock endorsement list: ${error.message}`, + ) } - return await endorsementList.update({ adminLock: true }) } async unlock(endorsementList: EndorsementList): Promise { @@ -279,7 +290,7 @@ export class EndorsementListService { return result } - async getOwnerInfo(listId: string, owner?: string) { + async getOwnerInfo(listId: string, owner?: string): Promise { this.logger.info(`Finding single endorsement lists by id "${listId}"`) if (!owner) { const endorsementList = await this.endorsementListModel.findOne({ @@ -297,15 +308,12 @@ export class EndorsementListService { try { const person = await this.nationalRegistryApiV3.getName(owner) return person?.fulltNafn ? person.fulltNafn : '' - } catch (e) { - if (e instanceof Error) { - this.logger.warn( - `Occured when fetching owner name from NationalRegistryApi ${e.message} \n${e.stack}`, - ) - return '' - } else { - throw e - } + } catch (error) { + this.logger.error('Failed to fetch owner name from NationalRegistry', { + error: error.message, + listId, + }) + return '' } } @@ -592,7 +600,11 @@ export class EndorsementListService { }) return { success: true } } catch (error) { - this.logger.error('Failed to send email', error) + this.logger.error('Failed to send PDF email', { + error: error.message, + listId, + recipientEmail, + }) return { success: false } } } @@ -668,7 +680,10 @@ export class EndorsementListService { }) return { success: true } } catch (error) { - this.logger.error('Failed to send email', error) + this.logger.error('Failed to send creation notification email', { + error: error.message, + listId: endorsementList.id, + }) return { success: false } } } @@ -770,7 +785,10 @@ export class EndorsementListService { }) return { success: true } } catch (error) { - this.logger.error('Failed to send email', error) + this.logger.error('Failed to send creation notification email', { + error: error.message, + listId: endorsementList.id, + }) return { success: false } } } @@ -780,44 +798,53 @@ export class EndorsementListService { fileType: 'pdf' | 'csv', ): Promise { try { - this.logger.info(`Exporting list ${listId} as ${fileType}`, { listId }) - - // Validate file type if (!['pdf', 'csv'].includes(fileType)) { throw new BadRequestException( - 'Invalid file type. Allowed values are "pdf" or "csv".', + `Invalid file type. Allowed values are "pdf" or "csv"`, ) } - // Fetch endorsement list const endorsementList = await this.fetchEndorsementList(listId, user) if (!endorsementList) { throw new NotFoundException( - `Endorsement list ${listId} not found or access denied.`, + `Endorsement list ${listId} not found or access denied`, ) } - // Create file buffer const fileBuffer = fileType === 'pdf' ? await this.createPdfBuffer(endorsementList) : this.createCsvBuffer(endorsementList) - // Upload to S3 const filename = `undirskriftalisti-${listId}-${new Date() .toISOString() .replace(/[:.]/g, '-')}.${fileType}` + await this.uploadFileToS3(fileBuffer, filename, fileType) - // Generate presigned URL with 60 minutes expiration const url = await this.s3Service.getPresignedUrl({ bucket: environment.exportsBucketName, key: filename, }) + return { url } } catch (error) { - this.logger.error(`Failed to export list ${listId}`, { error }) - throw error + this.logger.error('Failed to export list', { + error: error.message, + listId, + fileType, + }) + + if ( + error instanceof BadRequestException || + error instanceof NotFoundException + ) { + throw error // Re-throw validation errors + } + + throw new InternalServerErrorException( + `Failed to export list: ${error.message}`, + ) } } @@ -858,14 +885,21 @@ export class EndorsementListService { endorsementList, ownerName, ) + + if (!Buffer.isBuffer(pdfBuffer)) { + throw new InternalServerErrorException( + 'Generated PDF is not a valid buffer', + ) + } + return pdfBuffer } catch (error) { - this.logger.error( - `Failed to create PDF buffer for endorsement list ${endorsementList.id}`, - { error }, - ) - throw new Error( - `Error generating PDF for endorsement list ${endorsementList.id}`, + this.logger.error('Failed to create PDF buffer', { + error: error.message, + listId: endorsementList.id, + }) + throw new InternalServerErrorException( + `Failed to generate PDF: ${error.message}`, ) } } @@ -876,16 +910,24 @@ export class EndorsementListService { fileType: 'pdf' | 'csv', ): Promise { try { + if (!environment.exportsBucketName) { + throw new InternalServerErrorException('S3 bucket name is undefined') + } + await this.s3Service.uploadFile( fileBuffer, { bucket: environment.exportsBucketName, key: filename }, - { - ContentType: fileType === 'pdf' ? 'application/pdf' : 'text/csv', - }, + { ContentType: fileType === 'pdf' ? 'application/pdf' : 'text/csv' }, ) } catch (error) { - this.logger.error(`Failed to upload file to S3`, { error, filename }) - throw new Error('Error uploading file to S3') + this.logger.error('Failed to upload file to S3', { + error: error.message, + filename, + bucketName: environment.exportsBucketName, + }) + throw new InternalServerErrorException( + `Failed to upload file to S3: ${error.message}`, + ) } } } diff --git a/apps/services/endorsements/api/src/environments/environment.ts b/apps/services/endorsements/api/src/environments/environment.ts index 3633c57a21c4..9fe184273854 100644 --- a/apps/services/endorsements/api/src/environments/environment.ts +++ b/apps/services/endorsements/api/src/environments/environment.ts @@ -20,7 +20,7 @@ const devConfig = { sender: 'devland.is', address: 'development@island.is', }, - exportsBucketName: process.env.ENDORSEMENT_SYSTEM_EXPORTS_BUCKET_NAME, + exportsBucketName: 'island-is-dev-exports-endorsement-system', } const prodConfig = {