Skip to content

Commit

Permalink
Merge pull request #16 from reapit/construct/edge-api-swagger
Browse files Browse the repository at this point in the history
construct: add edge-api-swagger
  • Loading branch information
joshbalfour committed Oct 24, 2023
2 parents e2eedb1 + ab57d2a commit 060aa6b
Show file tree
Hide file tree
Showing 24 changed files with 956 additions and 45 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@reapit-cdk/generate-readme": "workspace:^",
"@reapit-cdk/integration-tests": "workspace:^",
"@types/jest": "^29.5.5",
"@types/swagger-ui-dist": "^3.30.3",
"aws-cdk": "2.100.0",
"aws-sdk-client-mock": "^3.0.0",
"aws-sdk-client-mock-jest": "^3.0.0",
Expand Down
59 changes: 59 additions & 0 deletions packages/constructs/cross-region-stack-export/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# @reapit-cdk/cross-region-stack-export


![npm version](https://img.shields.io/npm/v/@reapit-cdk/cross-region-stack-export)
![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/cross-region-stack-export)
![coverage: 74.02%25](https://img.shields.io/badge/coverage-74.02%25-orange)
![Integ Tests: X](https://img.shields.io/badge/Integ%20Tests-X-red)

Allows you to share values between stack across regions and accounts.

## Package Installation:

```sh
yarn add --dev @reapit-cdk/cross-region-stack-export
# or
npm install @reapit-cdk/cross-region-stack-export --save-dev
```

## Usage
```ts
import { CfnOutput, Stack, App } from 'aws-cdk-lib'
import { CrossRegionStackExport } from '@reapit-cdk/cross-region-stack-export'
import { Bucket } from 'aws-cdk-lib/aws-s3'

const app = new App()
const euStack = new Stack(app, 'stack-eu', {
env: {
account: '11111111',
region: 'eu-west-1',
},
})

const exporter = new CrossRegionStackExport(euStack, 'exporter')
exporter.setValue('thing', 'avalue')

const bucket = new Bucket(euStack, 'bucket')
exporter.setValue('bucketArn', bucket.bucketArn)

const usStack = new Stack(app, 'stack-us', {
env: {
account: '2222222222',
region: 'us-east-1',
},
})

const importer = exporter.getImporter(usStack, 'eu-importer')

const euThing = importer.getValue('thing')
const euBucket = Bucket.fromBucketArn(usStack, 'eu-bucket', importer.getValue('bucketArn'))

new CfnOutput(usStack, 'euThing', {
value: euThing,
})

new CfnOutput(usStack, 'euBucketName', {
value: euBucket.bucketName,
})

```
4 changes: 4 additions & 0 deletions packages/constructs/edge-api-swagger/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
src
tests
.eslintrc.js
tsconfig.json
53 changes: 53 additions & 0 deletions packages/constructs/edge-api-swagger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@reapit-cdk/edge-api-swagger",
"version": "0.0.0",
"description": "Add a swagger endpoint to your EdgeAPI",
"homepage": "https://github.com/reapit/ts-cdk-constructs/blob/main/packages/constructs/edge-api-swagger",
"readme": "https://github.com/reapit/ts-cdk-constructs/blob/main/packages/constructs/edge-api-swagger/readme.md",
"bugs": {
"url": "https://github.com/reapit/ts-cdk-constructs/issues"
},
"license": "MIT",
"author": {
"name": "Josh Balfour",
"email": "jbalfour@reapit.com"
},
"repository": {
"url": "https://github.com/reapit/ts-cdk-constructs.git"
},
"scripts": {
"build": "reapit-cdk-tsup",
"check": "yarn run root:check -p $(pwd)",
"lint": "reapit-cdk-eslint",
"test": "yarn run root:test -- $(pwd)",
"prepack": "reapit-version-package && yarn build",
"integ": "yarn run root:integ -- $(pwd)",
"jsii:build": "rpt-cdk-jsii",
"jsii:publish": "rpt-cdk-jsii --publish"
},
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
"main": "dist/index.js",
"types": "dist/index.d.ts"
},
"dependencies": {
"openapi3-ts": "^4.1.2",
"swagger-ui-dist": "^5.9.0",
"typescript": "^5.1.3"
},
"peerDependencies": {
"@reapit-cdk/edge-api": "workspace:^",
"aws-cdk-lib": "^2.96.2",
"constructs": "^10.2.70"
},
"devDependencies": {
"@reapit-cdk/eslint-config": "workspace:^",
"@reapit-cdk/integration-tests": "workspace:^",
"@reapit-cdk/jsii": "workspace:^",
"@reapit-cdk/tsup": "workspace:^",
"@reapit-cdk/version-package": "workspace:^",
"aws-cdk-lib": "^2.96.2",
"constructs": "^10.2.70"
}
}
73 changes: 73 additions & 0 deletions packages/constructs/edge-api-swagger/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# @reapit-cdk/edge-api-swagger


![npm version](https://img.shields.io/npm/v/@reapit-cdk/edge-api-swagger)
![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/edge-api-swagger)
![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red)
![Integ Tests: X](https://img.shields.io/badge/Integ%20Tests-X-red)

Add a swagger endpoint to your EdgeAPI

## Package Installation:

```sh
yarn add --dev @reapit-cdk/edge-api-swagger
# or
npm install @reapit-cdk/edge-api-swagger --save-dev
```

## Usage
```ts
import { Stack, App } from 'aws-cdk-lib'
import { EdgeAPI, EdgeAPILambda } from '@reapit-cdk/edge-api'
import { Code, Runtime } from 'aws-cdk-lib/aws-lambda'
import { EdgeAPISwaggerEndpoint } from '@reapit-cdk/edge-api-swagger'
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager'
import * as path from 'path'

const app = new App()
const stack = new Stack(app, 'stack-name')

const certificate = new Certificate(stack, 'certificate', {
domainName: 'example.org',
})
const api = new EdgeAPI(stack, 'api', {
certificate,
domains: ['example.org', 'example.com'],
devMode: false,
defaultEndpoint: {
destination: 'example.com',
},
})

const lambda = new EdgeAPILambda(stack, 'lambda', {
code: Code.fromAsset(path.resolve('../lambda/dist')),
codePath: path.resolve('../lambda/src/index.ts'), // gets added to the docs
handler: 'index.handler',
runtime: Runtime.NODEJS_18_X,
environment: {
aVariable: 'contents',
},
})

api.addEndpoint({
pathPattern: '/api/lambda',
lambda,
})

api.addEndpoint(
new EdgeAPISwaggerEndpoint(stack, 'docs', {
api,
url: 'https://example.org',

pathPattern: '/swagger', // optional, defaults to /swagger

// optional
info: {
title: '', // defaults to Edge API
version: '', // defaults to 1.0.0
},
}),
)

```
114 changes: 114 additions & 0 deletions packages/constructs/edge-api-swagger/src/edge-api-swagger-endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { EdgeAPI, FrontendEndpoint, endpointIsLambdaEndpoint, endpointIsProxyEndpoint } from '@reapit-cdk/edge-api'
import { Construct } from 'constructs'

import { Bucket } from 'aws-cdk-lib/aws-s3'
import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment'
import { RemovalPolicy } from 'aws-cdk-lib'
import { generateOpenAPIDocs } from './generation/generate-openapi-docs'
import { EndpointInputItem } from './generation'
import { getAbsoluteFSPath } from 'swagger-ui-dist'
import { InfoObject } from 'openapi3-ts/oas30'

interface EdgeAPISwaggerEndpointProps {
api: EdgeAPI
url: string
pathPattern?: string
info?: InfoObject
}

const swaggerHtml = (urlPrefix: string) => `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="SwaggerUI"
/>
<title>SwaggerUI</title>
<link rel="stylesheet" href="${urlPrefix}/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="${urlPrefix}/swagger-ui-bundle.js" crossorigin></script>
<script src="${urlPrefix}/swagger-ui-standalone-preset.js" crossorigin></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: '${urlPrefix}/openapi.json',
dom_id: '#swagger-ui',
});
};
</script>
</body>
</html>`

export class EdgeAPISwaggerEndpoint extends Construct implements FrontendEndpoint {
bucket: Bucket
invalidationItems: string[]
pathPattern: string

constructor(scope: Construct, id: string, props: EdgeAPISwaggerEndpointProps) {
super(scope, id)
this.pathPattern = props.pathPattern ?? '/swagger'

const destinationKeyPrefix = this.pathPattern.replace(/^\/+/g, '')

this.bucket = new Bucket(this, 'bucket', {
websiteIndexDocument: 'index.html',
websiteErrorDocument: destinationKeyPrefix + '/index.html',
removalPolicy: RemovalPolicy.RETAIN, // otherwise deletion will fail on stack destroy due to non-empty bucket
publicReadAccess: true,
blockPublicAccess: {
blockPublicAcls: false,
blockPublicPolicy: false,
ignorePublicAcls: false,
restrictPublicBuckets: false,
},
})

const openapiJson = generateOpenAPIDocs({
url: props.url,
info: props.info,
endpointsInput: props.api._endpoints.map((endpoint): EndpointInputItem => {
if (endpointIsLambdaEndpoint(endpoint)) {
return {
codePath: endpoint.lambda.codePath,
pathPattern: endpoint.pathPattern,
isFrontend: false,
}
}
if (endpointIsProxyEndpoint(endpoint)) {
return {
isProxy: true,
pathPattern: endpoint.pathPattern,
proxyDestination: endpoint.destination,
}
}
return {
pathPattern: endpoint.pathPattern,
isFrontend: true,
}
}),
})

new BucketDeployment(this, 'deployment', {
sources: [
Source.data('index.html', swaggerHtml(`${props.url}/${destinationKeyPrefix}`)),
Source.jsonData('openapi.json', openapiJson),
Source.asset(getAbsoluteFSPath()),
],
destinationBucket: this.bucket,
destinationKeyPrefix,
retainOnDelete: false,
})

this.invalidationItems = [
'/index.html',
'/openapi.json',
'/swagger-ui-bundle.js',
'/swagger-ui-standalone-preset.js',
'/swagger-ui.css',
]
}
}
Loading

0 comments on commit 060aa6b

Please sign in to comment.