Skip to content

Commit

Permalink
feat(dynamo db): integrate with dynamo db (#770)
Browse files Browse the repository at this point in the history
* feat(step functions service): add step functions service

* feat(dynamo cb client): add get all items function

* feat(config): add env var for step functions

* feat(dynamodb): integration with infra service

* test(dynamodb service): add test cases

* fix(infra service): fix wrong feature flag

* style(infra service): use env var rather than inline const

* feat(dynamodb service): add type guard

* fix(typeguard): fix type guard

* style(env var): change naming

* fix(typeguard): check for undefined values

* style(env var): better naming

* fix(config): give default value for step functions arn

* style(dynamoDb service): add args to constructor for ease of testing

* fix(infra service): add awaits

* fix(step functions): region should come from env vars

* fix(infra service): handle error appropriately
  • Loading branch information
kishore03109 authored May 24, 2023
1 parent 5b0a50f commit b768fea
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 134 deletions.
41 changes: 41 additions & 0 deletions microservices/site-launch/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,44 @@ export interface SiteLaunchMessage {
status?: SiteLaunchStatus
statusMetadata?: string
}

export function isSiteLaunchMessage(obj: unknown): obj is SiteLaunchMessage {
if (!obj) {
return false
}

const message = obj as SiteLaunchMessage

return (
typeof message.repoName === "string" &&
typeof message.appId === "string" &&
typeof message.primaryDomainSource === "string" &&
typeof message.primaryDomainTarget === "string" &&
typeof message.domainValidationSource === "string" &&
typeof message.domainValidationTarget === "string" &&
typeof message.requestorEmail === "string" &&
typeof message.agencyEmail === "string" &&
(typeof message.githubRedirectionUrl === "undefined" ||
typeof message.githubRedirectionUrl === "string") &&
(typeof message.redirectionDomain === "undefined" ||
(Array.isArray(message.redirectionDomain) &&
message.redirectionDomain.every(
(rd) =>
typeof rd.source === "string" &&
typeof rd.target === "string" &&
typeof rd.type === "string"
))) &&
(typeof message.status === "undefined" ||
(typeof message.status === "object" &&
typeof message.status.state === "string" &&
(message.status.state === "success" ||
message.status.state === "failure" ||
message.status.state === "pending") &&
typeof message.status.message === "string" &&
Object.keys(SiteLaunchLambdaStatus).includes(
message.status.message as SiteLaunchLambdaStatus
))) &&
(typeof message.statusMetadata === "undefined" ||
typeof message.statusMetadata === "string")
)
}
28 changes: 22 additions & 6 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,13 @@ const config = convict({
},
},
aws: {
region: {
doc: "AWS region",
env: "AWS_REGION",
format: "required-string",
default: "ap-southeast-1",
},
amplify: {
region: {
doc: "AWS region",
env: "AWS_REGION",
format: "required-string",
default: "ap-southeast-1",
},
accountNumber: {
doc: "AWS account number (microservices)",
env: "AWS_ACCOUNT_NUMBER",
Expand Down Expand Up @@ -180,6 +180,14 @@ const config = convict({
default: "site-launch",
},
},
stepFunctions: {
stepFunctionsArn: {
doc: "Amazon Resource Name (ARN) of the Step Functions state machine",
env: "STEP_FUNCTIONS_ARN",
format: "required-string",
default: "SiteLaunchStepFunctions-dev",
},
},
sqs: {
incomingQueueUrl: {
doc: "URL of the incoming SQS queue",
Expand All @@ -193,6 +201,14 @@ const config = convict({
format: "required-string",
default: "",
},
featureFlags: {
shouldDeprecateSiteQueues: {
doc: "Whether the queues are deprecated",
env: "FF_DEPRECATE_SITE_QUEUES",
format: "required-boolean",
default: false,
},
},
},
},
github: {
Expand Down
13 changes: 11 additions & 2 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { SubcollectionPageService } from "@root/services/fileServices/MdPageServ
import { UnlinkedPageService } from "@root/services/fileServices/MdPageServices/UnlinkedPageService"
import { CollectionYmlService } from "@root/services/fileServices/YmlFileServices/CollectionYmlService"
import { FooterYmlService } from "@root/services/fileServices/YmlFileServices/FooterYmlService"
import DynamoDBService from "@root/services/infra/DynamoDBService"
import { isomerRepoAxiosInstance } from "@services/api/AxiosInstance"
import { ResourceRoomDirectoryService } from "@services/directoryServices/ResourceRoomDirectoryService"
import { ConfigYmlService } from "@services/fileServices/YmlFileServices/ConfigYmlService"
Expand All @@ -58,6 +59,7 @@ import ReposService from "@services/identity/ReposService"
import SitesService from "@services/identity/SitesService"
import InfraService from "@services/infra/InfraService"
import { statsService } from "@services/infra/StatsService"
import StepFunctionsService from "@services/infra/StepFunctionsService"
import ReviewRequestService from "@services/review/ReviewRequestService"

import { apiLogger } from "./middleware/apiLogger"
Expand All @@ -71,6 +73,7 @@ import { PageService } from "./services/fileServices/MdPageServices/PageService"
import CollaboratorsService from "./services/identity/CollaboratorsService"
import LaunchClient from "./services/identity/LaunchClient"
import LaunchesService from "./services/identity/LaunchesService"
import DynamoDBDocClient from "./services/infra/DynamoDBClient"
import { rateLimiter } from "./services/utilServices/RateLimiter"
import { isSecure } from "./utils/auth-utils"

Expand Down Expand Up @@ -205,6 +208,10 @@ const launchesService = new LaunchesService({
launchClient,
})
const queueService = new QueueService()
const stepFunctionsService = new StepFunctionsService()
const dynamoDBService = new DynamoDBService({
dynamoDBClient: new DynamoDBDocClient(),
})

const identityAuthService = getIdentityAuthService(gitHubService)
const collaboratorsService = new CollaboratorsService({
Expand All @@ -222,9 +229,11 @@ const infraService = new InfraService({
launchesService,
queueService,
collaboratorsService,
stepFunctionsService,
dynamoDBService,
})
// poller for incoming queue
infraService.pollQueue()
// poller site launch updates
infraService.pollMessages()

const authenticationMiddleware = getAuthenticationMiddleware()
const authorizationMiddleware = getAuthorizationMiddleware({
Expand Down
43 changes: 7 additions & 36 deletions src/services/infra/DynamoDBClient.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { DynamoDBClient, ScanCommandOutput } from "@aws-sdk/client-dynamodb"
import {
DynamoDBDocumentClient,
PutCommand,
GetCommand,
UpdateCommand,
DeleteCommand,
UpdateCommandInput,
PutCommandOutput,
UpdateCommandOutput,
DeleteCommandOutput,
GetCommandOutput,
ScanCommand,
} from "@aws-sdk/lib-dynamodb"
import autoBind from "auto-bind"

Expand Down Expand Up @@ -43,7 +39,7 @@ export default class DynamoDBDocClient {
}

this.dynamoDBDocClient = DynamoDBDocumentClient.from(
new DynamoDBClient({ region: config.get("aws.amplify.region") }),
new DynamoDBClient({ region: config.get("aws.region") }),
{ marshallOptions, unmarshallOptions }
)

Expand All @@ -52,7 +48,7 @@ export default class DynamoDBDocClient {

private withLogger = async <T>(
promise: Promise<T>,
type: "create" | "get" | "delete" | "update"
type: "create" | "scan" | "delete" | "update"
): Promise<T> => {
try {
return await promise
Expand Down Expand Up @@ -82,38 +78,13 @@ export default class DynamoDBDocClient {
)
}

getItem = async (
tableName: string,
key: string
): Promise<GetCommandOutput> => {
getAllItems = async (tableName: string): Promise<ScanCommandOutput> => {
const params = {
TableName: tableName,
Key: { appId: key },
}

return this.withLogger(
this.dynamoDBDocClient.send(new GetCommand(params)),
"get"
)
}

updateItem = async ({
TableName,
Key,
UpdateExpression,
ExpressionAttributeNames,
ExpressionAttributeValues,
}: UpdateParams): Promise<UpdateCommandOutput> => {
const params: UpdateCommandInput = {
TableName,
Key,
UpdateExpression,
ExpressionAttributeNames,
ExpressionAttributeValues,
}
return this.withLogger(
this.dynamoDBDocClient.send(new UpdateCommand(params)),
"update"
this.dynamoDBDocClient.send(new ScanCommand(params)),
"scan"
)
}

Expand Down
88 changes: 31 additions & 57 deletions src/services/infra/DynamoDBService.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,56 @@
import {
DeleteCommandOutput,
GetCommandOutput,
UpdateCommandOutput,
} from "@aws-sdk/lib-dynamodb"
import { AttributeValue } from "@aws-sdk/client-dynamodb"
import { DeleteCommandOutput } from "@aws-sdk/lib-dynamodb"
import autoBind from "auto-bind"

import { config } from "@config/config"

import { SiteLaunchMessage } from "@root/../microservices/site-launch/shared/types"
import {
SiteLaunchMessage,
isSiteLaunchMessage,
} from "@root/../microservices/site-launch/shared/types"

import DynamoDBClient, { UpdateParams } from "./DynamoDBClient"
import DynamoDBClient from "./DynamoDBClient"

const MOCK_LAUNCH: SiteLaunchMessage = {
repoName: "my-repo",
appId: "my-app",
primaryDomainSource: "example.com",
primaryDomainTarget: "myapp.example.com",
domainValidationSource: "example.com",
domainValidationTarget: "myapp.example.com",
requestorEmail: "[email protected]",
agencyEmail: "[email protected]",
githubRedirectionUrl: "https://github.com/my-repo",
redirectionDomain: [
{
source: "example.com",
target: "myapp.example.com",
type: "A",
},
],
status: { state: "pending", message: "PENDING_DURING_SITE_LAUNCH" },
}
export default class DynamoDBService {
private readonly dynamoDBClient: DynamoDBClient

private readonly TABLE_NAME: string

constructor() {
this.dynamoDBClient = new DynamoDBClient()
this.TABLE_NAME = config.get("aws.dynamodb.siteLaunchTableName")
constructor({
dynamoDBClient,
dynamoDbTableName = config.get("aws.dynamodb.siteLaunchTableName"),
}: {
dynamoDBClient: DynamoDBClient
dynamoDbTableName?: string
}) {
this.dynamoDBClient = dynamoDBClient
this.TABLE_NAME = dynamoDbTableName
autoBind(this)
}

async createItem(message: SiteLaunchMessage): Promise<void> {
await this.dynamoDBClient.createItem(this.TABLE_NAME, MOCK_LAUNCH)
await this.dynamoDBClient.createItem(this.TABLE_NAME, message)
}

async getItem(message: SiteLaunchMessage): Promise<GetCommandOutput> {
return this.dynamoDBClient.getItem(this.TABLE_NAME, MOCK_LAUNCH.appId)
}
async getAllCompletedLaunches(): Promise<SiteLaunchMessage[]> {
const entries = ((
await this.dynamoDBClient.getAllItems(this.TABLE_NAME)
).Items?.filter(isSiteLaunchMessage) as unknown) as SiteLaunchMessage[]

const completedEntries =
entries?.filter(
(entry) =>
entry.status?.state === "success" || entry.status?.state === "failure"
) || []

async updateItem(message: SiteLaunchMessage): Promise<UpdateCommandOutput> {
// TODO: delete mocking after integration in IS-186
MOCK_LAUNCH.status = { state: "success", message: "SUCCESS_SITE_LIVE" }
const updateParams: UpdateParams = {
TableName: this.TABLE_NAME,
Key: { appId: MOCK_LAUNCH.appId },
// The update expression to apply to the item,
// in this case setting the "status" attribute to a value
UpdateExpression:
"set #status.#state = :state, #status.#message = :message",
ExpressionAttributeNames: {
"#status": "status",
"#state": "state",
"#message": "message",
},
// A map of expression attribute values used in the update expression,
// in this case mapping ":state" to the value of the Launch status state and ":message" to the value of the Launch status message
ExpressionAttributeValues: {
":state": MOCK_LAUNCH.status.state,
":message": MOCK_LAUNCH.status.message,
},
}
return this.dynamoDBClient.updateItem(updateParams)
// Delete after retrieving the items
Promise.all(completedEntries.map((entry) => this.deleteItem(entry)))
return completedEntries
}

async deleteItem(message: SiteLaunchMessage): Promise<DeleteCommandOutput> {
return this.dynamoDBClient.deleteItem(this.TABLE_NAME, {
appId: MOCK_LAUNCH.appId,
appId: message.appId,
})
}
}
Loading

0 comments on commit b768fea

Please sign in to comment.