Skip to content

Commit

Permalink
WIP: make bootstrapping-lambda accept direct invocations (from octopu…
Browse files Browse the repository at this point in the history
…s-api)
  • Loading branch information
twrichards committed Jun 14, 2024
1 parent 849690c commit 8739981
Show file tree
Hide file tree
Showing 16 changed files with 328 additions and 85 deletions.
34 changes: 4 additions & 30 deletions bootstrapping-lambda/local/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,5 @@
import { AppSync } from "@aws-sdk/client-appsync";
import {
pinboardConfigPromiseGetter,
STAGE,
standardAwsConfig,
} from "../../shared/awsIntegration";
import { APP } from "../../shared/constants";
import { ENVIRONMENT_VARIABLE_KEYS } from "../../shared/environmentVariables";
import { initEnvVars } from "./initLocalEnvVars";

pinboardConfigPromiseGetter("sentryDSN").then(
(sentryDSN) => (process.env[ENVIRONMENT_VARIABLE_KEYS.sentryDSN] = sentryDSN)
);

new AppSync(standardAwsConfig)
.listGraphqlApis({
maxResults: 25, // TODO consider implementing paging (for absolute future proofing)
})
.then((_) =>
_.graphqlApis?.find(
(api) => api.tags?.["Stage"] === STAGE && api.tags?.["App"] === APP
)
)
.then((appSyncAPI) => {
if (!appSyncAPI) {
throw Error("could not find AppSync API");
}
process.env[ENVIRONMENT_VARIABLE_KEYS.graphqlEndpoint] =
appSyncAPI.uris?.["GRAPHQL"];

import("../src/server"); // actually start the server, once the environment variable is set
});
initEnvVars().then(() => {
import("../src/server"); // actually start the server, once the environment variables are set
});
32 changes: 32 additions & 0 deletions bootstrapping-lambda/local/initLocalEnvVars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AppSync } from "@aws-sdk/client-appsync";
import {
pinboardConfigPromiseGetter,
STAGE,
standardAwsConfig,
} from "shared/awsIntegration";
import { APP } from "shared/constants";
import { ENVIRONMENT_VARIABLE_KEYS } from "shared/environmentVariables";

export const initEnvVars = (): Promise<void> => {
pinboardConfigPromiseGetter("sentryDSN").then(
(sentryDSN) =>
(process.env[ENVIRONMENT_VARIABLE_KEYS.sentryDSN] = sentryDSN)
);

return new AppSync(standardAwsConfig)
.listGraphqlApis({
maxResults: 25, // TODO consider implementing paging (for absolute future proofing)
})
.then((_) =>
_.graphqlApis?.find(
(api) => api.tags?.["Stage"] === STAGE && api.tags?.["App"] === APP
)
)
.then((appSyncAPI) => {
if (!appSyncAPI) {
throw Error("could not find AppSync API");
}
process.env[ENVIRONMENT_VARIABLE_KEYS.graphqlEndpoint] =
appSyncAPI.uris?.["GRAPHQL"];
});
};
58 changes: 58 additions & 0 deletions bootstrapping-lambda/local/simulateOctopus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import prompts from "prompts";
import { handleImagingCallFromOctopus } from "../src/octopusImagingHandler";
import { S3 } from "@aws-sdk/client-s3";
import { standardAwsConfig } from "shared/awsIntegration";
import { IMAGINE_REQUEST_TYPES } from "shared/octopusImaging";
import { getYourEmail } from "shared/local/yourEmail";
import { initEnvVars } from "./initLocalEnvVars";

initEnvVars().then(async () => {
const s3 = new S3(standardAwsConfig);

const newGridId = "TBC"; //TODO get media id of modified image

// noinspection InfiniteLoopJS
while (
// eslint-disable-next-line no-constant-condition
true
) {
const { args } = await prompts(
{
type: "select",
name: "args",
message: "Operation?",
choices: [
{
title: "ImagingOrderPickedUp",
value: {},
},
{
title: "GeneratePreSignedGridUploadUrl",
value: {
originalGridId: "223636f8d305a77e60fb2aa4525effbd66a7560d",
filename: "Historic_yachts_22.JPG",
newGridId,
requestType: IMAGINE_REQUEST_TYPES[0],
},
},
{
title: "ImagingOrderCompleted",
value: {
newGridId,
},
},
],
},
{ onCancel: () => process.exit() }
);

console.log(
(await handleImagingCallFromOctopus(s3, {
userEmail: await getYourEmail(),
workflowId: "65518",
pinboardItemId: "3458",
...args,
})) || "DONE"
);
}
});
1 change: 1 addition & 0 deletions bootstrapping-lambda/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"test": "jest --coverage --forceExit",
"bundle": "esbuild src/server.ts --bundle --minify --outfile=dist/index.js --platform=node --external:aws-sdk --external:@aws-sdk",
"watch": "ts-node-dev --respawn local/index.ts",
"simulate-octopus-call": "ts-node-dev --respawn local/simulateOctopus.ts",
"build": "run-p --print-label type-check bundle"
},
"devDependencies": {
Expand Down
24 changes: 24 additions & 0 deletions bootstrapping-lambda/src/appSyncRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CreateItemInput } from "shared/graphql/graphql";
import { AppSyncConfig } from "shared/appSyncConfig";
import { itemReturnFields } from "shared/itemReturnFields";
import fetch from "node-fetch";

