Untitled

mail@pastecode.io avatar
unknown
plain_text
9 days ago
37 kB
1
Indexable
Never
import { SqsToLambda } from "@aws-solutions-constructs/aws-sqs-lambda";
import * as cdk from "aws-cdk-lib";
import {
  RestApi,
  DomainName,
  Resource,
  Cors,
  LambdaIntegration,
  CognitoUserPoolsAuthorizer,
  BasePathMapping,
  AuthorizationType,
} from "aws-cdk-lib/aws-apigateway";
import { HttpMethod } from "aws-cdk-lib/aws-apigatewayv2";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as cloudtrail from "aws-cdk-lib/aws-cloudtrail";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as events from "aws-cdk-lib/aws-events";
import * as targets from "aws-cdk-lib/aws-events-targets";
import * as iam from "aws-cdk-lib/aws-iam";
import * as kms from 'aws-cdk-lib/aws-kms';
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as logs from "aws-cdk-lib/aws-logs";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as s3 from "aws-cdk-lib/aws-s3";
import { HttpMethods } from "aws-cdk-lib/aws-s3";
import * as sqs from "aws-cdk-lib/aws-sqs";
import * as waf from "aws-cdk-lib/aws-wafv2";
import {
  AwsCustomResource,
  AwsCustomResourcePolicy,
  PhysicalResourceId,
} from "aws-cdk-lib/custom-resources";
import { Construct } from "constructs/lib/construct";
import { camelCase, capitalize } from "lodash";

import { LAMBDA_FUNCTION_TIMEOUT_DURATION } from "@lambdas/shared/monitoring/lambda";
import { DRAFTS_FOLDER } from "@lambdas/shared/services/dto/S3";

import { EnvironmentConfig } from "./config";
import { KepcoLambda } from "./construct";
import { AuthStack } from "./nested-stacks/auth-stack";
import { DatabaseStack } from "./nested-stacks/database-stack";

export interface KepcoStackProps extends cdk.StackProps {
  stage: string;
  dbConfig: EnvironmentConfig["dbConfig"];
  domainConfig: EnvironmentConfig["domainConfig"];
  config: Pick<EnvironmentConfig, "application">;
  mailConfig: EnvironmentConfig["mailConfig"];
  apiGateway: EnvironmentConfig["apiGateway"];
}

export interface LambdaVpcConfig {
  vpc: cdk.aws_ec2.IVpc;
  vpcSubnets: cdk.aws_ec2.SubnetSelection;
  securityGroups: ec2.SecurityGroup[];
}

export class KepcoStack extends cdk.Stack {
  region: string;
  account: string;
  stage: string;
  lambdaVpcConfig: LambdaVpcConfig;
  config: Pick<EnvironmentConfig, "application">;
  domainConstruct: DomainName;
  adminDomainConstruct: DomainName;
  apiDomain: string;
  dbConfig: {
    databaseName: string;
    loginCredentialArn: string;
    host: string;
  };
  bucket: s3.Bucket;
  authorizer: CognitoUserPoolsAuthorizer;

  restApiResource: Resource;
  authStack: AuthStack;
  // s3ServerLoggingBucket: s3.Bucket;
  privateBucket: s3.Bucket;
  publicBucket: s3.Bucket;
  mailConfig: {
    apiKeyArn: string;
    apiKeyEncryptionKeyArn: string;
    domain: string;
    username: string;
  };
  logGroup: logs.LogGroup;

  constructor(scope: Construct, id: string, props: KepcoStackProps) {
    super(scope, id, props);
    this.region = cdk.Stack.of(this).region;
    this.account = cdk.Stack.of(this).account;
    // this.s3ServerLoggingBucket = this.createS3ServerLoggingBucket();
    this.privateBucket = this.createPrivateMoactBucket();
    this.publicBucket = this.createPublicMoactBucket();
    this.stage = props.stage;
    this.config = props.config;
    this.mailConfig = props.mailConfig;

    const vpc = this.createVpc(`MoactVpc${capitalize(camelCase(this.stage))}`);

    const lambdaSecurityGroup = new ec2.SecurityGroup(
      this,
      "LambdaSecurityGroup",
      {
        vpc,
        description: "Security group for lambda",
        allowAllOutbound: true,
      }
    );

    this.lambdaVpcConfig = {
      vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
      },
      securityGroups: [lambdaSecurityGroup],
    };

