Skip to content

Commit

Permalink
feat: add an audit table for applications submitted via S3 and Power …
Browse files Browse the repository at this point in the history
…Automate (#3397)
  • Loading branch information
jessicamcinchak authored Jul 10, 2024
1 parent 17f966d commit be4bb9d
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 16 deletions.
8 changes: 8 additions & 0 deletions api.planx.uk/modules/send/s3/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ describe(`uploading an application to S3`, () => {
slug: "unsupported-team",
},
});

queryMock.mockQuery({
name: "CreateS3Application",
matchOnVariables: false,
data: {
insertS3Application: { id: 1 },
},
});
});

it("requires auth", async () => {
Expand Down
72 changes: 56 additions & 16 deletions api.planx.uk/modules/send/s3/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import axios from "axios";
import type { NextFunction, Request, Response } from "express";
import { gql } from "graphql-request";
import { $api } from "../../../client";
import { Passport } from "../../../types";
import { uploadPrivateFile } from "../../file/service/uploadFile";
import { markSessionAsSubmitted } from "../../saveAndReturn/service/utils";
import axios from "axios";
import { isApplicationTypeSupported } from "../utils/helpers";
import { Passport } from "../../../types";

export async function sendToS3(
req: Request,
res: Response,
next: NextFunction,
) {
interface CreateS3Application {
insertS3Application: {
id: string;
};
}

const sendToS3 = async (req: Request, res: Response, next: NextFunction) => {
// `/upload-submission/:localAuthority` is only called via Hasura's scheduled event webhook, so body is wrapped in a "payload" key
const { payload } = req.body;
const localAuthority = req.params.localAuthority;
Expand Down Expand Up @@ -58,8 +61,7 @@ export async function sendToS3(
);

// Send a notification with the file URL to the Power Automate webook
let webhookResponseStatus: number | undefined;
await axios({
const webhookRequest = {
method: "POST",
url: powerAutomateWebhookURL,
adapter: "http",
Expand All @@ -73,20 +75,56 @@ export async function sendToS3(
file: fileUrl,
payload: doValidation ? "Validated ODP Schema" : "Discretionary",
},
})
.then((res) => {
// TODO Create & update audit table entry here
};
let webhookResponseStatus: number | undefined;
await axios(webhookRequest)
.then(async (res) => {
webhookResponseStatus = res.status;

// Mark session as submitted so that reminder and expiry emails are not triggered
markSessionAsSubmitted(sessionId);

// Create an audit entry
const applicationId = await $api.client.request<CreateS3Application>(
gql`
mutation CreateS3Application(
$session_id: String!
$team_slug: String!
$webhook_request: jsonb!
$webhook_response: jsonb = {}
) {
insertS3Application: insert_s3_applications_one(
object: {
session_id: $session_id
team_slug: $team_slug
webhook_request: $webhook_request
webhook_response: $webhook_response
}
) {
id
}
}
`,
{
session_id: sessionId,
team_slug: localAuthority,
webhook_request: webhookRequest,
webhook_response: res,
},
);

return {
application: {
...applicationId.insertS3Application,
},
};
})
.catch((error) => {
throw new Error(
`Failed to send submission notification to ${localAuthority}'s Power Automate Webhook (${sessionId}): ${error}`,
);
});

// Mark session as submitted so that reminder and expiry emails are not triggered
markSessionAsSubmitted(sessionId);

return res.status(200).send({
message: `Successfully uploaded submission to S3: ${fileUrl}`,
payload: doValidation ? "Validated ODP Schema" : "Discretionary",
Expand All @@ -100,4 +138,6 @@ export async function sendToS3(
}`,
});
}
}
};

export { sendToS3 };
40 changes: 40 additions & 0 deletions hasura.planx.uk/metadata/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,46 @@
- role: api
permission:
filter: {}
- table:
name: s3_applications
schema: public
insert_permissions:
- role: api
permission:
check: {}
columns:
- id
- webhook_request
- webhook_response
- session_id
- team_slug
- created_at
comment: ""
select_permissions:
- role: api
permission:
columns:
- id
- webhook_request
- webhook_response
- session_id
- team_slug
- created_at
filter: {}
comment: ""
update_permissions:
- role: api
permission:
columns:
- id
- webhook_request
- webhook_response
- session_id
- team_slug
- created_at
filter: {}
check: null
comment: ""
- table:
name: sessions
schema: public
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE "public.s3_applications" CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE "public"."s3_applications" (
"id" serial NOT NULL,
"session_id" text NOT NULL,
"team_slug" text NOT NULL,
"webhook_request" JSONB NOT NULL,
"webhook_response" JSONB NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY ("id")
);

COMMENT ON TABLE "public"."s3_applications" IS 'Stores a receipt of applications submitted using the Upload to AWS S3 method with notifications via Power Automate webhook';
79 changes: 79 additions & 0 deletions hasura.planx.uk/tests/s3_applications.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const { introspectAs } = require("./utils");

describe("s3_applications", () => {
describe("public", () => {
let i;
beforeAll(async () => {
i = await introspectAs("public");
});

test("cannot query s3 applications", () => {
expect(i.queries).not.toContain("s3_applications");
});

test("cannot create, update, or delete s3 applications", () => {
expect(i).toHaveNoMutationsFor("s3_applications");
});
});

describe("admin", () => {
let i;
beforeAll(async () => {
i = await introspectAs("admin");
});

test("has full access to query and mutate s3 applications", () => {
expect(i.queries).toContain("s3_applications");
expect(i.mutations).toContain("insert_s3_applications");
expect(i.mutations).toContain("update_s3_applications_by_pk");
expect(i.mutations).toContain("delete_s3_applications");
});
});

describe("platformAdmin", () => {
let i;
beforeAll(async () => {
i = await introspectAs("platformAdmin");
});

test("cannot query s3_applications", () => {
expect(i.queries).not.toContain("s3_applications");
});

test("cannot create, update, or delete s3_applications", () => {
expect(i).toHaveNoMutationsFor("s3_applications");
});
});

describe("teamEditor", () => {
let i;
beforeAll(async () => {
i = await introspectAs("teamEditor");
});

test("cannot query s3_applications", () => {
expect(i.queries).not.toContain("s3_applications");
});

test("cannot create, update, or delete s3_applications", () => {
expect(i).toHaveNoMutationsFor("s3_applications");
});
});

describe("api", () => {
let i;
beforeAll(async () => {
i = await introspectAs("api");
});

test("can query and mutate s3 applications", () => {
expect(i.queries).toContain("s3_applications");
expect(i.mutations).toContain("insert_s3_applications");
expect(i.mutations).toContain("update_s3_applications_by_pk");
});

test("cannot delete s3 applications", () => {
expect(i.mutations).not.toContain("delete_s3_applications");
});
});
});

0 comments on commit be4bb9d

Please sign in to comment.