export const appSyncCreateItem = (
config: AppSyncConfig,
input: CreateItemInput
) =>
fetch(config.graphqlEndpoint, {
method: "POST",
headers: {
authorization: config.authToken,
"content-type": "application/json",
},
body: JSON.stringify({
operationName: "SendMessage",
variables: {
input,
},
// the client listening to the subscription requires various fields to be returned, hence reuse of itemReturnFields
query: `mutation SendMessage($input: CreateItemInput!) { createItem(input: $input) { ${itemReturnFields} } }`,
}),
});
79 changes: 79 additions & 0 deletions bootstrapping-lambda/src/octopusImagingHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { S3 } from "@aws-sdk/client-s3";
import { generateAppSyncConfig } from "./generateAppSyncConfig";
import { generatePreSignedGridUploadUrl } from "shared/grid";
import { CreateItemInput } from "shared/graphql/graphql";
import { appSyncCreateItem } from "./appSyncRequest";
import {
IMAGING_COMPLETED_ITEM_TYPE,
IMAGING_PICKED_UP_ITEM_TYPE,
} from "shared/octopusImaging";

interface CommonArgs {
/* the email of the user who did the imaging work */
userEmail: string; //TODO probably receive the desktop auth token instead (to verify on pinboard side)
/* the id of the pinboard itself */
workflowId: string;
/* the itemId of the original request item in pinboard */
pinboardItemId: string;
}

type ImagingOrderPickedUp = CommonArgs;
interface GeneratePreSignedGridUploadUrl extends CommonArgs {
originalGridId: string;
filename: string;
/* SHA1 hash of the file content */
newGridId: string;
/* e.g. cutout, composite, etc */
requestType: string;
}
interface ImagingOrderCompleted extends CommonArgs {
/* SHA1 hash of the file content */
newGridId: string;
}

export type ImagingCallFromOctopus =
| ImagingOrderPickedUp
| GeneratePreSignedGridUploadUrl
| ImagingOrderCompleted;

export const isImagingCallFromOctopus = (
detail: any

Check warning on line 40 in bootstrapping-lambda/src/octopusImagingHandler.ts

View workflow job for this annotation

GitHub Actions / CI

Unexpected any. Specify a different type
): detail is ImagingCallFromOctopus => !!detail && "pinboardItemId" in detail;

export const handleImagingCallFromOctopus = async (
s3: S3,
detail: ImagingCallFromOctopus
): Promise<string | void> => {
console.log("Handling imaging call from Octopus", detail);
if ("originalGridId" in detail) {
return await generatePreSignedGridUploadUrl(detail);
}
const appSyncConfig = await generateAppSyncConfig(detail.userEmail, s3);
const appSyncCreateItemInput: CreateItemInput = {
pinboardId: detail.workflowId,
relatedItemId: detail.pinboardItemId,
claimable: false,
mentions: null, //TODO consider @ing the original requester for these updates
message: null,
groupMentions: null,
...("newGridId" in detail
? {
type: IMAGING_COMPLETED_ITEM_TYPE,
payload: null, //TODO make use of the newGridId
}
: {
type: IMAGING_PICKED_UP_ITEM_TYPE,
payload: null,
}),
};
return appSyncCreateItem(appSyncConfig, appSyncCreateItemInput).then(
async (response) => {
console.log(await response.text());
if (!response.ok) {
throw new Error(
`Failed to create item: ${response.status} ${response.statusText}`
);
}
}
);
};
20 changes: 17 additions & 3 deletions bootstrapping-lambda/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import {
} from "./middleware/auth-middleware";

import { getMetrics } from "./reporting/reportingServiceClient";
import {
handleImagingCallFromOctopus,
ImagingCallFromOctopus,
isImagingCallFromOctopus,
} from "./octopusImagingHandler";

const s3 = new S3(standardAwsConfig);