    const acmCert = acm.Certificate.fromCertificateArn(
      this,
      "moact-api-cert",
      props.domainConfig.acmArn
    );

    const hostedZone = route53.HostedZone.fromLookup(
      this,
      `MoactApiHostedZoneId${capitalize(camelCase(this.stage))}`,
      {
        domainName: props.domainConfig.apiDomain.slice(
          props.domainConfig.apiDomain.indexOf(".") + 1
        ),
      }
    );

    const database = new DatabaseStack(this, "DatabaseStack", {
      vpcConfig: this.lambdaVpcConfig,
      stage: this.stage,
      databaseConfig: props.dbConfig,
      acmCert: acmCert,
      domainConfig: props.domainConfig,
      hostedZone,
    });

    new cdk.CfnOutput(this, "dbProxyUrl", {
      value: database.dbProxy.endpoint,
    });

    this.dbConfig = {
      ...props.dbConfig,
      host: database.dbProxy.endpoint,
      loginCredentialArn: database.cluster.secret!.secretArn,
    };

    this.domainConstruct = new DomainName(
      this,
      `MoactApiDomain${capitalize(camelCase(this.stage))}`,
      {
        domainName: props.domainConfig.apiDomain,
        certificate: acmCert,
      }
    );

    const apiGatewayRecordTarget = route53.RecordTarget.fromAlias(
      new route53Targets.ApiGatewayDomain(this.domainConstruct)
    );

    new route53.ARecord(
      this,
      `MoactApiAliasRecord${capitalize(camelCase(this.stage))}`,
      {
        target: apiGatewayRecordTarget,
        zone: hostedZone,
        recordName: props.domainConfig.apiDomain.slice(
          0,
          props.domainConfig.apiDomain.indexOf(".")
        ),
      }
    );

    const restApi = new RestApi(
      this,
      `MoactRestAPI${capitalize(camelCase(this.stage))}`,
      {
        description: "Rest API for moact",
        defaultCorsPreflightOptions: {
          allowHeaders: ["Content-Type", "Authorization"],
          allowMethods: Cors.ALL_METHODS,
          allowOrigins: [props.apiGateway.cors.origin],
          allowCredentials: true,
          exposeHeaders: [
            "Referrer-Policy",
            "Cache-control",
            "Content-Security-Policy",
            "X-Content-Type-Options",
            "Content-Disposition"
          ],
        },
        deploy: true,
        deployOptions: {
          stageName: this.stage,
          tracingEnabled: true,
        },
      }
    );

    this.configApiGatewayWaf(restApi);

    //config prefix for all of endpoint /api/v1
    this.restApiResource = restApi.root.addResource("api").addResource("v1");

    //create path mapping betweene domain name and api
    new BasePathMapping(
      this,
      `BaseMapping${capitalize(camelCase(this.stage))}`,
      {
        domainName: this.domainConstruct,
        restApi: restApi,
      }
    );

    new cdk.CfnOutput(this, "DatabaseSecretArn", {
      value: this.dbConfig.loginCredentialArn,
    });

    this.authStack = new AuthStack(this, "AuthStack", {
      stage: this.stage,
      vpcConfig: this.lambdaVpcConfig,
      mailConfig: this.mailConfig,
    });

    // Instantiate authorizer from cognito user pool
    this.authorizer = new CognitoUserPoolsAuthorizer(
      this,
      `MoactAuthorizer${capitalize(camelCase(this.stage))}`,
      {
        cognitoUserPools: [this.authStack.userPool],
      }
    );

    new cdk.CfnOutput(this, "httpApiUrl", {
      value: this.domainConstruct.domainName,
    });

    new cdk.CfnOutput(this, "userPoolClientId", {
      value: this.authStack.userPoolClient.userPoolClientId,
    });

    new cdk.CfnOutput(this, "vpcId", {
      value: this.lambdaVpcConfig.vpc.vpcId,
    });

    const encryptionKey = new kms.Key(this, 'queueEncryptionKey', {
      enableKeyRotation: true,
    });

