Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Aloituskuulutus: Hyväksyttyjen pdf:ien generointi ja tallennus HASSUun #118

Merged
merged 1 commit into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TilasiirtymaToiminto } from "../../../common/graphql/apiModel";
import { aloitusKuulutusHandler } from "../../src/handler/aloitusKuulutusHandler";
import { UserFixture } from "../../test/fixture/userFixture";
import { userService } from "../../src/user";
import { localstackS3Client } from "../util/s3Util";

const { expect } = require("chai");

Expand All @@ -21,6 +22,10 @@ async function takeSnapshot(oid: string) {
describe("AloitusKuulutus", () => {
let userFixture: UserFixture;

before(async () => {
localstackS3Client();
});

afterEach(() => {
userFixture.logout();
sinon.reset();
Expand Down
10 changes: 9 additions & 1 deletion backend/src/database/model/projekti.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { AloitusKuulutusTila, Kieli, ProjektiRooli, ProjektiTyyppi, Viranomainen } from "../../../../common/graphql/apiModel";
import {
AloitusKuulutusTila,
Kieli,
ProjektiRooli,
ProjektiTyyppi,
Viranomainen,
} from "../../../../common/graphql/apiModel";

export type Kuulutus = {
kuulutusPaiva?: string;
Expand Down Expand Up @@ -44,6 +50,8 @@ export type AloitusKuulutusJulkaisu = {
velho: Velho;
suunnitteluSopimus?: SuunnitteluSopimus | null;
kielitiedot?: Kielitiedot | null;
aloituskuulutusPDFPath?: string | null;
aloituskuulutusIlmoitusPDFPath?: string | null;
tila?: AloitusKuulutusTila | null;
muokkaaja?: string | null;
hyvaksyja?: string | null;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/database/projektiDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ async function updateAloitusKuulutusJulkaisu(projekti: DBProjekti, julkaisu: Alo
}
for (let idx = 0; idx < aloitusKuulutusJulkaisut.length; idx++) {
if (aloitusKuulutusJulkaisut[idx].id == julkaisu.id) {
log.info("deleteAloitusKuulutusJulkaisu", { idx, julkaisu });
log.info("updateAloitusKuulutusJulkaisu", { idx, julkaisu });

const params = {
TableName: projektiTableName,
Expand Down
51 changes: 42 additions & 9 deletions backend/src/files/fileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ export type UploadFileProperties = {
uploadURL: string;
};

export type CreateFileProperties = {
oid: string;
filePathInProjekti: string;
fileName: string;
contents: Buffer;
};

export type PersistFileProperties = { targetFilePathInProjekti: string; uploadedFileSource: string; oid: string };

export class FileService {
Expand All @@ -40,13 +47,13 @@ export class FileService {
/**
* Moves a file from temporary upload location to a permanent location under a projekti
*/
async persistFileToProjekti(param: PersistFileProperties) {
const filePath = this.removePrefixFromFile(param.uploadedFileSource);
async persistFileToProjekti(param: PersistFileProperties): Promise<string> {
const filePath = FileService.removePrefixFromFile(param.uploadedFileSource);
const sourceFileProperties = await this.getUploadedSourceFileInformation(filePath);

const fileNameFromUpload = this.getFileNameFromPath(filePath);
const fileNameFromUpload = FileService.getFileNameFromPath(filePath);
const targetPath =
this.getProjektiDirectory(param.oid) + `/${param.targetFilePathInProjekti}/${fileNameFromUpload}`;
FileService.getProjektiDirectory(param.oid) + `/${param.targetFilePathInProjekti}/${fileNameFromUpload}`;
try {
await getS3Client().send(
new CopyObjectCommand({
Expand All @@ -66,7 +73,29 @@ export class FileService {
}
}

private getProjektiDirectory(oid: string) {
/**
* Creates a file to projekti
*/
async createFileToProjekti(param: CreateFileProperties): Promise<string> {
const pathWithinProjekti = `/${param.filePathInProjekti}/${param.fileName}`;
const targetPath = FileService.getProjektiDirectory(param.oid) + pathWithinProjekti;
try {
const commandOutput = await getS3Client().send(
new PutObjectCommand({
Body: param.contents,
Bucket: config.yllapitoBucketName,
Key: targetPath,
})
);
log.info(`Created file ${targetPath}`, commandOutput.$metadata);
return pathWithinProjekti;
} catch (e) {
log.error(e);
throw new Error("Error creating file to yllapito");
}
}

private static getProjektiDirectory(oid: string) {
return `yllapito/tiedostot/projekti/${oid}`;
}

Expand All @@ -84,16 +113,16 @@ export class FileService {
}
}

getFileNameFromPath(uploadedFilePath: string) {
private static getFileNameFromPath(uploadedFilePath: string): string {
return uploadedFilePath.replace(/^[0-9a-z-]+\//, "");
}

removePrefixFromFile(uploadedFileSource: string) {
private static removePrefixFromFile(uploadedFileSource: string) {
return uploadedFileSource;
}

async archiveProjekti({ oid, timestamp }: ArchivedProjektiKey) {
const sourcePrefix = this.getProjektiDirectory(oid);
async archiveProjekti({ oid, timestamp }: ArchivedProjektiKey): Promise<void> {
const sourcePrefix = FileService.getProjektiDirectory(oid);
const targetPrefix = sourcePrefix + "/" + timestamp;
const sourceBucket = config.yllapitoBucketName;
const targetBucket = config.archiveBucketName;
Expand Down Expand Up @@ -144,6 +173,10 @@ export class FileService {
ContinuationToken = NextContinuationToken;
} while (ContinuationToken);
}

getYllapitoPathForProjektiFile(oid: string, path: string): string {
return `/${FileService.getProjektiDirectory(oid)}${path}`;
}
}

export const fileService = new FileService();
38 changes: 37 additions & 1 deletion backend/src/handler/aloitusKuulutusHandler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { AloitusKuulutusTila, TilaSiirtymaInput, TilasiirtymaToiminto } from "../../../common/graphql/apiModel";
import {
AloitusKuulutusTila,
AsiakirjaTyyppi,
TilaSiirtymaInput,
TilasiirtymaToiminto,
} from "../../../common/graphql/apiModel";
import { requirePermissionLuku, requirePermissionMuokkaa } from "../user";
import { requireProjektiPaallikko } from "../user/userService";
import { projektiDatabase } from "../database/projektiDatabase";
import { asiakirjaAdapter } from "./asiakirjaAdapter";
import { AloitusKuulutus, AloitusKuulutusJulkaisu, DBProjekti } from "../database/model/projekti";
import { asiakirjaService } from "../asiakirja/asiakirjaService";
import { fileService } from "../files/fileService";

function findAloitusKuulutusWaitingForApproval(projekti: DBProjekti): AloitusKuulutusJulkaisu | undefined {
if (projekti.aloitusKuulutusJulkaisut) {
Expand Down Expand Up @@ -39,6 +46,23 @@ async function reject(projekti: DBProjekti, aloitusKuulutus: AloitusKuulutus, sy
await projektiDatabase.deleteAloitusKuulutusJulkaisu(projekti, julkaisuWaitingForApproval);
}

async function createAloituskuulutusPDF(
asiakirjaTyyppi: AsiakirjaTyyppi,
julkaisuWaitingForApproval: AloitusKuulutusJulkaisu,
projekti: DBProjekti
) {
const pdf = await asiakirjaService.createPdf({
asiakirjaTyyppi,
aloitusKuulutusJulkaisu: julkaisuWaitingForApproval,
});
return await fileService.createFileToProjekti({
oid: projekti.oid,
filePathInProjekti: "aloituskuulutus",
fileName: pdf.nimi,
contents: Buffer.from(pdf.sisalto, "base64"),
});
}

async function approve(projekti: DBProjekti, aloitusKuulutus: AloitusKuulutus) {
const projektiPaallikko = requireProjektiPaallikko(projekti);
const julkaisuWaitingForApproval = findAloitusKuulutusWaitingForApproval(projekti);
Expand All @@ -48,7 +72,19 @@ async function approve(projekti: DBProjekti, aloitusKuulutus: AloitusKuulutus) {
await removeRejectionReasonIfExists(projekti, aloitusKuulutus);
julkaisuWaitingForApproval.tila = AloitusKuulutusTila.HYVAKSYTTY;
julkaisuWaitingForApproval.hyvaksyja = projektiPaallikko.uid;

await projektiDatabase.updateAloitusKuulutusJulkaisu(projekti, julkaisuWaitingForApproval);

julkaisuWaitingForApproval.aloituskuulutusPDFPath = await createAloituskuulutusPDF(
AsiakirjaTyyppi.ALOITUSKUULUTUS,
julkaisuWaitingForApproval,
projekti
);
julkaisuWaitingForApproval.aloituskuulutusIlmoitusPDFPath = await createAloituskuulutusPDF(
AsiakirjaTyyppi.ILMOITUS_KUULUTUKSESTA,
julkaisuWaitingForApproval,
projekti
);
}

async function removeRejectionReasonIfExists(projekti: DBProjekti, aloitusKuulutus: AloitusKuulutus) {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/handler/asiakirjaAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export class AsiakirjaAdapter {
if (dbProjekti.aloitusKuulutus) {
const { esitettavatYhteystiedot, palautusSyy: _palautusSyy, ...includedFields } = dbProjekti.aloitusKuulutus;
return {
id: createNextID(dbProjekti),
...includedFields,
id: createNextID(dbProjekti),
yhteystiedot: adaptYhteystiedot(dbProjekti, esitettavatYhteystiedot),
velho: adaptVelho(dbProjekti),
suunnitteluSopimus: cloneDeep(dbProjekti.suunnitteluSopimus),
Expand Down
11 changes: 9 additions & 2 deletions backend/src/handler/projektiAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import mergeWith from "lodash/mergeWith";
import { KayttoOikeudetManager } from "./kayttoOikeudetManager";
import { personSearch } from "../personSearch/personSearchClient";
import pickBy from "lodash/pickBy";
import { fileService } from "../files/fileService";

export class ProjektiAdapter {
public adaptProjekti(dbProjekti: DBProjekti): API.Projekti {
Expand All @@ -34,7 +35,7 @@ export class ProjektiAdapter {
aloitusKuulutus: adaptAloitusKuulutus(aloitusKuulutus),
suunnitteluSopimus: adaptSuunnitteluSopimus(suunnitteluSopimus),
liittyvatSuunnitelmat: adaptLiittyvatSuunnitelmat(liittyvatSuunnitelmat),
aloitusKuulutusJulkaisut: adaptAloitusKuulutusJulkaisut(aloitusKuulutusJulkaisut),
aloitusKuulutusJulkaisut: adaptAloitusKuulutusJulkaisut(dbProjekti.oid, aloitusKuulutusJulkaisut),
velho: {
__typename: "Velho",
...velho,
Expand Down Expand Up @@ -149,19 +150,25 @@ function adaptYhteystiedot(yhteystiedot: Yhteystieto[]): API.Yhteystieto[] {
}

function adaptAloitusKuulutusJulkaisut(
oid: string,
aloitusKuulutusJulkaisut?: AloitusKuulutusJulkaisu[] | null
): API.AloitusKuulutusJulkaisu[] | undefined {
if (aloitusKuulutusJulkaisut) {
return aloitusKuulutusJulkaisut.map((julkaisu) => {
const { yhteystiedot, velho, suunnitteluSopimus, kielitiedot, ...fieldsToCopyAsIs } = julkaisu;

return {
...fieldsToCopyAsIs,
__typename: "AloitusKuulutusJulkaisu",
yhteystiedot: adaptYhteystiedot(yhteystiedot),
velho: adaptVelho(velho),
suunnitteluSopimus: adaptSuunnitteluSopimus(suunnitteluSopimus),
kielitiedot: adaptKielitiedot(kielitiedot),
...fieldsToCopyAsIs,
aloituskuulutusPDFPath: fileService.getYllapitoPathForProjektiFile(oid, julkaisu.aloituskuulutusPDFPath),
aloituskuulutusIlmoitusPDFPath: fileService.getYllapitoPathForProjektiFile(
oid,
julkaisu.aloituskuulutusIlmoitusPDFPath
),
};
});
}
Expand Down
6 changes: 3 additions & 3 deletions backend/test/__snapshots__/apiHandler.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,9 @@ Array [
"Save projekti having while adding one muokkaaja more. There should be three persons in the projekti now",
Object {
"aloitusKuulutus": Object {
"__typename": "AloitusKuulutus",
"elyKeskus": "Pirkanmaa",
"esitettavatYhteystiedot": Array [
Object {
"__typename": "Yhteystieto",
"etunimi": "Marko",
"organisaatio": "Kajaani",
"puhelinnumero": "0293121213",
Expand Down Expand Up @@ -437,7 +435,9 @@ Object {
},
"aloitusKuulutusJulkaisut": Array [
Object {
"__typename": "AloitusKuulutus",
"__typename": "AloitusKuulutusJulkaisu",
"aloituskuulutusIlmoitusPDFPath": "/yllapito/tiedostot/projekti/1/aloituskuulutus/ILMOITUS TOIMIVALTAISEN VIRANOMAISEN KUULUTUKSESTA Testiprojekti 1.pdf",
"aloituskuulutusPDFPath": "/yllapito/tiedostot/projekti/1/aloituskuulutus/KUULUTUS SUUNNITTELUN ALOITTAMISESTA Testiprojekti 1.pdf",
"elyKeskus": "Pirkanmaa",
"hankkeenKuvaus": "Lorem Ipsum",
"hankkeenKuvausRuotsi": "På Svenska",
Expand Down
28 changes: 27 additions & 1 deletion backend/test/apiHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import cloneDeep from "lodash/cloneDeep";
import mergeWith from "lodash/mergeWith";
import { PersonSearchFixture } from "./personSearch/lambda/personSearchFixture";
import { Kayttajas } from "../src/personSearch/kayttajas";
import { AwsClientStub, mockClient } from "aws-sdk-client-mock";
import { getS3Client } from "../src/aws/clients";
import { PutObjectCommand, PutObjectCommandInput, S3Client } from "@aws-sdk/client-s3";

const { expect, assert } = require("chai");

Expand Down Expand Up @@ -51,6 +54,7 @@ describe("apiHandler", () => {
let insertAloitusKuulutusJulkaisuStub: sinon.SinonStub;
let updateAloitusKuulutusJulkaisuStub: sinon.SinonStub;
let deleteAloitusKuulutusJulkaisuStub: sinon.SinonStub;
let mockS3CLient: AwsClientStub<S3Client>;

beforeEach(() => {
createProjektiStub = sinon.stub(projektiDatabase, "createProjekti");
Expand All @@ -61,6 +65,7 @@ describe("apiHandler", () => {
updateAloitusKuulutusJulkaisuStub = sinon.stub(projektiDatabase, "updateAloitusKuulutusJulkaisu");
deleteAloitusKuulutusJulkaisuStub = sinon.stub(projektiDatabase, "deleteAloitusKuulutusJulkaisu");
loadVelhoProjektiByOidStub = sinon.stub(velho, "loadProjekti");
mockS3CLient = mockClient(getS3Client());

fixture = new ProjektiFixture();
personSearchFixture = new PersonSearchFixture();
Expand All @@ -84,6 +89,13 @@ describe("apiHandler", () => {
createProjektiStub.resolves();
}

function validatePutObjectCommandInput(callNumber: number, yllapitoBucketName: string, filePath: string) {
const { Bucket: aloituskuulutusBucket, Key: aloituskuulutusKey } = mockS3CLient.call(callNumber).args[0]
.input as PutObjectCommandInput;
expect(aloituskuulutusBucket).to.eq(yllapitoBucketName);
expect(aloituskuulutusKey).to.eq(filePath);
}

describe("lataaProjekti", () => {
it("should load a new project from Velho", async () => {
userFixture.loginAs(UserFixture.mattiMeikalainen);
Expand Down Expand Up @@ -271,7 +283,7 @@ describe("apiHandler", () => {
],
suunnitteluSopimus: null,
euRahoitus: false, // mandatory field for perustiedot
aloitusKuulutus: fixture.aloitusKuulutus,
aloitusKuulutus: fixture.aloitusKuulutusInput,
kielitiedot: {
ensisijainenKieli: Kieli.SUOMI,
toissijainenKieli: Kieli.SAAME,
Expand Down Expand Up @@ -320,12 +332,26 @@ describe("apiHandler", () => {
});

// Accept aloituskuulutus
mockS3CLient.on(PutObjectCommand).resolves({});
await api.siirraTila({
oid,
tyyppi: TilasiirtymaTyyppi.ALOITUSKUULUTUS,
toiminto: TilasiirtymaToiminto.HYVAKSY,
});

const calls = mockS3CLient.calls();
expect(calls).to.have.length(2);
validatePutObjectCommandInput(
0,
"hassu-localstack-yllapito",
"yllapito/tiedostot/projekti/1/aloituskuulutus/KUULUTUS SUUNNITTELUN ALOITTAMISESTA Testiprojekti 1.pdf"
);
validatePutObjectCommandInput(
1,
"hassu-localstack-yllapito",
"yllapito/tiedostot/projekti/1/aloituskuulutus/ILMOITUS TOIMIVALTAISEN VIRANOMAISEN KUULUTUKSESTA Testiprojekti 1.pdf"
);

// Verify that the accepted aloituskuulutus is available
await validateAloitusKuulutusState({ oid, expectedState: AloitusKuulutusTila.HYVAKSYTTY });

Expand Down
7 changes: 3 additions & 4 deletions backend/test/files/fileService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import { fileService } from "../../src/files/fileService";
import * as sinon from "sinon";
import { getS3Client } from "../../src/aws/clients";
import { uuid } from "../../src/util/uuid";
import { mockClient } from "aws-sdk-client-mock";
import { AwsStub } from "aws-sdk-client-mock/dist/types/awsClientStub";
import { HeadObjectCommand } from "@aws-sdk/client-s3";
import { AwsClientStub, mockClient } from "aws-sdk-client-mock";
import { HeadObjectCommand, S3Client } from "@aws-sdk/client-s3";

const { expect } = require("chai");

const sandbox = sinon.createSandbox();

describe("UploadService", () => {
let mockS3CLient: AwsStub<any, any>;
let mockS3CLient: AwsClientStub<S3Client>;

afterEach(() => {
sandbox.reset();
Expand Down
Loading