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

Production deploy #2459

Merged
merged 13 commits into from
Nov 20, 2023
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
2 changes: 1 addition & 1 deletion api.planx.uk/docs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const parameters = {
type: "string",
required: true,
description:
"Name of the Local Authority, usually the same as Planx `team`",
"Name of the Local Authority, usually the same as PlanX `team`",
},
hasuraAuth: {
name: "authorization",
Expand Down
89 changes: 0 additions & 89 deletions api.planx.uk/inviteToPay/inviteToPay.ts

This file was deleted.

6 changes: 6 additions & 0 deletions api.planx.uk/lib/hasura/metadata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ interface ScheduledEvent {
args: ScheduledEventArgs;
}

export interface CombinedResponse {
bops?: ScheduledEventResponse;
uniform?: ScheduledEventResponse;
email?: ScheduledEventResponse;
}

interface ScheduledEventArgs {
headers: Record<string, string>[];
retry_conf: {
Expand Down
2 changes: 1 addition & 1 deletion api.planx.uk/modules/admin/session/zip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import supertest from "supertest";
import app from "../../../server";
import { authHeader } from "../../../tests/mockJWT";

jest.mock("../../../send/exportZip", () => ({
jest.mock("../../send/utils/exportZip", () => ({
buildSubmissionExportZip: jest.fn().mockResolvedValue({
filename: "tests/mocks/test.zip",
remove: jest.fn,
Expand Down
2 changes: 1 addition & 1 deletion api.planx.uk/modules/admin/session/zip.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
import { buildSubmissionExportZip } from "../../../send/exportZip";
import { buildSubmissionExportZip } from "../../send/utils/exportZip";

/**
* @swagger
Expand Down
10 changes: 10 additions & 0 deletions api.planx.uk/modules/gis/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Router } from "express";
import { locationSearch } from "./service";
import { classifiedRoadsSearch } from "./service/classifiedRoads";

const router = Router();

router.get("/gis/:localAuthority", locationSearch);
router.get("/roads", classifiedRoadsSearch);

export default router;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import supertest from "supertest";

import loadOrRecordNockRequests from "../tests/loadOrRecordNockRequests";
import app from "../server";
import loadOrRecordNockRequests from "../../../tests/loadOrRecordNockRequests";
import app from "../../../server";
import { PASSPORT_FN } from "./classifiedRoads";

it("returns an error if required query param is missing", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { gql } from "graphql-request";
import fetch from "isomorphic-fetch";
import { addDesignatedVariable, omitGeometry } from "./helpers";
import { baseSchema } from "./local_authorities/metadata/base";
import { $api } from "../client";
import { $api } from "../../../client";

export interface LocalAuthorityMetadata {
planningConstraints: {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import supertest from "supertest";

import { locationSearchWithTimeout } from "../gis";
import loadOrRecordNockRequests from "../tests/loadOrRecordNockRequests";
import app from "../server";
import loadOrRecordNockRequests from "../../../tests/loadOrRecordNockRequests";
import app from "../../../server";
import { locationSearchWithTimeout } from ".";

// Tests commented out due to reliance on external API calls and fallibility of nocks
// Please comment in and run locally if making changes to /gis functionality
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
rollupResultLayers,
addDesignatedVariable,
} from "../helpers.js";
import { planningConstraints } from "./metadata/scotland";
import { planningConstraints } from "./metadata/scotland.js";

// Process local authority metadata
const gisLayers = getQueryableConstraints(planningConstraints);
Expand Down
169 changes: 87 additions & 82 deletions api.planx.uk/pay/index.ts → api.planx.uk/modules/pay/controller.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,32 @@
import assert from "assert";
import { NextFunction, Request, Response } from "express";
import { Request } from "express";
import { responseInterceptor } from "http-proxy-middleware";
import SlackNotify from "slack-notify";
import { logPaymentStatus } from "../send/helpers";
import { logPaymentStatus } from "../send/utils/helpers";
import { usePayProxy } from "./proxy";
import { $api } from "../client";
import { ServerError } from "../errors";
import { GovUKPayment } from "@opensystemslab/planx-core/types";
import { addGovPayPaymentIdToPaymentRequest } from "./utils";
import { $api } from "../../client";
import { ServerError } from "../../errors";
import { GovUKPayment, PaymentRequest } from "@opensystemslab/planx-core/types";
import {
addGovPayPaymentIdToPaymentRequest,
postPaymentNotificationToSlack,
} from "./service/utils";
import {
InviteToPayController,
PaymentProxyController,
PaymentRequestProxyController,
} from "./types";

assert(process.env.SLACK_WEBHOOK_URL);

// exposed as /pay/:localAuthority and also used as middleware
// returns the url to make a gov uk payment
export async function makePaymentViaProxy(
req: Request,
res: Response,
next: NextFunction,
) {
// confirm that this local authority (aka team) has a pay token configured before creating the proxy
const isSupported =
process.env[`GOV_UK_PAY_TOKEN_${req.params.localAuthority.toUpperCase()}`];

if (!isSupported) {
return next(
new ServerError({
message: `GOV.UK Pay is not enabled for this local authority (${req.params.localAuthority})`,
status: 400,
}),
);
}

const flowId = req.query?.flowId as string | undefined;
const sessionId = req.query?.sessionId as string | undefined;
const teamSlug = req.params.localAuthority;

if (!flowId || !sessionId || !teamSlug) {
return next(
new ServerError({
message: "Missing required query param",
status: 400,
}),
);
}
export const makePaymentViaProxy: PaymentProxyController = async (
req,
res,
next,
) => {
const { flowId, sessionId } = res.locals.parsedReq.query;
const teamSlug = res.locals.parsedReq.params.localAuthority;

const session = await $api.session.findDetails(sessionId);

Expand Down Expand Up @@ -77,28 +61,16 @@ export async function makePaymentViaProxy(
},
req,
)(req, res, next);
}
};

export async function makeInviteToPayPaymentViaProxy(
req: Request,
res: Response,
next: NextFunction,
) {
// confirm that this local authority (aka team) has a pay token configured before creating the proxy
const isSupported =
process.env[`GOV_UK_PAY_TOKEN_${req.params.localAuthority.toUpperCase()}`];

if (!isSupported) {
return next({
status: 400,
message: `GOV.UK Pay is not enabled for this local authority (${req.params.localAuthority})`,
});
}

const flowId = req.query?.flowId as string | undefined;
const sessionId = req.query?.sessionId as string | undefined;
const paymentRequestId = req.params?.paymentRequest as string;
const teamSlug = req.params.localAuthority;
export const makeInviteToPayPaymentViaProxy: PaymentRequestProxyController = (
req,
res,
next,
) => {
const { flowId, sessionId } = res.locals.parsedReq.query;
const { localAuthority: teamSlug, paymentRequest: paymentRequestId } =
res.locals.parsedReq.params;

// drop req.params.localAuthority from the path when redirecting
// so redirects to plain [GOV_UK_PAY_URL] with correct bearer token
Expand Down Expand Up @@ -130,7 +102,7 @@ export async function makeInviteToPayPaymentViaProxy(
},
req,
)(req, res, next);
}
};

// exposed as /pay/:localAuthority/:paymentId and also used as middleware
// fetches the status of the payment
Expand All @@ -141,11 +113,10 @@ export const fetchPaymentViaProxy = fetchPaymentViaProxyWithCallback(

export function fetchPaymentViaProxyWithCallback(
callback: (req: Request, govUkPayment: GovUKPayment) => Promise<void>,
) {
return async (req: Request, res: Response, next: NextFunction) => {
const flowId = req.query?.flowId as string | undefined;
const sessionId = req.query?.sessionId as string | undefined;
const teamSlug = req.params.localAuthority;
): PaymentProxyController {
return async (req, res, next) => {
const { flowId, sessionId } = res.locals.parsedReq.query;
const teamSlug = res.locals.parsedReq.params.localAuthority;

// will redirect to [GOV_UK_PAY_URL]/:paymentId with correct bearer token
usePayProxy(
Expand Down Expand Up @@ -184,22 +155,56 @@ export function fetchPaymentViaProxyWithCallback(
};
}

export async function postPaymentNotificationToSlack(
req: Request,
govUkResponse: GovUKPayment,
label = "",
) {
// if it's a prod payment, notify #planx-notifications so we can monitor for subsequent submissions
if (govUkResponse?.payment_provider !== "sandbox") {
const slack = SlackNotify(process.env.SLACK_WEBHOOK_URL!);
const getStatus = (state: GovUKPayment["state"]) =>
state.status + (state.message ? ` (${state.message})` : "");
const payMessage = `:coin: New GOV Pay payment ${label} *${
govUkResponse.payment_id
}* with status *${getStatus(govUkResponse.state)}* [${
req.params.localAuthority
}]`;
await slack.send(payMessage);
console.log("Payment notification posted to Slack");
export const inviteToPay: InviteToPayController = async (_req, res, next) => {
const { sessionId } = res.locals.parsedReq.params;
const { payeeEmail, payeeName, applicantName, sessionPreviewKeys } =
res.locals.parsedReq.body;
// lock session before creating a payment request
const locked = await $api.session.lock(sessionId);
if (locked === null) {
return next(
new ServerError({
message: "session not found",
status: 404,
}),
);
}
}
if (locked === false) {
const cause = new Error(
"this session could not be locked, perhaps because it is already locked",
);
return next(
new ServerError({
message: `could not initiate a payment request: ${cause.message}`,
status: 400,
cause,
}),
);
}

let paymentRequest: PaymentRequest | undefined;
try {
paymentRequest = await $api.paymentRequest.create({
sessionId,
applicantName,
payeeName,
payeeEmail,
sessionPreviewKeys,
});
} catch (e: unknown) {
// revert the session lock on failure
await $api.session.unlock(sessionId);
return next(
new ServerError({
message:
e instanceof Error
? `could not initiate a payment request: ${e.message}`
: "could not initiate a payment request due to an unknown error",
status: 500,
cause: e,
}),
);
}

res.json(paymentRequest);
};
Loading
Loading