    const saveCollectionDLQ = new sqs.Queue(
      this,
      "saveCollectionDeadLetterQueue",
      {
        receiveMessageWaitTime: cdk.Duration.seconds(20),
        fifo: true,
        contentBasedDeduplication: true,
        encryptionMasterKey: encryptionKey,
      }
    );

    const saveCollectionQueue = new sqs.Queue(this, "saveCollectionQueue", {
      retentionPeriod: cdk.Duration.days(14),
      enforceSSL: true,
      receiveMessageWaitTime: cdk.Duration.seconds(20),
      visibilityTimeout: cdk.Duration.seconds(
        6 * LAMBDA_FUNCTION_TIMEOUT_DURATION.saveCollectionHandler
      ),
      fifo: true,
      contentBasedDeduplication: true,
      deadLetterQueue: {
        queue: saveCollectionDLQ,
        maxReceiveCount: 1,
      },
      encryptionMasterKey: encryptionKey,
    });

    const saveNormTransactionDLQ = new sqs.Queue(
      this,
      "saveNormTransactionDeadLetterQueue",
      {
        receiveMessageWaitTime: cdk.Duration.seconds(20),
        fifo: true,
        contentBasedDeduplication: true,
        encryptionMasterKey: encryptionKey,
      }
    );

    const saveNormTransactionQueue = new sqs.Queue(
      this,
      "saveNormTransactionQueue",
      {
        retentionPeriod: cdk.Duration.days(14),
        enforceSSL: true,
        receiveMessageWaitTime: cdk.Duration.seconds(20),
        visibilityTimeout: cdk.Duration.seconds(
          6 * LAMBDA_FUNCTION_TIMEOUT_DURATION.saveNormTransactionHandler
        ),
        fifo: true,
        contentBasedDeduplication: true,
        deadLetterQueue: {
          queue: saveNormTransactionDLQ,
          maxReceiveCount: 1,
        },
        encryptionMasterKey: encryptionKey,
      }
    );

    const sharpLayer = new lambda.LayerVersion(this, "sharpLayer", {
      layerVersionName: "sharpLayer",
      code: lambda.Code.fromAsset("./assets/sharp.zip"),
      compatibleRuntimes: [lambda.Runtime.NODEJS_18_X],
      compatibleArchitectures: [lambda.Architecture.X86_64],
    });

    this.logGroup = new logs.LogGroup(
      this,
      `MoactLogGroup${capitalize(camelCase(this.stage))}`,
      {
        removalPolicy: cdk.RemovalPolicy.DESTROY,
        retention: logs.RetentionDays.INFINITE,
      }
    );

