Skip to content

Commit

Permalink
feat: [Contracts] Download contract
Browse files Browse the repository at this point in the history
  • Loading branch information
radulescuandrew committed Sep 25, 2024
1 parent 1f6bf24 commit 7f67828
Show file tree
Hide file tree
Showing 20 changed files with 191 additions and 106 deletions.
18 changes: 18 additions & 0 deletions .serverless/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"unknown": {
"versionSfCore": null,
"versionFramework": "4.2.4",
"isWithinCompose": false,
"composeOrgName": null,
"composeServiceName": null,
"command": [],
"options": {},
"error": null,
"machineId": "4fa7584a6810489c980dfeebe3fa579c",
"serviceProviderAwsCfStackId": null,
"serviceProviderAwsCfStackCreated": null,
"serviceProviderAwsCfStackUpdated": null,
"serviceProviderAwsCfStackStatus": null,
"serviceProviderAwsCfStackOutputs": null
}
}
3 changes: 3 additions & 0 deletions backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { AppModule } from './app.module';
import { Environment } from './infrastructure/config/environment-config';
import { ExceptionsFilter } from './infrastructure/filters/exception.filter';
import { createQueueMonitoring } from './infrastructure/config/create-bull-board';
import { json } from 'express';

async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);

app.use(json({ limit: '50mb' })); // Increase the limit to 50mb to avoid the error "Request Entity Too Large"

