Skip to content

Commit

Permalink
Add Upsert operation
Browse files Browse the repository at this point in the history
  • Loading branch information
tmclaugh committed Oct 22, 2024
1 parent 07c8816 commit 32d5523
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 37 deletions.
111 changes: 79 additions & 32 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,37 +58,8 @@ paths:
content:
application/json:
schema:
type: object
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
properties:
namespace:
type: string
name:
type: string
title:
type: string
annotations:
type: object
required:
- name
additionalProperties: true
spec:
type: object
properties:
owner:
type: string
type:
type: string
required:
- apiVersion
- kind
- metadata
$ref: "#/components/schemas/Entity"

responses:
'201':
description: Created
Expand Down Expand Up @@ -283,7 +254,7 @@ paths:
content:
application/json:
schema:
type: object
$ref: "#/components/schemas/RequestIdResponse"
'400':
description: Client failure
content:
Expand All @@ -304,9 +275,85 @@ paths:
httpMethod: POST
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DeleteEntityFunction.Arn}/invocations"
put:
summary: Delete catalog item
description: Delete catalog item
parameters:
- $ref: "#/components/parameters/namespace"
- $ref: "#/components/parameters/kind"
- $ref: "#/components/parameters/name"
- $ref: "#/components/parameters/headerContentTypeJson"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Entity"
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/RequestIdResponse"
'400':
description: Client failure
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
'500':
description: Server failure
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
security:
- serverlessOpsCognitoPool:
- Fn::Sub: https://${Hostname}/catalog.write
x-amazon-apigateway-integration:
type: AWS_PROXY
httpMethod: POST
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UpsertEntityFunction.Arn}/invocations"

components:
schemas:
Entity:
type: object
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
properties:
namespace:
type: string
name:
type: string
title:
type: string
annotations:
type: object
required:
- name
additionalProperties: true
spec:
type: object
properties:
owner:
type: string
type:
type: string
required:
- owner
additionalProperties: true
required:
- apiVersion
- kind
- metadata
CreateEntityResponse:
type: object
properties:
Expand Down
65 changes: 61 additions & 4 deletions src/handlers/CreateEntity/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ interface Item extends Entity {
itemType: string
}

export async function putEntity(entity: Entity): Promise<void> {
export async function putEntity(entity: Entity, upsert: boolean): Promise<void> {

const namespace = entity.metadata.namespace
const kind = entity.kind.toLowerCase()
Expand All @@ -47,7 +47,10 @@ export async function putEntity(entity: Entity): Promise<void> {
const params: PutItemCommandInput = {
TableName: DDB_TABLE_NAME,
Item: marshall(item),
ConditionExpression: 'attribute_not_exists(pk) AND attribute_not_exists(sk)'
}

if (!upsert) {
params.ConditionExpression = 'attribute_not_exists(pk) AND attribute_not_exists(sk)'
}

try {
Expand All @@ -65,7 +68,7 @@ export async function putEntity(entity: Entity): Promise<void> {
}


export async function handler (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> {
export async function handler_create (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> {
LOGGER.debug('Received event', { event })
const event_id = context.awsRequestId

Expand All @@ -74,7 +77,7 @@ export async function handler (event: APIGatewayProxyEvent, context: Context): P
let statusCode: number
let body: string
try {
const output = await putEntity(entity)
await putEntity(entity, false)
statusCode = 201
body = JSON.stringify({'request_id': event_id})
} catch (error) {
Expand All @@ -99,3 +102,57 @@ export async function handler (event: APIGatewayProxyEvent, context: Context): P
body
}
}

export async function handler_upsert (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> {
LOGGER.debug('Received event', { event })
const event_id = context.awsRequestId

const entity: Entity = JSON.parse(event.body || '{}') // Already validated body at Gateway

// Upsert data must match request path
const namespace = entity.metadata.namespace
const kind = entity.kind.toLowerCase()
const name = entity.metadata.name

if (
namespace !== event.pathParameters?.namespace ||
kind !== event.pathParameters?.kind ||
name !== event.pathParameters?.name
) {
return {
statusCode: 400,
body: JSON.stringify({
name: 'BadRequest',
message: 'Entity metadata does not match request path'
})
}
}

let statusCode: number
let body: string
try {
await putEntity(entity, true)
statusCode = 201
body = JSON.stringify({'request_id': event_id})
} catch (error) {
LOGGER.error("Operation failed", { event })
const fault = (<DynamoDBServiceException>error).$fault
switch (fault) {
case 'client':
statusCode = 400
break;
default:
statusCode = 500
break;
}
body = JSON.stringify({
name: (<Error>error).name,
message: (<Error>error).message
})
}

return {
statusCode,
body
}
}
32 changes: 31 additions & 1 deletion template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Resources:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./dist/handlers/CreateEntity
Handler: function.handler
Handler: function.handler_create
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DdbTable
Expand All @@ -95,6 +95,35 @@ Resources:
Principal: apigateway.amazonaws.com


UpsertEntityFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./dist/handlers/UpsertEntity
Handler: function.handler_upsert
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DdbTable
Metadata:
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: "es2020"
Format: "esm"
MainFields: module,main # This is to help with ESM modules
Sourcemap: !Ref EnableSourceMaps
OutExtension:
- .js=.mjs
EntryPoints:
- function.js

UpsertEntityFunctionInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt UpsertEntityFunction.Arn
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com


GetEntityFunction:
Type: AWS::Serverless::Function
Properties:
Expand Down Expand Up @@ -123,6 +152,7 @@ Resources:
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com


DeleteEntityFunction:
Type: AWS::Serverless::Function
Properties:
Expand Down

0 comments on commit 32d5523

Please sign in to comment.