    this.createTrailForObjectLevelEventLogs([
      {
        bucket: this.privateBucket,
      },
      {
        bucket: this.publicBucket,
      }
    ])
    this.getUserProfileHandler();
    this.updateUserHandler(sharpLayer);
    this.getUserBalanceHandler();
    this.listActiveMissionHandler();
    this.getUserPortfolioHandler();
    this.getMissionHandler();
    this.createDeleteUserHandler();
    this.getListUserMissionHandler();
    this.createUserMissionHandler(sharpLayer);
    this.getListContentHandler();
    this.getContentDetailHandler();
    this.normDistributionBatchHandler();
    this.getListCollectionHandler();
    this.getCollectionHandler();
    this.getListNormTransactionHandler();
    this.createSaveCollectionHandler(saveCollectionQueue);
    this.createSaveCollectionConsumer(saveCollectionQueue);
    this.createSaveNormTransactionHandler(saveNormTransactionQueue);
    this.createSaveNormTransactionConsumer(saveNormTransactionQueue);
    this.getPresignedUrlHandler();
    this.createS3FoldersHandler();
    this.getListSystemSettingHandler();
    this.airdropBatchHandler();
  }

  // private createS3ServerLoggingBucket() {
  //   return new s3.Bucket(
  //     this,
  //     `MoactS3ServerLoggingBucket${capitalize(camelCase(this.stage))}`,
  //     {
  //       removalPolicy: cdk.RemovalPolicy.RETAIN,
  //       enforceSSL: true,
  //       versioned: true,
  //       blockPublicAccess: new cdk.aws_s3.BlockPublicAccess({
  //         blockPublicAcls: true,
  //         ignorePublicAcls: true,
  //         blockPublicPolicy: true,
  //         restrictPublicBuckets: true,
  //       }),
  //     }
  //   );
  // }

  private createPrivateMoactBucket() {
    return new s3.Bucket(
      this,
      `MoactPrivateBucket${capitalize(camelCase(this.stage))}`,
      {
        removalPolicy: cdk.RemovalPolicy.RETAIN,
        enforceSSL: true,
        versioned: true,
        blockPublicAccess: new cdk.aws_s3.BlockPublicAccess({
          blockPublicAcls: true,
          ignorePublicAcls: true,
          blockPublicPolicy: true,
          restrictPublicBuckets: true,
        }),
        lifecycleRules: [
          {
            id: "autoDeleteNonResizeImageLifecycle",
            enabled: true,
            expiration: cdk.Duration.days(1),
            noncurrentVersionExpiration: cdk.Duration.days(1),
            prefix: DRAFTS_FOLDER,
          },
        ],
        cors: [
          {
            allowedHeaders: ["*"],
            allowedMethods: [
              HttpMethods.GET,
              HttpMethods.HEAD,
              HttpMethods.PUT,
              HttpMethods.POST,
              HttpMethods.DELETE,
            ],
            allowedOrigins: ["*"],
            exposedHeaders: [""],
            maxAge: 3000,
          },
        ],
      }
    );
  }

  private createPublicMoactBucket() {
    const bucket = new s3.Bucket(
      this,
      `MoactPublicBucket${capitalize(camelCase(this.stage))}`,
      {
        removalPolicy: cdk.RemovalPolicy.RETAIN,
        enforceSSL: false,
        publicReadAccess: true,
        websiteIndexDocument: "index.html",
        versioned: true,
        blockPublicAccess: new cdk.aws_s3.BlockPublicAccess({
          blockPublicAcls: false,
          ignorePublicAcls: false,
          blockPublicPolicy: false,
          restrictPublicBuckets: false,
        }),
      }
    );

    bucket.addToResourcePolicy(new iam.PolicyStatement({
      effect: iam.Effect.DENY,
      principals: [new iam.ArnPrincipal("*")],
      actions: ["*"],
      resources: [bucket.arnForObjects("*")],
      conditions: {
        'Bool': {
          'aws:SecureTransport': 'false',
        },
      },
    }));

    return bucket;
  }

  private createS3FoldersHandler() {
    const handler = new KepcoLambda(this, "CreateS3FoldersHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/init-s3-empty-folder"),
        environment: {
          REGION: this.region,
          S3_BUCKET_NAME: this.publicBucket.bucketName,
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.publicBucket.grantPut(handler);

    const lambdaTrigger = new AwsCustomResource(
      this,
      "CreateS3PublicFolderTrigger",
      {
        policy: AwsCustomResourcePolicy.fromStatements([
          new iam.PolicyStatement({
            actions: ["lambda:InvokeFunction"],
            effect: iam.Effect.ALLOW,
            resources: [handler.functionArn],
          }),
        ]),
        timeout: cdk.Duration.minutes(1),
        installLatestAwsSdk: true,
        onCreate: {
          service: "Lambda",
          action: "invoke",
          parameters: {
            FunctionName: handler.functionName,
            InvocationType: "Event",
          },
          physicalResourceId: PhysicalResourceId.of(
            "JobSenderTriggerPhysicalId"
          ),
        },
      }
    );

    lambdaTrigger.node.addDependency(handler, this.publicBucket);
  }

  private createTrailForObjectLevelEventLogs(s3Selector: cloudtrail.S3EventSelector[]) {
    const trail = new cloudtrail.Trail(this, "MoactObjectLevelEventTrail");

    trail.addS3EventSelector(s3Selector, {
      readWriteType: cloudtrail.ReadWriteType.WRITE_ONLY,
      includeManagementEvents: false,
    })

    return trail;
  }

  private getUserProfileHandler() {
    const handler = new KepcoLambda(this, "GetUserProfileHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/user-profile"),
        environment: {
          REGION: this.region,
          S3_BUCKET_NAME: this.privateBucket.bucketName,
          S3_PRIVATE_URL_EXPIRES:
            this.config.application.privateUrlExpires.toString(),
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.privateBucket.grantRead(handler);

    this.restApiResource
      .resourceForPath("users")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private updateUserHandler(layer: cdk.aws_lambda.LayerVersion) {
    const handler = new KepcoLambda(this, "UpdateUserHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        layers: [layer],
        memorySize: 1024,
        code: lambda.Code.fromAsset("./dist/lambdas/update-user"),
        timeout: cdk.Duration.seconds(
          LAMBDA_FUNCTION_TIMEOUT_DURATION.updateUserHandler
        ),
        environment: {
          S3_BUCKET_NAME: this.privateBucket.bucketName,
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.privateBucket.grantDelete(handler);
    this.privateBucket.grantPut(handler);
    this.privateBucket.grantRead(handler);
    this.restApiResource
      .resourceForPath("users")
      .addMethod(HttpMethod.POST, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getUserBalanceHandler() {
    const handler = new KepcoLambda(this, "GetUserBalanceHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/get-user-balance"),
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("users/balance")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private listActiveMissionHandler() {
    const handler = new KepcoLambda(this, "ListActiveMissionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        memorySize: 1024,
        code: lambda.Code.fromAsset("./dist/lambdas/list-active-mission"),
        logGroup: this.logGroup,
        environment: {
          S3_PUBLIC_DOMAIN_NAME: this.publicBucket.bucketDomainName,
        },
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("missions")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getMissionHandler() {
    const handler = new KepcoLambda(this, "GetMissionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/get-mission"),
        environment: {
          S3_PUBLIC_DOMAIN_NAME: this.publicBucket.bucketDomainName,
          S3_BUCKET_NAME: this.privateBucket.bucketName,
          S3_PRIVATE_URL_EXPIRES:
            this.config.application.privateUrlExpires.toString(),
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.privateBucket.grantRead(handler);

    this.restApiResource
      .resourceForPath("missions/{missionId}")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getUserPortfolioHandler() {
    const handler = new KepcoLambda(this, "GetUserPortfolioHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/get-portfolio"),
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("users/portfolio")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getListUserMissionHandler() {
    const handler = new KepcoLambda(this, "GetListUserMissionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/list-user-mission"),
        logGroup: this.logGroup,
        environment: {
          S3_PUBLIC_DOMAIN_NAME: this.publicBucket.bucketDomainName,
          S3_BUCKET_NAME: this.privateBucket.bucketName,
          S3_PRIVATE_URL_EXPIRES:
            this.config.application.privateUrlExpires.toString(),
        },
      },
    }).getLambda();

    this.privateBucket.grantRead(handler);

    this.restApiResource
      .resourceForPath("user-missions")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getListNormTransactionHandler() {
    const handler = new KepcoLambda(this, "GetListNormTransactionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/list-norm-transaction"),
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("norm-transactions")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getListContentHandler() {
    const handler = new KepcoLambda(this, "GetListContentHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/list-content"),
        logGroup: this.logGroup,
        environment: {
          S3_PUBLIC_DOMAIN_NAME: this.publicBucket.bucketDomainName,
        },
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("contents")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private createSaveCollectionHandler(queue: sqs.Queue) {
    const handler = new KepcoLambda(this, "SaveCollectionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/save-collection"),
        environment: {
          QUEUE_URL: queue.queueUrl,
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    queue.grantSendMessages(handler);

    this.restApiResource
      .resourceForPath("collections")
      .addMethod(HttpMethod.POST, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getListCollectionHandler() {
    const handler = new KepcoLambda(this, "GetListCollectionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/list-collection"),
        logGroup: this.logGroup,
        environment: {
          S3_PUBLIC_DOMAIN_NAME: this.publicBucket.bucketDomainName,
        },
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("collections")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getCollectionHandler() {
    const handler = new KepcoLambda(this, "GetCollectionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/get-collection"),
        logGroup: this.logGroup,
        environment: {
          S3_PUBLIC_DOMAIN_NAME: this.publicBucket.bucketDomainName,
        },
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("collections/{collectionId}")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private normDistributionBatchHandler() {
    const handler = new KepcoLambda(this, "NormDistributionBatchHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        timeout: cdk.Duration.seconds(
          LAMBDA_FUNCTION_TIMEOUT_DURATION.normDistributionBatchHandler
        ),
        code: lambda.Code.fromAsset("./dist/lambdas/norm-distribution-batch"),
        logGroup: this.logGroup,
      },
    }).getLambda();

    new events.Rule(this, "NormDistributionBatchExecuteRule", {
      targets: [new targets.LambdaFunction(handler)],
      schedule: events.Schedule.expression(
        this.config.application.normDistributionBatchExecuteTimeRule
      ),
    });
  }

  private createUserMissionHandler(layer: cdk.aws_lambda.LayerVersion) {
    const handler = new KepcoLambda(this, "CreateUserMissionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        memorySize: 1024,
        code: lambda.Code.fromAsset("./dist/lambdas/create-user-mission"),
        timeout: cdk.Duration.seconds(
          LAMBDA_FUNCTION_TIMEOUT_DURATION.createUserMissionHandler
        ),
        layers: [layer],
        environment: {
          S3_BUCKET_NAME: this.privateBucket.bucketName,
          S3_PRIVATE_URL_EXPIRES:
            this.config.application.privateUrlExpires.toString(),
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.privateBucket.grantRead(handler);
    this.privateBucket.grantPut(handler);
    this.privateBucket.grantDelete(handler);

    this.restApiResource
      .resourceForPath("user-missions")
      .addMethod(HttpMethod.POST, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private createVpc(name: string) {
    return new ec2.Vpc(this, name, {
      ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
      natGateways: 1,
      subnetConfiguration: [
        {
          cidrMask: 22,
          name: "ingress",
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 22,
          name: "application",
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        },
        {
          cidrMask: 22,
          name: "rds",
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
      ],
    });
  }

  private createDeleteUserHandler() {
    const handler = new KepcoLambda(this, "DeleteUserHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/delete-user"),
        environment: {
          USER_POOL_ID: this.authStack.userPool.userPoolId,
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    handler.addToRolePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ["cognito-idp:AdminDeleteUser"],
        resources: [
          `arn:aws:cognito-idp:${this.region}:${this.account}:userpool/*`,
        ],
      })
    );

    this.restApiResource
      .resourceForPath("users")
      .addMethod(HttpMethod.DELETE, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getContentDetailHandler() {
    const handler = new KepcoLambda(this, "GetContentDetailHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/get-content"),
        logGroup: this.logGroup,
        environment: {
          S3_PUBLIC_DOMAIN_NAME: this.publicBucket.bucketDomainName,
        },
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("contents/{contentId}")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private createSaveCollectionConsumer(queue: sqs.Queue) {
    const handler = new KepcoLambda(this, "saveCollectionConsumer", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        code: lambda.Code.fromAsset("./dist/lambdas/save-collection-consumer"),
        timeout: cdk.Duration.seconds(
          LAMBDA_FUNCTION_TIMEOUT_DURATION.saveCollectionHandler
        ),
        environment: {
          SALE_WALLET_ADDRESS: this.config.application.saleWalletAddress,
        },
        ...this.lambdaVpcConfig,
        logGroup: this.logGroup,
      },
    }).getLambda();

    new SqsToLambda(this, "SqsToSaveCollectionConsumer", {
      existingLambdaObj: handler,
      existingQueueObj: queue,
      sqsEventSourceProps: {
        maxConcurrency: 50,
        reportBatchItemFailures: true,
        batchSize: 1,
      },
    });
  }

  private createSaveNormTransactionHandler(queue: sqs.Queue) {
    const handler = new KepcoLambda(this, "SaveNormTransactionHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/save-norm-transaction"),
        environment: {
          QUEUE_URL: queue.queueUrl,
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    queue.grantSendMessages(handler);

    this.restApiResource
      .resourceForPath("norm-transactions")
      .addMethod(HttpMethod.POST, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private createSaveNormTransactionConsumer(queue: sqs.Queue) {
    const handler = new KepcoLambda(this, "saveNormTransactionConsumer", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        code: lambda.Code.fromAsset(
          "./dist/lambdas/save-norm-transaction-consumer"
        ),
        timeout: cdk.Duration.seconds(
          LAMBDA_FUNCTION_TIMEOUT_DURATION.saveNormTransactionHandler
        ),
        ...this.lambdaVpcConfig,
        logGroup: this.logGroup,
      },
    }).getLambda();

    new SqsToLambda(this, "SqsToSaveNormTransactionConsumer", {
      existingLambdaObj: handler,
      existingQueueObj: queue,
      sqsEventSourceProps: {
        maxConcurrency: 50,
        reportBatchItemFailures: true,
        batchSize: 1,
      },
    });
  }

  private getPresignedUrlHandler() {
    const handler = new KepcoLambda(this, "PresignedUrlHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/get-presigned-url"),
        environment: {
          UPLOAD_PRESIGN_EXPR:
            this.config.application.presingedUrlExpires.toString(),
          S3_BUCKET_NAME: this.privateBucket.bucketName,
        },
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.privateBucket.grantReadWrite(handler);

    this.restApiResource
      .resourceForPath("presigned-url")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private getListSystemSettingHandler() {
    const handler = new KepcoLambda(this, "GetListSystemSettingHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        code: lambda.Code.fromAsset("./dist/lambdas/list-system-setting"),
        logGroup: this.logGroup,
      },
    }).getLambda();

    this.restApiResource
      .resourceForPath("system-settings")
      .addMethod(HttpMethod.GET, new LambdaIntegration(handler), {
        authorizer: this.authorizer,
        authorizationType: AuthorizationType.COGNITO,
      });
  }

  private airdropBatchHandler() {
    const handler = new KepcoLambda(this, "AirdropBatchHandler", {
      account: this.account,
      stage: this.stage,
      region: this.region,
      lambdaProps: {
        ...this.lambdaVpcConfig,
        timeout: cdk.Duration.seconds(
          LAMBDA_FUNCTION_TIMEOUT_DURATION.airdropBatchHandler
        ),
        code: lambda.Code.fromAsset("./dist/lambdas/airdrop-batch"),
        logGroup: this.logGroup,
      },
    }).getLambda();

    new events.Rule(this, "AirdropBatchExecuteRule", {
      targets: [new targets.LambdaFunction(handler)],
      schedule: events.Schedule.expression(
        this.config.application.airdropBatchExecuteTimeRule
      ),
    });
  }

  private configApiGatewayWaf(restApiGateway: RestApi) {
    const webACL = new waf.CfnWebACL(
      this,
      `MoactACL2${capitalize(camelCase(this.stage))}`,
      {
        defaultAction: {
          allow: {},
        },
        scope: "REGIONAL",
        visibilityConfig: {
          cloudWatchMetricsEnabled: true,
          metricName: "waf",
          sampledRequestsEnabled: false,
        },

        rules: [
          {
            name: "AWS-AWSManagedRulesCommonRuleSet",
            priority: 0,
            statement: {
              managedRuleGroupStatement: {
                vendorName: "AWS",
                name: "AWSManagedRulesCommonRuleSet",
              },
            },
            overrideAction: {
              none: {},
            },
            visibilityConfig: {
              sampledRequestsEnabled: true,
              cloudWatchMetricsEnabled: true,
              metricName: "AWS-AWSManagedRulesCommonRuleSet",
            },
          },
          {
            name: "AWS-AWSManagedRulesAdminProtectionRuleSet",
            priority: 1,
            statement: {
              managedRuleGroupStatement: {
                vendorName: "AWS",
                name: "AWSManagedRulesAdminProtectionRuleSet",
              },
            },
            overrideAction: {
              none: {},
            },
            visibilityConfig: {
              sampledRequestsEnabled: true,
              cloudWatchMetricsEnabled: true,
              metricName: "AWS-AWSManagedRulesAdminProtectionRuleSet",
            },
          },
          {
            name: "AWS-AWSManagedRulesKnownBadInputsRuleSet",
            priority: 2,
            statement: {
              managedRuleGroupStatement: {
                vendorName: "AWS",
                name: "AWSManagedRulesKnownBadInputsRuleSet",
              },
            },
            overrideAction: {
              none: {},
            },
            visibilityConfig: {
              sampledRequestsEnabled: true,
              cloudWatchMetricsEnabled: true,
              metricName: "AWS-AWSManagedRulesKnownBadInputsRuleSet",
            },
          },
        ],
      }
    );

    new waf.CfnWebACLAssociation(this, "WebACLAssociation", {
      webAclArn: webACL.attrArn,
      resourceArn: `arn:aws:apigateway:${cdk.Stack.of(this).region
        }::/restapis/${restApiGateway.restApiId}/stages/${restApiGateway.deploymentStage.stageName
        }`,
    });
  }
}
Leave a Comment