app.enableCors({
exposedHeaders: 'content-disposition', // Allow header in the Axios Response Headers
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class DocumentPDFGenerator {
// HTMLtoPDF(fileHTML);

const result = await axios.post(
'https://iywe2rp7u1.execute-api.us-east-1.amazonaws.com/test',
'https://715w11fnq9.execute-api.eu-west-1.amazonaws.com/generate-pdf',
fileHTML,
{
headers: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export class CreateDocumentContractUsecase implements IUseCaseService<string> {

// 8. Generate the PDF
try {
// TODO: Make it async, so we can return the contract id immediately
await this.documentPDFGenerator.generateContractPDF(contract.id);
} catch (error) {
this.logger.error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ export class GetManyDocumentContractsByVolunteerUsecase
}

// 2. Find the document contracts based on the volunteerId
return this.documentContractFacade.findMany({
const contracts = await this.documentContractFacade.findMany({
...paginationOptions,
organizationId,
volunteerId: volunteer.id,
});

return contracts;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { IUseCaseService } from 'src/common/interfaces/use-case-service.interface';
import { Pagination } from 'src/infrastructure/base/repository-with-pagination.class';
import { S3Service } from 'src/infrastructure/providers/s3/module/s3.service';
import {
FindManyDocumentContractListViewOptions,
IDocumentContractListViewModel,
Expand All @@ -13,11 +14,30 @@ export class GetManyDocumentContractsUsecase
{
constructor(
private readonly documentContractFacade: DocumentContractFacade,
private readonly s3Service: S3Service,
) {}

public async execute(
findOptions: FindManyDocumentContractListViewOptions,
): Promise<Pagination<IDocumentContractListViewModel>> {
return this.documentContractFacade.findMany(findOptions);
const contracts = await this.documentContractFacade.findMany(findOptions);

const contractsWithPath = await Promise.all(
contracts.items.map(async (contract) => {
return {
...contract,
documentFilePath: contract.documentFilePath
? await this.s3Service.generatePresignedURL(
contract.documentFilePath,
)
: null,
};
}),
);

return {
...contracts,
items: contractsWithPath,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { ExceptionsService } from 'src/infrastructure/exceptions/exceptions.service';
import { S3Service } from 'src/infrastructure/providers/s3/module/s3.service';
import { ContractExceptionMessages } from 'src/modules/documents/exceptions/contract.exceptions';
import { IDocumentContractWebItemModel } from 'src/modules/documents/models/document-contract-web-item.model';
import { DocumentContractFacade } from 'src/modules/documents/services/document-contract.facade';
Expand All @@ -9,6 +10,7 @@ export class GetOneDocumentContractForNgoUsecase {
constructor(
private readonly documentContractFacade: DocumentContractFacade,
private readonly exceptionService: ExceptionsService,
private readonly s3Service: S3Service,
) {}

async execute({
Expand All @@ -29,6 +31,13 @@ export class GetOneDocumentContractForNgoUsecase {
);
}

return contract;
const contractWithPath = {
...contract,
documentFilePath: contract.documentFilePath
? await this.s3Service.generatePresignedURL(contract.documentFilePath)
: null,
};

return contractWithPath;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { ExceptionsService } from 'src/infrastructure/exceptions/exceptions.service';
import { S3Service } from 'src/infrastructure/providers/s3/module/s3.service';
import { DocumentContractListViewEntity } from 'src/modules/documents/entities/document-contract-list-view.entity';
import { ContractExceptionMessages } from 'src/modules/documents/exceptions/contract.exceptions';
import { DocumentContractFacade } from 'src/modules/documents/services/document-contract.facade';
Expand All @@ -12,6 +13,7 @@ export class GetOneDocumentContractForVolunteerUsecase {
private readonly documentContractFacade: DocumentContractFacade,
private readonly volunteerFacade: VolunteerFacade,
private readonly exceptionService: ExceptionsService,
private readonly s3Service: S3Service,
) {}

async execute({
Expand Down Expand Up @@ -45,6 +47,13 @@ export class GetOneDocumentContractForVolunteerUsecase {
);
}

return contract;
const contractWithPath = {
...contract,
documentFilePath: contract.documentFilePath
? await this.s3Service.generatePresignedURL(contract.documentFilePath)
: null,
};

return contractWithPath;
}
}
1 change: 1 addition & 0 deletions frontend/src/common/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const downloadFile = (uri: string, name: string) => {
const link = document.createElement('a');
link.href = uri;
link.setAttribute('download', name);
link.setAttribute('target', '_blank');
document.body.appendChild(link);
link.click();
link.remove();
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/components/ContractInfoContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import FormReadOnlyElement from './FormReadOnlyElement';
import {
ApprovedDocumentContractStatusMapper,
DocumentContractStatusMarkerColorMapper,
downloadFile,
formatDate,
} from '../common/utils/utils';
import StatusWithMarker from './StatusWithMarker';
Expand Down Expand Up @@ -98,8 +99,14 @@ export const ContractInfoContent = ({

<button
className="bg-white rounded-md text-turquoise-500 hover:text-turquoise-700 focus:outline-none focus:shadow-blue"
// todo: download contract
// onClick={onDownloadContractClick.bind(null, contract.uri, contract.fileName)}
onClick={() => {
if (contract.documentFilePath) {
downloadFile(
contract.documentFilePath,
contract.documentFilePath?.split('/').pop() || 'contract.pdf',
);
}
}}
aria-label="download-contract"
type="button"
>
Expand Down
11 changes: 8 additions & 3 deletions frontend/src/components/DocumentContractsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ApprovedDocumentContractStatusMapper,
DocumentContractStatusMarkerColorMapper,
downloadExcel,
downloadFile,
// downloadFile,
formatDate,
} from '../common/utils/utils';
Expand Down Expand Up @@ -206,9 +207,13 @@ const ContractsTable = ({
{
label: t('general:download', { item: i18n.t('general:contract').toLowerCase() }),
icon: <ArrowDownTrayIcon className="menu-icon" />,
// todo: download contract
onClick: () => {
console.log('TODO: download contract');
onClick: (contract: IDocumentContract) => {
if (contract.documentFilePath) {
downloadFile(
contract.documentFilePath,
contract.documentFilePath?.split('/').pop() || 'contract.pdf',
);
}
},
},
];
Expand Down
File renamed without changes.
File renamed without changes.
77 changes: 77 additions & 0 deletions lambda-pdf-generator/handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const puppeteer = require("puppeteer-core");
const chromium = require("@sparticuz/chromium");
const { S3Client, PutObjectCommand, DeleteObjectCommand } = require("@aws-sdk/client-s3");

const s3 = new S3Client({ region: 'eu-west-1' });
const S3_BUCKET = 'vic-staging-private-enid';

chromium.setHeadlessMode = true;

exports.generatePDF = async (event) => {
console.log('Received headers:', event.headers);

const {
'x-document-contract-id': documentContractId,
'x-organization-id': organizationId,
'x-existing-file-path': existingContractFilePath
} = event.headers;

if (!documentContractId || !organizationId) {
return {
statusCode: 400,
body: JSON.stringify({ message: 'Missing required headers' }),
};
}

try {
const browser = await puppeteer.launch({
executablePath: await chromium.executablePath(),
headless: chromium.headless,
ignoreHTTPSErrors: true,
args: [...chromium.args, "--hide-scrollbars", "--disable-web-security", '--no-sandbox', '--disable-setuid-sandbox'],
});

const page = await browser.newPage();
await page.setContent(event.body);
const buffer = await page.pdf({ format: 'A4' });
await browser.close();

const fileName = `documents/contracts/${organizationId}/${documentContractId}-${Date.now()}.pdf`;
const s3Params = {
Bucket: S3_BUCKET,
Key: fileName,
Body: buffer,
ContentType: 'application/pdf',
};

await s3.send(new PutObjectCommand(s3Params));

if (existingContractFilePath) {
try {
await s3.send(new DeleteObjectCommand({
Bucket: S3_BUCKET,
Key: existingContractFilePath,
}));
} catch (error) {
console.error('Error deleting existing PDF from S3:', error);
// TODO: Log this error to Sentry
}
}

console.log(`PDF uploaded successfully: https://${s3Params.Bucket}.s3.amazonaws.com/${s3Params.Key}`);

return {
statusCode: 200,
body: JSON.stringify({
message: 'PDF successfully uploaded to S3',
url: s3Params.Key,
}),
};
} catch (error) {
console.error('Error in PDF generation process:', error);
return {
statusCode: 500,
body: JSON.stringify({ message: 'Internal server error during PDF generation' }),
};
}
};
File renamed without changes.
File renamed without changes.
23 changes: 23 additions & 0 deletions lambda-pdf-generator/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

app: pdf-generator
# "service" is the name of this project. This will also be added to your AWS resource names.
service: pdf-generator-service

# package:
# individually: true
# patterns:
# - '!node_modules/@sparticuz/chromium/bin/**'

provider:
name: aws
runtime: nodejs20.x
region: eu-west-1
timeout: 30

functions:
generatePDF:
handler: handler.generatePDF
events:
- httpApi: 'POST /generate-pdf'
memorySize: 512
description: 'Generate a PDF from an HTML template'
8 changes: 6 additions & 2 deletions mobile/src/screens/DocumentsContract.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo, useRef, useState } from 'react';
import { View, StyleSheet, Platform } from 'react-native';
import { View, StyleSheet, Platform, Linking } from 'react-native';
import PageLayout from '../layouts/PageLayout';
import { useTranslation } from 'react-i18next';
import { usePaddingTop } from '../hooks/usePaddingTop';
Expand Down Expand Up @@ -266,7 +266,11 @@ export const DocumentsContract = ({ navigation, route }: any) => {
// leftIcon={<DocumentIcon color={'yellow'} backgroundColor={'yellow'} />}
startDate={contract.documentStartDate}
endDate={contract.documentEndDate}
onPress={() => {}}
onPress={() => {
if (contract.documentFilePath) {
Linking.openURL(contract.documentFilePath);
}
}}
// todo: download contract
// onPress={() => onContractPress(item)}
info={info}
Expand Down
Loading

0 comments on commit 7f67828

Please sign in to comment.