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 #2326

Merged
merged 17 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a636268
chore: Bump API memory (and CPU) (#2312)
DafyddLlyr Oct 16, 2023
102393e
fix: Disable grant_new_user_template_team_access trigger in DB sync (…
DafyddLlyr Oct 17, 2023
bf52435
feat: Explicitly handle redaction (#2313)
DafyddLlyr Oct 17, 2023
c835985
[skip pizza] bump @babel/traverse in /hasura.planx.uk/tests (#2317)
dependabot[bot] Oct 17, 2023
3bc7483
chore(deps): bump @babel/traverse in /editor.planx.uk (#2318)
dependabot[bot] Oct 17, 2023
34f5159
chore: Convert remain /admin endpoint to client (#2320)
DafyddLlyr Oct 17, 2023
7dba169
chore(deps-dev): bump @babel/traverse in /api.planx.uk (#2316)
dependabot[bot] Oct 17, 2023
44eebf0
chore: update local development instructions in README (#2321)
jessicamcinchak Oct 17, 2023
a58803e
fix: Unique key for `FormWrapper` in `ChecklistComponent` (#2324)
DafyddLlyr Oct 18, 2023
08f4e1b
fix: Drop redundant (and error-throwing) `:first-child` selector (#2325)
DafyddLlyr Oct 18, 2023
3515034
chore: Convert download-schema endpoint to public client (#2305)
DafyddLlyr Oct 18, 2023
017f77c
chore: Update `planx-core` (#2323)
DafyddLlyr Oct 18, 2023
9515b88
feat: track user resetting flow (#2307)
Mike-Heneghan Oct 18, 2023
d86cd87
feat: update analytics_log with the created_at of new analytics_log r…
Mike-Heneghan Oct 18, 2023
93cf90e
feat: add an admin endpoint for the Digital Planning Application form…
jessicamcinchak Oct 19, 2023
c79cbd4
chore: Improve Uniform error logging (#2327)
DafyddLlyr Oct 19, 2023
40a8b01
chore: Update Bucks certificates [skip pizza] (#2329)
DafyddLlyr Oct 19, 2023
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
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
API_PORT=7002
API_URL_EXT=http://localhost:${API_PORT}

# JWT_SECRET must be at least 32 characters
JWT_SECRET=👻

SESSION_SECRET=👻

# Google Cloud OAuth 2.0 Client ID
GOOGLE_CLIENT_ID=👻
GOOGLE_CLIENT_SECRET=👻

Expand Down Expand Up @@ -49,9 +51,9 @@ MINIO_ADMIN_PORT=9001

# PostgreSQL
PG_PORT=7001
PG_DATABASE=👻
PG_DATABASE=planxdb
PG_USERNAME=dbuser
PG_PASSWORD=👻
PG_USERNAME=👻

# PG user with permission to sync content between environments
PRODUCTION_PG_URL_FOR_USER_GITHUB_ACTIONS=👻
Expand Down
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ planx-new is a monorepo containing our full application stack. Here's a quick su

1. Download and install the following dependencies if you don't have them already:
- [Docker](https://docs.docker.com/get-docker/)
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
- [Docker Compose](https://docs.docker.com/compose/install/)
- [PNPM](https://github.com/pnpm/pnpm) `npm install -g [email protected]`
- [Node](https://nodejs.org/en/download) `pnpm env use --global 18.16.1`
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)

1. Clone this repository.
**If you're an OSL developer:**

1. Clone this repository

2. Setup your AWS CLI client with SSO - [detailed guide here](https://github.com/theopensystemslab/planx-new/blob/main/doc/how-to/how-to-setup-aws-sso-credentials.md)

Expand All @@ -48,6 +51,30 @@ planx-new is a monorepo containing our full application stack. Here's a quick su

9. Open `http://localhost:3000` and login with your Google email address

**If you're not a member of OSL, follow these steps:**

1. Fork or clone this repository

2. Copy all `.example` env files into their respective directories and replace 👻 with a string like "SECRET" or longer where noted

3. Setup free OAuth Client ID credentails in the [Google Cloud APIs console](https://console.cloud.google.com/apis/credentials)
- Application type = "Web application"
- Authorised JavaScript origins = "http://localhost:3000"
- Authorised redirect URIs = "http://localhost:7002/auth/google/callback"
- Update the `GOOGLE_CLIENT_ID` & `GOOGLE_CLIENT_SECRET` env vars with your new credentials

4. Run `pnpm start` from the project root to set up docker containers for the application's backend (postgres, sharedb, api and hasura server processes). Please note you will not be able to run commands that sync seed data from production.

5. Move into the hasura directory `cd ../hasura.planx.uk` and install dependencies `pnpm i`.

6. Open [Hasura's](https://hasura.io/) web console (`cd hasura.planx.uk` then `pnpm start`) and add your Google email address to the `users` table. You'll also likely want to create an initial `team`. This will eventually allow you to authenticate into the application as an admin.

7. Follow steps 7-9 above to start the editor and login !

At this point you'll be running the full Planx application locally in a docker container. See our Github Actions pull request workflow as an example of how to deploy via docker to a virtual linux server, or explore the `infrastructure` directory for how to deploy via Pulumi infrastructure-as-code to AWS.

We'd love to hear what you're building on Planx, don't hesitate to get in touch with questions.

### Docker

The root of the project has several scripts set up to help you manage your docker containers:
Expand Down Expand Up @@ -111,3 +138,4 @@ There are a few dependent packages that are closely related to this project:

- https://github.com/theopensystemslab/planx-core
- https://github.com/theopensystemslab/map
- https://github.com/theopensystemslab/digital-planning-data-schemas
4 changes: 1 addition & 3 deletions api.planx.uk/admin/session/bops.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { expectedPayload } from "../../tests/mocks/bopsMocks";
const endpoint = (strings: TemplateStringsArray) =>
`/admin/session/${strings[0]}/bops`;

const mockGenerateBOPSPayload = jest.fn().mockResolvedValue({
exportData: expectedPayload,
});
const mockGenerateBOPSPayload = jest.fn().mockResolvedValue(expectedPayload);

jest.mock("@opensystemslab/planx-core", () => {
return {
Expand Down
2 changes: 1 addition & 1 deletion api.planx.uk/admin/session/bops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const getBOPSPayload = async (
next: NextFunction,
) => {
try {
const { exportData } = await $api.export.bopsPayload(req.params.sessionId);
const exportData = await $api.export.bopsPayload(req.params.sessionId);
res.set("content-type", "application/json");
return res.send(exportData);
} catch (error) {
Expand Down
17 changes: 7 additions & 10 deletions api.planx.uk/admin/session/csv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@ import { authHeader } from "../../tests/mockJWT";
const endpoint = (strings: TemplateStringsArray) =>
`/admin/session/${strings[0]}/csv`;

const mockGenerateCSVData = jest.fn().mockResolvedValue({
responses: [
{
question: "Is this a test?",
responses: [{ value: "Yes" }],
metadata: {},
},
],
redactedResponses: [],
});
const mockGenerateCSVData = jest.fn().mockResolvedValue([
{
question: "Is this a test?",
responses: [{ value: "Yes" }],
metadata: {},
},
]);
jest.mock("@opensystemslab/planx-core", () => {
return {
CoreDomainClient: jest.fn().mockImplementation(() => ({
Expand Down
8 changes: 3 additions & 5 deletions api.planx.uk/admin/session/csv.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { stringify } from "csv-stringify";
import { NextFunction, Request, Response } from "express";
import { getClient } from "../../client";
import { $api } from "../../client";

/**
* @swagger
Expand All @@ -26,8 +26,7 @@ export async function getCSVData(
next: NextFunction,
) {
try {
const $client = getClient();
const { responses } = await $client.export.csvData(req.params.sessionId);
const responses = await $api.export.csvData(req.params.sessionId);

if (req.query?.download) {
stringify(responses, {
Expand Down Expand Up @@ -70,8 +69,7 @@ export async function getRedactedCSVData(
next: NextFunction,
) {
try {
const $client = getClient();
const { redactedResponses } = await $client.export.csvData(
const redactedResponses = await $api.export.csvDataRedacted(
req.params.sessionId,
);

Expand Down
53 changes: 53 additions & 0 deletions api.planx.uk/admin/session/digitalPlanningData.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import supertest from "supertest";
import app from "../../server";
import { authHeader } from "../../tests/mockJWT";
import { expectedPlanningPermissionPayload } from "../../tests/mocks/digitalPlanningDataMocks";

const endpoint = (strings: TemplateStringsArray) =>
`/admin/session/${strings[0]}/digital-planning-application`;

const mockGenerateDigitalPlanningApplicationPayload = jest
.fn()
.mockResolvedValue(expectedPlanningPermissionPayload);

jest.mock("@opensystemslab/planx-core", () => {
return {
CoreDomainClient: jest.fn().mockImplementation(() => ({
export: {
digitalPlanningDataPayload: () =>
mockGenerateDigitalPlanningApplicationPayload(),
},
})),
};
});

describe("Digital Planning Application payload admin endpoint", () => {
it("requires a user to be logged in", async () => {
await supertest(app)
.get(endpoint`123`)
.expect(401)
.then((res) =>
expect(res.body).toEqual({
error: "No authorization token was found",
}),
);
});

it("requires a user to have the 'platformAdmin' role", async () => {
await supertest(app)
.get(endpoint`123`)
.set(authHeader({ role: "teamEditor" }))
.expect(403);
});

it("returns a valid JSON payload", async () => {
await supertest(app)
.get(endpoint`123`)
.set(authHeader({ role: "platformAdmin" }))
.expect(200)
.expect("content-type", "application/json; charset=utf-8")
.then((res) =>
expect(res.body).toEqual(expectedPlanningPermissionPayload),
);
});
});
35 changes: 35 additions & 0 deletions api.planx.uk/admin/session/digitalPlanningData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { NextFunction, Request, Response } from "express";
import { $api } from "../../client";

/**
* @swagger
* /admin/session/{sessionId}/digital-planning-application:
* get:
* summary: Generates a Digital Planning Application payload
* description: Generates a Digital Planning Application payload and validates it against the Digital Planning Data JSON Schema
* tags:
* - admin
* parameters:
* - $ref: '#/components/parameters/sessionId'
* security:
* - bearerAuth: []
*/
export const getDigitalPlanningApplicationPayload = async (
req: Request,
res: Response,
next: NextFunction,
) => {
try {
const data = await $api.export.digitalPlanningDataPayload(
req.params.sessionId,
);
res.set("content-type", "application/json");
return res.send(data);
} catch (error) {
return next({
message:
"Failed to make Digital Planning Application payload: " +
(error as Error).message,
});
}
};
12 changes: 5 additions & 7 deletions api.planx.uk/admin/session/html.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { generateApplicationHTML } from "@opensystemslab/planx-core";
import { getClient } from "../../client";
import { $api } from "../../client";
import type { RequestHandler } from "express";
import type { PlanXExportData } from "@opensystemslab/planx-core/types";

Expand All @@ -20,11 +20,10 @@ type HTMLExportHandler = RequestHandler<{ sessionId: string }, string>;
*/
export const getHTMLExport: HTMLExportHandler = async (req, res, next) => {
try {
const $client = getClient();
const session = await $client.session.find(req.params.sessionId);
const session = await $api.session.find(req.params.sessionId);
if (!session) throw Error(`Unable to find session ${req.params.sessionId}`);

const { responses } = await $client.export.csvData(req.params.sessionId);
const responses = await $api.export.csvData(req.params.sessionId);
const boundingBox =
session.data.passport.data["property.boundary.site.buffered"];

Expand Down Expand Up @@ -61,11 +60,10 @@ export const getRedactedHTMLExport: HTMLExportHandler = async (
next,
) => {
try {
const $client = getClient();
const session = await $client.session.find(req.params.sessionId);
const session = await $api.session.find(req.params.sessionId);
if (!session) throw Error(`Unable to find session ${req.params.sessionId}`);

const { redactedResponses } = await $client.export.csvData(
const redactedResponses = await $api.export.csvDataRedacted(
req.params.sessionId,
);
const boundingBox =
Expand Down
4 changes: 3 additions & 1 deletion api.planx.uk/admin/session/oneAppXML.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const mockGenerateOneAppXML = jest
jest.mock("../../client", () => {
return {
$api: {
generateOneAppXML: () => mockGenerateOneAppXML(),
export: {
oneAppPayload: () => mockGenerateOneAppXML(),
},
},
};
});
Expand Down
2 changes: 1 addition & 1 deletion api.planx.uk/admin/session/oneAppXML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const getOneAppXML = async (
next: NextFunction,
) => {
try {
const xml = await $api.generateOneAppXML(req.params.sessionId);
const xml = await $api.export.oneAppPayload(req.params.sessionId);
res.set("content-type", "text/xml");
return res.send(xml);
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion api.planx.uk/inviteToPay/createPaymentSendEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const createPaymentSendEvents = async (
const now = new Date();
const combinedResponse: CombinedResponse = {};

const session = await $api.getSessionById(payload.sessionId);
const session = await $api.session.find(payload.sessionId);
if (!session) {
return next({
status: 400,
Expand Down
6 changes: 3 additions & 3 deletions api.planx.uk/inviteToPay/inviteToPay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function inviteToPay(
}

// lock session before creating a payment request
const locked = await $api.lockSession(sessionId);
const locked = await $api.session.lock(sessionId);
if (locked === null) {
return next(
new ServerError({
Expand All @@ -63,7 +63,7 @@ export async function inviteToPay(

let paymentRequest: PaymentRequest | undefined;
try {
paymentRequest = await $api.createPaymentRequest({
paymentRequest = await $api.paymentRequest.create({
sessionId,
applicantName,
payeeName,
Expand All @@ -72,7 +72,7 @@ export async function inviteToPay(
});
} catch (e: unknown) {
// revert the session lock on failure
await $api.unlockSession(sessionId);
await $api.session.unlock(sessionId);
return next(
new ServerError({
message:
Expand Down
2 changes: 1 addition & 1 deletion api.planx.uk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"dependencies": {
"@airbrake/node": "^2.1.8",
"@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#d92224b",
"@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#c2d6f35",
"@types/isomorphic-fetch": "^0.0.36",
"adm-zip": "^0.5.10",
"aws-sdk": "^2.1467.0",
Expand Down
Loading
Loading