diff --git a/.gitignore b/.gitignore index 1552d2c2..fb763b02 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ cdk.context.json # CDK asset staging directory .cdk.staging cdk.out/ +cdk.context.json diff --git a/services/core/helpers/certificates.ts b/services/core/helpers/certificates.ts index 1b8857d0..1d4237ac 100644 --- a/services/core/helpers/certificates.ts +++ b/services/core/helpers/certificates.ts @@ -1,11 +1,12 @@ import { StringParameter } from "aws-cdk-lib/aws-ssm"; import { Construct } from "constructs"; +import { getStage } from "./env-helpers"; export const getCertificateArn = ( scope: Construct, - stage: string, subDomain: string ): string | undefined => { + const stage = getStage(); return ( StringParameter.fromStringParameterAttributes( scope, diff --git a/services/core/resources/constructs/api-gateway.ts b/services/core/resources/constructs/api-gateway.ts index 0b3d2798..ac5ce0f5 100644 --- a/services/core/resources/constructs/api-gateway.ts +++ b/services/core/resources/constructs/api-gateway.ts @@ -1,20 +1,21 @@ import { - BasePathMapping, - DomainName, EndpointType, RestApi, RestApiProps, } from "aws-cdk-lib/aws-apigateway"; import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; +import { ARecord, HostedZone, RecordTarget } from "aws-cdk-lib/aws-route53"; +import { ApiGateway } from "aws-cdk-lib/aws-route53-targets"; import { Construct } from "constructs"; import { buildResourceName } from "../../helpers"; export interface CoreApiProps extends Omit { - domainNameString: string; + rootDomain?: string; + subDomain?: string; certificateArn?: string; } -export class CoreApi extends RestApi { +export class OrionApi extends RestApi { constructor(scope: Construct, id: string, props: CoreApiProps) { super(scope, id, { ...props, @@ -25,37 +26,38 @@ export class CoreApi extends RestApi { }, defaultCorsPreflightOptions: { allowHeaders: [ - 'Content-Type', - 'X-Amz-Date', - 'Authorization', - 'X-Api-Key', + "Content-Type", + "X-Amz-Date", + "Authorization", + "X-Api-Key", ], - allowMethods: ['OPTIONS', 'GET', 'POST'], + allowMethods: ["OPTIONS", "GET", "POST"], allowCredentials: true, - allowOrigins: ['http://localhost:3000'], + allowOrigins: ["http://localhost:3000", "https://*.oriontools.ai"], }, }); - const { certificateArn, domainNameString } = props; + const { rootDomain, subDomain, certificateArn } = props; - if (certificateArn) { + if (certificateArn && rootDomain && subDomain) { const certificate = Certificate.fromCertificateArn( this, "Certificate", certificateArn ); - // Create custom domain - const customDomain = new DomainName(this, "CustomDomain", { - domainName: domainNameString, + this.addDomainName("DomainName", { + domainName: `${subDomain}.${rootDomain}`, certificate: certificate, - endpointType: EndpointType.EDGE, // or REGIONAL + endpointType: EndpointType.EDGE, }); - // Create a base path mapping that links the custom domain to the API - new BasePathMapping(this, "BasePathMapping", { - domainName: customDomain, - restApi: this, + new ARecord(this, "ApiSubDomainDNS", { + zone: HostedZone.fromLookup(this, "baseZone", { + domainName: rootDomain, + }), + recordName: subDomain, + target: RecordTarget.fromAlias(new ApiGateway(this)), }); } } diff --git a/services/core/resources/index.ts b/services/core/resources/index.ts index acd86a74..9b4d93c8 100644 --- a/services/core/resources/index.ts +++ b/services/core/resources/index.ts @@ -4,18 +4,31 @@ import { RemovalPolicyAspect } from "../aspects/RemovalPolicyAspect"; import { buildResourceName, getRegion, getStage } from "../helpers"; import { CoreStack } from "./stacks/core-stack"; +import { DemoStack } from "./stacks/demo-stack"; const app = new App(); + +//Env const stage = getStage(); const region = getRegion(); +//Stacks const coreStack = new CoreStack(app, "crgpt-core", { stackName: buildResourceName("crgpt-core"), stage: stage, - env: { region }, + env: { region, account: process.env.CDK_DEFAULT_ACCOUNT }, +}); + +const demoStack = new DemoStack(app, "crgpt-demo", { + stackName: buildResourceName("crgpt-demo"), + stage: stage, + env: { region, account: process.env.CDK_DEFAULT_ACCOUNT }, + userTable: coreStack.userTable, }); +//Aspects Aspects.of(coreStack).add(new RemovalPolicyAspect()); +Aspects.of(demoStack).add(new RemovalPolicyAspect()); -//enable OTel traces +//OTel traces Tags.of(app).add("baselime:tracing", `true`); diff --git a/services/core/resources/stacks/core-stack.ts b/services/core/resources/stacks/core-stack.ts index 5f261b84..04e58ae1 100644 --- a/services/core/resources/stacks/core-stack.ts +++ b/services/core/resources/stacks/core-stack.ts @@ -1,14 +1,13 @@ import { Stack, StackProps } from "aws-cdk-lib"; import { LambdaIntegration } from "aws-cdk-lib/aws-apigateway"; +import { Table } from "aws-cdk-lib/aws-dynamodb"; import { Key } from "aws-cdk-lib/aws-kms"; import { Construct } from "constructs"; -import { DemoReviewLambda } from "../../functions/demo-review-lambda/config"; import { GetUserLambda } from "../../functions/get-user/config"; import { ReviewLambda } from "../../functions/review-lambda/config"; import { UpdateUserLambda } from "../../functions/update-user/config"; import { getCertificateArn, getDomainName, getStage } from "../../helpers"; -import { CoreApi } from "../constructs/api-gateway"; -import { ReviewBucket } from "../constructs/review-bucket"; +import { OrionApi } from "../constructs/api-gateway"; import { UserTable } from "../constructs/user-table"; interface CoreStackProps extends StackProps { @@ -16,59 +15,43 @@ interface CoreStackProps extends StackProps { } export class CoreStack extends Stack { + userTable: Table; constructor(scope: Construct, id: string, props: CoreStackProps) { super(scope, id, props); - const api = new CoreApi(this, "core-api", { - domainNameString: `api.${getDomainName(props.stage)}`, - certificateArn: getCertificateArn(this, props.stage, "api"), + //API + const api = new OrionApi(this, "core-api", { + rootDomain: getDomainName(props.stage), + subDomain: "api", + certificateArn: getCertificateArn(this, "api"), }); - const userTable = new UserTable(this, "user-database"); - - const reviewLambda = new ReviewLambda(this, "review-lambda"); - - const postReviewRoute = api.root.addResource("postReview"); - postReviewRoute.addMethod("POST", new LambdaIntegration(reviewLambda)); - - // We use a separate api for the demo review to enable strong throttling on it - const demoApi = new CoreApi(this, "demo-api", { - deployOptions: { - throttlingBurstLimit: 1, - throttlingRateLimit: 1, - }, - domainNameString: `demo.${getDomainName(props.stage)}`, - certificateArn: getCertificateArn(this, props.stage, "demo"), - }); - - const demoReviewBucket = new ReviewBucket(this, "demo-review-bucket"); - - const demoReviewLambda = new DemoReviewLambda(this, "demo-review-lambda", { - table: userTable, - bucket: demoReviewBucket, - }); - - const demoReviewRoute = demoApi.root.addResource("demoReview"); - demoReviewRoute.addMethod("POST", new LambdaIntegration(demoReviewLambda)); - - // Resources for user management + //KMS const kmsKey = new Key(this, "encryption-key", { enableKeyRotation: true, alias: `${getStage()}/encryption-key`, }); + //DynamoDB + this.userTable = new UserTable(this, "user-database"); + + //Lambda + const reviewLambda = new ReviewLambda(this, "review-lambda"); const updateUserLambda = new UpdateUserLambda(this, "update-user-lambda", { - table: userTable, + table: this.userTable, kmsKey: kmsKey, }); + const getUserLambda = new GetUserLambda(this, "get-user-lambda", { + table: this.userTable, + }); + + //Routes + const postReviewRoute = api.root.addResource("postReview"); + postReviewRoute.addMethod("POST", new LambdaIntegration(reviewLambda)); const updateUserRoute = api.root.addResource("updateUser"); updateUserRoute.addMethod("POST", new LambdaIntegration(updateUserLambda)); - const getUserLambda = new GetUserLambda(this, "get-user-lambda", { - table: userTable, - }); - const getUserRoute = api.root.addResource("getUser"); getUserRoute.addMethod("GET", new LambdaIntegration(getUserLambda)); } diff --git a/services/core/resources/stacks/demo-stack.ts b/services/core/resources/stacks/demo-stack.ts new file mode 100644 index 00000000..07dfc7cb --- /dev/null +++ b/services/core/resources/stacks/demo-stack.ts @@ -0,0 +1,43 @@ +import { Stack, StackProps } from "aws-cdk-lib"; +import { LambdaIntegration } from "aws-cdk-lib/aws-apigateway"; +import { Table } from "aws-cdk-lib/aws-dynamodb"; +import { Construct } from "constructs"; +import { DemoReviewLambda } from "../../functions/demo-review-lambda/config"; +import { getCertificateArn, getDomainName } from "../../helpers"; +import { OrionApi } from "../constructs/api-gateway"; +import { ReviewBucket } from "../constructs/review-bucket"; + +interface DemoStackProps extends StackProps { + stage: string; + userTable: Table; +} + +export class DemoStack extends Stack { + constructor(scope: Construct, id: string, props: DemoStackProps) { + super(scope, id, props); + + // Separate API for the demo to enable strong throttling + const demoApi = new OrionApi(this, "demo-api", { + deployOptions: { + throttlingBurstLimit: 1, + throttlingRateLimit: 1, + }, + rootDomain: getDomainName(props.stage), + subDomain: "demo", + certificateArn: getCertificateArn(this, "demo"), + }); + + //Resources + const demoReviewBucket = new ReviewBucket(this, "demo-review-bucket"); + + //Lambda + const demoReviewLambda = new DemoReviewLambda(this, "demo-review-lambda", { + table: props.userTable, + bucket: demoReviewBucket, + }); + + //Routes + const demoReviewRoute = demoApi.root.addResource("demoReview"); + demoReviewRoute.addMethod("POST", new LambdaIntegration(demoReviewLambda)); + } +}