Expand Down Expand Up @@ -162,8 +167,17 @@ if (IS_RUNNING_LOCALLY) {
const PORT = 3030;
server.listen(PORT, () => console.log(`Listening on port ${PORT}`));
} else {
exports.handler = (
event: lambda.APIGatewayProxyEvent,
exports.handler = async (
payload: lambda.APIGatewayProxyEvent | ImagingCallFromOctopus,
context: lambda.Context
) => proxy(createServer(server), event, context);
) => {
if ("httpMethod" in payload) {
// from API Gateway
return proxy(createServer(server), payload, context);
} else if (isImagingCallFromOctopus(payload)) {
return await handleImagingCallFromOctopus(s3, payload);
}
console.error("unexpected payload", payload);
throw new Error("Not implemented");
};
}
9 changes: 9 additions & 0 deletions cdk/lib/__snapshots__/stack.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ Object {
"gu:cdk:version": "49.5.0",
},
"Outputs": Object {
"BootstrappingLambdaFunctionName": Object {
"Description": "pinboard-bootstrapping-lambda-TEST function name",
"Export": Object {
"Name": "pinboard-bootstrapping-lambda-TEST-function-name",
},
"Value": Object {
"Ref": "pinboardbootstrappinglambdaD2C487DA",
},
},
"pinboardbootstrappinglambdaapiEndpoint4DE1E032": Object {
"Value": Object {
"Fn::Join": Array [
Expand Down
9 changes: 8 additions & 1 deletion cdk/lib/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ export class PinBoardStack extends GuStack {

const bootstrappingLambdaBasename = "pinboard-bootstrapping-lambda";
const bootstrappingLambdaApiBaseName = `${bootstrappingLambdaBasename}-api`;
const bootstrappingLambdaFunctionName = `${bootstrappingLambdaBasename}-${this.stage}`;

const bootstrappingLambdaFunction = new lambda.Function(
this,
Expand All @@ -665,7 +666,7 @@ export class PinBoardStack extends GuStack {
"/pinboard/sentryDSN"
),
},
functionName: `${bootstrappingLambdaBasename}-${this.stage}`,
functionName: bootstrappingLambdaFunctionName,
code: lambda.Code.fromBucket(
deployBucket,
`${this.stack}/${this.stage}/${bootstrappingLambdaApiBaseName}/${bootstrappingLambdaApiBaseName}.zip`
Expand Down Expand Up @@ -740,5 +741,11 @@ export class PinBoardStack extends GuStack {
description: `${bootstrappingLambdaApiBaseName}-hostname`,
value: `${bootstrappingApiDomainName.domainNameAliasDomainName}`,
});

new CfnOutput(this, `BootstrappingLambdaFunctionName`, {
exportName: `${bootstrappingLambdaFunctionName}-function-name`,
description: `${bootstrappingLambdaFunctionName} function name`,
value: `${bootstrappingLambdaFunction.functionName}`,
});
}
}
24 changes: 1 addition & 23 deletions client/gql.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { gql } from "@apollo/client";
import { itemReturnFields } from "shared/itemReturnFields";

const pinboardReturnFields = `
composerId
Expand Down Expand Up @@ -47,29 +48,6 @@ export const gqlGetItemCounts = gql`
}
`;

const itemReturnFields = `
id
type
userEmail
timestamp
pinboardId
message
payload
mentions {
label
isMe
}
groupMentions {
label
isMe
}
claimedByEmail
claimable
relatedItemId
editHistory
deletedAt
`;

// TODO: consider updating the resolver (cdk/stack.ts) to use a Query with a secondary index (if performance degrades when we have lots of items)
export const gqlGetInitialItems = (pinboardId: string) => gql`
query MyQuery {
Expand Down
8 changes: 7 additions & 1 deletion client/src/itemDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import Pencil from "../icons/pencil.svg";
import { ITEM_HOVER_MENU_CLASS_NAME, ItemHoverMenu } from "./itemHoverMenu";
import { EditItem } from "./editItem";
import { Reply } from "./reply";
import { IMAGING_REQUEST_ITEM_TYPE } from "shared/octopusImaging";
import {
IMAGING_COMPLETED_ITEM_TYPE,
IMAGING_PICKED_UP_ITEM_TYPE,
IMAGING_REQUEST_ITEM_TYPE,
} from "shared/octopusImaging";

interface ItemDisplayProps {
item: Item | PendingItem;
Expand Down Expand Up @@ -97,6 +101,8 @@ export const ItemDisplay = ({
item.userEmail === userEmail &&
item.type !== "claim" &&
item.type !== IMAGING_REQUEST_ITEM_TYPE &&
item.type !== IMAGING_PICKED_UP_ITEM_TYPE &&
item.type !== IMAGING_COMPLETED_ITEM_TYPE &&
!item.claimedByEmail;

return (
Expand Down
Loading

0 comments on commit 8739981

Please sign in to comment.