[
  {
    "path": ".github/workflows/build.yml",
    "content": "permissions:\n  contents: read\nname: Build\non:\n  push:\n    branches:\n      - main\n  workflow_dispatch:\n  pull_request:\njobs:\n  Build-and-Test-CDK:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n      - name: Use Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: '22.x'\n      - run: |\n          npm ci\n          npm run format:check\n        working-directory: ./cdk\n        name: Install dependencies and run static analysis\n      - run: |\n          npm run build\n          npm run test\n        working-directory: ./cdk\n        name: build and test\n  Build-and-Test-Webapp:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n      - name: Use Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: '22.x'\n      - run: |\n          npm ci\n          npm run format:check\n        working-directory: ./webapp\n        name: Install dependencies and run static analysis\n      - run: |\n          cp .env.local.example .env.local\n          npm run build\n        working-directory: ./webapp\n        name: build\n"
  },
  {
    "path": ".github/workflows/commitlint.yml",
    "content": "name: PR Title\n\non:\n  pull_request_target:\n    types: [opened, edited, synchronize]\n\npermissions:\n  pull-requests: read\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: amannn/action-semantic-pull-request@v6\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/needs-triage.yml",
    "content": "name: Add needs-triage label\non:\n  issues:\n    types: [opened]\n\npermissions:\n  issues: write\n\njobs:\n  add-label:\n    if: ${{ !endsWith(github.event.issue.user.login, '[bot]') }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/github-script@v7\n        with:\n          script: |\n            await github.rest.issues.addLabels({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issue_number: context.issue.number,\n              labels: ['needs-triage']\n            });\n"
  },
  {
    "path": ".github/workflows/release-please.yml",
    "content": "on:\n  push:\n    branches:\n      - main\n\npermissions:\n  contents: write\n  pull-requests: write\n\nname: release-please\n\njobs:\n  release-please:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: googleapis/release-please-action@v4\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close stale issues and PRs\non:\n  schedule:\n    - cron: '0 0 * * *'\n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v9\n        with:\n          days-before-stale: 60\n          days-before-close: 14\n          stale-issue-label: stale\n          stale-pr-label: stale\n          stale-issue-message: >\n            This issue has been automatically marked as stale because it has not had\n            recent activity. It will be closed if no further activity occurs within 14 days.\n          stale-pr-message: >\n            This PR has been automatically marked as stale because it has not had\n            recent activity. It will be closed if no further activity occurs within 14 days.\n          exempt-issue-labels: 'priority: high'\n          exempt-pr-labels: 'priority: high'\n"
  },
  {
    "path": ".github/workflows/update_snapshot.yml",
    "content": "name: Update snapshot\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - 'dependabot/**'\n  issue_comment:\n    types: [created]\n\npermissions:\n  contents: write\njobs:\n  update:\n    if: >\n      github.event_name == 'push' ||\n      github.event_name == 'workflow_dispatch' ||\n      (github.event.issue.pull_request && contains(github.event.comment.body, '/update-snapshot'))\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          ref: ${{ github.event_name == 'issue_comment' && format('refs/pull/{0}/head', github.event.issue.number) || '' }}\n      - name: Use Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"22.x\"\n      - run: |\n          npm ci\n          npm run test -- -u\n        working-directory: ./cdk\n      - name: Add & Commit\n        uses: EndBug/add-and-commit@v7.2.0\n        with:\n          add: \"cdk/test/__snapshots__/.\"\n          message: \"update snapshot\"\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n    \"trailingComma\": \"all\",\n    \"tabWidth\": 2,\n    \"semi\": true,\n    \"singleQuote\": true,\n    \"printWidth\": 120\n}"
  },
  {
    "path": ".release-please-manifest.json",
    "content": "{\n  \".\": \"2.1.0\"\n}\n"
  },
  {
    "path": ".serverless-full-stack-webapp-starter-kit/design/DESIGN_PRINCIPLES.md",
    "content": "# Design Principles\n\nThis document is for kit maintainers. If you copied this kit to build your own app, you can safely delete the `.serverless-full-stack-webapp-starter-kit/` directory.\n\n## What this kit is\n\nA template you **copy** (not fork) and grow into your own app. It is not a framework — users are expected to read, understand, and modify every file.\n\n## Quality standards\n\nAs an aws-samples project:\n\n- Correctness is the top priority — users learn patterns from this code.\n- Reproducibility — following the README must produce a working deployment.\n- Readability — code should be understandable by developers new to serverless.\n- One-command deploy — `npx cdk deploy --all` must be the only deployment step.\n\nThe litmus test for any PR: \"After this merges, will a developer who copies the kit build their app on a correct understanding?\"\n\n## Design decisions\n\n### Template, not framework\n\n- Breaking changes have low impact — users copy and diverge. Major versions can be bumped without a lengthy deprecation cycle.\n- The sample app exists solely to prove all kit components work together. Users will delete it. Do not expand sample app features beyond what is needed for this proof.\n- Avoid over-abstraction. Readability and modifiability matter more than DRY.\n\n### What to include\n\n- Patterns that every serverless full-stack webapp needs (auth, DB, async jobs, real-time).\n- Operational essentials (migration, logging, cost-optimized defaults).\n- Only what cannot be trivially added later.\n\n### What to exclude\n\n- App-specific business logic.\n- Dependencies on specific AI models or services.\n- Patterns needed by fewer than half of expected users.\n\n### Technology choices\n\n| Choice | Rationale |\n|--------|-----------|\n| `prisma db push` over `prisma migrate` | Simpler default for starter-kit scope. Users can switch to `prisma migrate` when they need migration history. |\n| NAT Instance over NAT Gateway | ~$30/month savings. Acceptable trade-off for a starter kit. |\n| Single Lambda for all async jobs | Reduces cold starts and simplifies deployment. `cmd` parameter selects the entry point. |\n| `proxy.ts` over Next.js middleware | Runs inside Lambda handler, avoiding cold-start CPU starvation from JWKS fetch in middleware. |\n| `output: 'standalone'` | Required for Lambda deployment via Docker image. |\n| Lambda Web Adapter | Enables response streaming with CloudFront + Lambda Function URL. |\n\n### Architecture Decision Records (ADR)\n\nADRs are not used yet. Introduce `design/adr/` when a major technology decision is made (e.g., ORM migration, database engine change) that requires recording the context, alternatives considered, and rationale.\n\n### Migration guides\n\nWhen a breaking change is introduced, write a migration guide alongside the ADR. Place it in `docs/migration/` (e.g., `docs/migration/v3-migration-prompt.md`).\n\nA migration guide is not a complete step-by-step procedure for users. It is an input for an AI coding agent — a meta-prompt that the agent reads, compares against the user's codebase, and uses to build a project-specific migration plan. Write it with that consumer in mind: describe what changed, why, and what patterns in user code are affected, rather than prescribing exact commands.\n\nTo surface the guide in release notes, include a link in the `BREAKING CHANGE:` commit footer:\n\n```\nfeat!: replace ORM from Prisma to Drizzle\n\nBREAKING CHANGE: ORM has been replaced. See [migration guide](docs/migration/v3-migration-prompt.md) for details.\n```\n\nrelease-please will carry this into the Breaking Changes section of the GitHub Release.\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# AGENTS.md\n\n## Commands\n\n```bash\n# webapp\ncd webapp && npm ci\ncd webapp && npm run dev          # starts on port 3010\ncd webapp && npm run build\ncd webapp && npm run lint\ncd webapp && npm run format\n\n# cdk\ncd cdk && npm ci\ncd cdk && npm run build\ncd cdk && npm test\ncd cdk && npm run format\ncd cdk && npx cdk deploy --all\ncd cdk && npx cdk diff\n\n# local development (requires Docker)\ndocker compose up -d              # PostgreSQL on port 5432\ncd webapp && npx prisma db push   # sync schema to local DB\ncd webapp && npm run dev\n```\n\n## Development guide\n\n### Authentication\n\nAll server-side mutations must go through `authActionClient` (defined in `lib/safe-action.ts`). It validates the Cognito session via Amplify server-side auth and injects `ctx.userId`. Never call Prisma directly from a Server Action without this middleware.\n\n`proxy.ts` handles route protection (redirect to `/sign-in` for unauthenticated users). It is NOT a Next.js middleware file — it runs inside the Lambda handler. There is no `middleware.ts` in this project.\n\n### Async jobs\n\nThe dispatch flow is: Server Action → `runJob()` (Lambda async invoke) → `async-job-runner.ts` (discriminated union dispatch) → job handler → `sendEvent()` (AppSync Events) → client `useEventBus` hook.\n\nTo add a new job:\n1. Add a Zod schema with a `type` literal to the discriminated union in `async-job-runner.ts`\n2. Implement the handler in `src/jobs/async-job/`\n3. Add the case to the switch statement\n\nAll job types share a single Lambda function via `job.Dockerfile`. The CDK `cmd` parameter selects the entry point.\n\n### Database migration\n\n`prisma db push` is used for schema sync by default. The migration runner Lambda is invoked automatically during `cdk deploy` via CDK Trigger. For manual invocation, use the `MigrationCommand` from CDK outputs.\n\nSchema changes: edit `prisma/schema.prisma` → run `npx prisma db push` locally → commit. The `zod-prisma-types` generator auto-generates Zod schemas from the Prisma schema. If you switch to `prisma migrate`, update the migration runner accordingly.\n\n### Lambda environment\n\nThe webapp runs on Lambda behind CloudFront via Lambda Web Adapter (response streaming). `next.config.ts` uses `output: 'standalone'`. Build-time env vars (prefixed `NEXT_PUBLIC_`) are injected via CDK `ContainerImageBuild` build args — they cannot be changed at runtime.\n\n### Real-time notifications\n\nServer → client push uses AppSync Events. Server-side: `sendEvent(channelName, payload)` with IAM SigV4 signing. Client-side: `useEventBus` hook with Cognito user pool auth. The channel namespace is `event-bus/`.\n\n## Documentation policy\n\n- Do not document what can be derived from code. An agent can read the codebase.\n- Enforce verifiable constraints with tests and linters, not prose.\n- Code comments explain \"why not\" only — the non-obvious reason something was done a certain way.\n- Before adding a line to this file, ask: \"If I remove this line, will an agent make a mistake?\" If no, don't add it. If a root cause is fixed, remove the corresponding line.\n\n## Conventions\n\n- PR titles and code comments in English.\n- Issues and discussions in English or Japanese.\n- PR titles follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `chore:`, etc.).\n- UI components: use [shadcn/ui](https://ui.shadcn.com/). Do not introduce alternative component libraries.\n- Logs: use JSON structured output.\n- Dependencies: esbuild and Next.js bundle everything, so only packages with native binaries needed at Lambda runtime belong in `dependencies`. Everything else goes in `devDependencies`.\n\n## Do not\n\n- Do not bypass `authActionClient` for any mutation. No raw Prisma calls from Server Actions.\n- Do not add `middleware.ts`. Route protection is handled by `proxy.ts` inside the Lambda runtime.\n- Do not use `prisma migrate` commands unless you have explicitly switched from `prisma db push`. The default setup uses `prisma db push`.\n- Do not hardcode AWS region or account IDs. Use CDK context or environment variables.\n- Do not add `NEXT_PUBLIC_` env vars to `.env.local` for deployed builds — they must be set as CDK build args in `webapp.ts`.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [2.1.0](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/compare/v2.0.0...v2.1.0) (2026-03-22)\n\n\n### Features\n\n* add /update-snapshot comment trigger to update_snapshot workflow ([764a4fa](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/764a4fa0808b7fb11307f393208449588daa8b3c))\n* add CloudWatch LogGroup with retention policy to Lambda functions ([#117](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/117)) ([53877bb](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/53877bb31b5af7cfbb5e80903be076e8ce1c38d6)), closes [#103](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/103)\n* **database:** enable Data API and connection logging ([#123](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/123)) ([e32dc7a](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/e32dc7ad5ceb0c36fd287c18e64177e92f0c5ff0))\n* increase webapp Lambda memory from 512MB to 1024MB ([#116](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/116)) ([03c5a00](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/03c5a007e141c25b9631a3b38680f62dfe22320f)), closes [#101](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/101)\n\n\n### Bug Fixes\n\n* add lambda:InvokeFunction permission for CloudFront OAC ([#83](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/83)) ([3cc66bf](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/3cc66bfb775f3a086f231783955b258136ddd266))\n* **auth:** improve auth error handling and fix Link CORS issue ([#120](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/120)) ([84be605](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/84be6057d7416c7dd34a7eec3422144bce2f964c))\n* disable Cognito self sign-up by default ([#115](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/115)) ([9396e6f](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/9396e6fbefe2feadb5e2eea4ccc03aa2a1c0888e)), closes [#106](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/106)\n* prevent CloudFront cache poisoning for Next.js RSC responses ([#119](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/119)) ([70cddda](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/70cddda904ee816a763c53a7a36ce1ea183ff941))\n* **prisma:** add retry for Aurora Serverless v2 connection errors ([#121](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/121)) ([7c05dfb](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/7c05dfb61949caf8b8f79a56ec7c2c1e88a04839))\n* support Amazon Linux 2023 for NAT instance ([#81](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/81)) ([0c41aa8](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/0c41aa8ec1946883f397126c8e6ae91c0e96b1b0))\n\n## 2.0.0 (2026-03-18)\n\n\n### Features\n\n* invalidate cloudfront caches when lambda configuration changes ([#63](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/63)) ([b76d122](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/b76d1223d1e00a6b77ee5f89b4d7f9678b01232a))\n\n\n### Bug Fixes\n\n* pass useNatInstance prop to MainStack ([#71](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/71)) ([23c9e31](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/23c9e31a73d44254cf45d84015bc6cd9c045880b))\n* Workflow does not contain permissions ([#59](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/59)) ([34be1de](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/34be1deaf7e02320e097861b989e1e046bfa8488))\n* Workflow does not contain permissions ([#60](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/issues/60)) ([6fb8901](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/commit/6fb89018d26428c069679a1443e2010cb4bb4fc5))\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "## Code of Conduct\nThis project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).\nFor more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact\nopensource-codeofconduct@amazon.com with any additional questions or comments.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\nThank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional\ndocumentation, we greatly value feedback and contributions from our community.\n\nPlease read through this document before submitting any issues or pull requests to ensure we have all the necessary\ninformation to effectively respond to your bug report or contribution.\n\n## Language policy\n\n- PR titles and code comments: English\n- Issues and discussions: English or Japanese\n\n## Commit and PR conventions\n\nPR titles follow [Conventional Commits](https://www.conventionalcommits.org/) (Angular style). Enforced by CI. Releases are automated via [Release Please](https://github.com/googleapis/release-please).\n\n## Reporting Bugs/Feature Requests\n\nWe welcome you to use the GitHub issue tracker to report bugs or suggest features.\n\nWhen filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already\nreported the issue. Please try to include as much information as you can. Details like these are incredibly useful:\n\n* A reproducible test case or series of steps\n* The version of our code being used\n* Any modifications you've made relevant to the bug\n* Anything unusual about your environment or deployment\n\n## Contributing via Pull Requests\nContributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:\n\n1. You are working against the latest source on the *main* branch.\n2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.\n3. You open an issue to discuss any significant work - we would hate for your time to be wasted.\n\nTo send us a pull request, please:\n\n1. Fork the repository.\n2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.\n3. Ensure local tests pass.\n4. Commit to your fork using clear commit messages.\n5. Send us a pull request, answering any default questions in the pull request interface.\n6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.\n\nGitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and\n[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).\n\n\n## Finding contributions to work on\nLooking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.\n\n\n## Code of Conduct\nThis project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).\nFor more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact\nopensource-codeofconduct@amazon.com with any additional questions or comments.\n\n\n## Security issue notifications\nIf you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.\n\n\n## Licensing\n\nSee the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Serverless Full Stack WebApp Starter Kit\n[![Build](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/actions/workflows/build.yml/badge.svg)](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/actions/workflows/build.yml)\n[![Release](https://img.shields.io/github/v/release/aws-samples/serverless-full-stack-webapp-starter-kit)](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit/releases)\n\nA serverless full-stack web app template you **copy and grow into your own app**. Not a framework — you own every file.\n\nCopy, deploy with a single command, then replace the sample todo app with your own features.\n\n## What you get\n\n1. **Working sample app** — A todo app with authentication, DB CRUD, async jobs, and real-time notifications wired end-to-end. Designed as a readable reference for AI coding agents and humans alike.\n2. **End-to-end type safety** — Types flow from Prisma ORM through Zod schemas and Server Actions to React components in a single chain.\n3. **Serverless from day one** — Fully serverless architecture starting under $10/month that scales without operational overhead.\n4. **Integrated DB migration** — Schema migration is integrated into the CDK deploy process via CDK Trigger, providing a development-to-production path out of the box.\n\nYou can refer to [the blog article](https://tmokmss.github.io/blog/posts/serverless-fullstack-webapp-architecture-2025/) for more details (also [Japanese version](https://tmokmss.hatenablog.com/entry/serverless-fullstack-webapp-architecture-2025)).\n\n## Sample app\n\nThe kit includes a simple todo app to demonstrate how all components work together.\n\n<img align=\"left\" width=\"300\" src=\"./.serverless-full-stack-webapp-starter-kit/docs/imgs/signin.png\">\nSign in/up page redirects to Cognito Managed Login.\n<br clear=\"left\"/>\n\n&nbsp;\n\n<img align=\"left\" width=\"300\" src=\"./.serverless-full-stack-webapp-starter-kit/docs/imgs/top.png\">\nAfter login, you can add, delete, and manage your todo items. The translate button triggers an async job and pushes a real-time notification to refresh the page.\n<br clear=\"left\"/>\n\n## Architecture\n\n![architecture](./.serverless-full-stack-webapp-starter-kit/docs/imgs/architecture.png)\n\n| Service | Role |\n|---------|------|\n| [Aurora PostgreSQL Serverless v2](https://aws.amazon.com/rds/aurora/serverless/) | Relational database with Prisma ORM |\n| [Next.js App Router](https://nextjs.org/docs/app) on [Lambda](https://aws.amazon.com/lambda/) | Unified frontend and backend |\n| [CloudFront](https://aws.amazon.com/cloudfront/) + Lambda Function URL | Content delivery with response streaming |\n| [Cognito](https://aws.amazon.com/cognito/) | Authentication (email by default, OIDC federation supported) |\n| [AppSync Events](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html) + Lambda | Async jobs and real-time notifications |\n| [EventBridge](https://aws.amazon.com/eventbridge/) | Scheduled jobs |\n| [CloudWatch](https://aws.amazon.com/cloudwatch/) + S3 | Access logging |\n| [CDK](https://aws.amazon.com/cdk/) | Infrastructure as Code |\n\nFully serverless — high cost efficiency, scalability, and minimal operational overhead.\n\n## Getting started\n\nPrerequisites:\n* [Node.js](https://nodejs.org/) (>= v20)\n* [Docker](https://docs.docker.com/get-docker/)\n* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) with a configured IAM profile\n\n### 1. Copy the kit\n\nUse the GitHub template (\"Use this template\" button) or clone and copy:\n\n```sh\ngit clone https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit.git my-app\ncd my-app\nrm -rf .git && git init\n# Record the kit version in your initial commit for future reference\ngit add -A && git commit -m \"Initial commit from serverless-full-stack-webapp-starter-kit vX.Y.Z\"\n```\n\n### 2. Customize (optional)\n\n- Update the application name (stack name, tags) in [`cdk/bin/cdk.ts`](cdk/bin/cdk.ts)\n- Set a custom domain in `cdk/bin/cdk.ts`\n- Remove `cdk.context.json` from `cdk/.gitignore` and commit it (recommended for your own project)\n- Switch from `prisma db push` to `prisma migrate` if you need migration history\n\n### 3. Deploy\n\n```sh\ncd cdk\nnpm ci\nnpx cdk bootstrap\nnpx cdk deploy --all\n```\n\nInitial deployment takes about 20 minutes. After success, you'll see:\n\n```\n ✅  ServerlessWebappStarterKitStack\n\nOutputs:\nServerlessWebappStarterKitStack.FrontendDomainName = https://web.example.com\nServerlessWebappStarterKitStack.DatabaseSecretsCommand = aws secretsmanager get-secret-value ...\nServerlessWebappStarterKitStack.DatabasePortForwardCommand = aws ssm start-session ...\n```\n\nOpen the `FrontendDomainName` URL to try the sample app.\n\n> **Note:** `DatabasePortForwardCommand` establishes a local connection to your RDS database, and `DatabaseSecretsCommand` retrieves database credentials from Secrets Manager.\n\n### 4. Add your own features\n\nSee [`AGENTS.md`](./AGENTS.md) for development guide — local development setup, authentication patterns, async job setup, DB migration, and coding conventions.\n\nTo add social sign-in (Google, Facebook, etc.), see [Add social sign-in to a user pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-configuring-federation-with-social-idp.html).\n\n## Maintenance policy\n\nThis kit follows [Semantic Versioning](https://semver.org/). Since users copy (not fork) this kit, breaking changes are introduced as new major versions without a lengthy deprecation cycle.\n\n## Cost\n\nSample cost breakdown for us-east-1, one month, with cost-optimized configuration:\n\n| Service | Usage Details | Monthly Cost [USD] |\n|---------|--------------|-------------------|\n| Aurora Serverless v2 | 0.5 ACU × 2 hour/day, 1GB storage | 3.6 |\n| Cognito | 100 MAU | 1.5 |\n| AppSync Events | 100 events/month, 10 hours connection/user/month | 0.02 |\n| Lambda | 1024MB × 200ms/request | 0.15 |\n| Lambda@Edge | 128MB × 50ms/request | 0.09 |\n| VPC | NAT Instance (t4g.nano) × 1 | 3.02 |\n| EventBridge | Scheduler 100 jobs/month | 0.0001 |\n| CloudFront | Data transfer 1kB/request | 0.01 |\n| **Total** | | **8.49** |\n\nAssumes 100 users/month, 1000 requests/user. Costs could be further reduced with [Free Tier](https://aws.amazon.com/free/).\n\n## Clean up\n\n```sh\ncd cdk\nnpx cdk destroy --force\n```\n\n## Maintainers\n* [Kenji Kono (konokenj)](https://github.com/konokenj)\n\n### Core contributors\n* [Masashi Tomooka (tmokmss)](https://github.com/tmokmss) — original author\n* [Kazuho Cryer-Shinozuka (badmintoncryer)](https://github.com/badmintoncryer)\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\nContributors (human and AI) **must** read [`.serverless-full-stack-webapp-starter-kit/design/DESIGN_PRINCIPLES.md`](./.serverless-full-stack-webapp-starter-kit/design/DESIGN_PRINCIPLES.md) before making changes. It defines the design decisions and constraints that govern this kit.\n\n## Security\nSee [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.\n\n## License\nThis library is licensed under the MIT-0 License. See the LICENSE file.\n"
  },
  {
    "path": "cdk/.gitignore",
    "content": "*.js\n!jest.config.js\n!lib/constructs/cf-lambda-furl-service/cf-function/*.js\n*.d.ts\nnode_modules\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n\n# Ignoring this file only because it is an OSS sample solution.\n# It is generally recommended to store the cdk.context.json in git.\ncdk.context.json\n"
  },
  {
    "path": "cdk/README.md",
    "content": "# Infrastructure as Code (AWS CDK)\nThis is the IaC project written in AWS Cloud Development Kit (CDK).\n\n## Useful commands\n* `npx cdk deploy`: deploy the infrastructure\n* `npx cdk deploy --require-approval never`: deploy without confirmation (useful for automation)\n* `npx cdk deploy --hotswap`: deploy with hotswap feature enabled (useful for development)\n* `npx cdk watch`: watch your code and deploy every time changes are detected\n* `npx cdk destroy`: delete the infrastructure\n"
  },
  {
    "path": "cdk/bin/cdk.ts",
    "content": "#!/usr/bin/env node\nimport 'source-map-support/register';\nimport * as cdk from 'aws-cdk-lib';\nimport { MainStack } from '../lib/main-stack';\nimport { UsEast1Stack } from '../lib/us-east-1-stack';\n\nconst app = new cdk.App();\n\ninterface EnvironmentProps {\n  account: string;\n\n  /**\n   * Custom domain name for the webapp and Cognito.\n   * You need to have a public Route53 hosted zone for the domain name in your AWS account.\n   *\n   * @default No custom domain name. When not specified, the stack automatically generates\n   * a random prefix for the Cognito domain (e.g., webapp-abc123def4.auth.us-west-2.amazoncognito.com)\n   * and uses the CloudFront default domain (e.g., d1234567890.cloudfront.net) for the webapp.\n   */\n  domainName?: string;\n\n  /**\n   * Use a NAT instance instead of NAT Gateways.\n   * @default true\n   */\n  useNatInstance?: boolean;\n}\n\nconst props: EnvironmentProps = {\n  account: process.env.CDK_DEFAULT_ACCOUNT!,\n  // domainName: 'FIXME.example.com',\n  useNatInstance: true,\n};\n\nconst virginia = new UsEast1Stack(app, 'ServerlessWebappStarterKitUsEast1Stack', {\n  env: {\n    account: props.account,\n    region: 'us-east-1',\n  },\n  crossRegionReferences: true,\n  domainName: props.domainName,\n});\nnew MainStack(app, 'ServerlessWebappStarterKitStack', {\n  env: {\n    account: props.account,\n    region: process.env.CDK_DEFAULT_REGION,\n  },\n  crossRegionReferences: true,\n  sharedCertificate: virginia.certificate,\n  domainName: props.domainName,\n  useNatInstance: props.useNatInstance,\n  signPayloadHandler: virginia.signPayloadHandler,\n});\n\ncdk.Tags.of(app).add('Application', 'ServerlessFullStackWebappStarterKit');\n\n// import { Aspects } from 'aws-cdk-lib';\n// import { AwsSolutionsChecks } from 'cdk-nag';\n// Aspects.of(app).add(new AwsSolutionsChecks());\n"
  },
  {
    "path": "cdk/cdk.json",
    "content": "{\n  \"app\": \"npx ts-node --prefer-ts-exts bin/cdk.ts\",\n  \"watch\": {\n    \"include\": [\n      \"**\"\n    ],\n    \"exclude\": [\n      \"README.md\",\n      \"cdk*.json\",\n      \"**/*.d.ts\",\n      \"**/*.js\",\n      \"tsconfig.json\",\n      \"package*.json\",\n      \"yarn.lock\",\n      \"node_modules\",\n      \"test\"\n    ]\n  },\n  \"context\": {\n    \"@aws-cdk/aws-lambda:recognizeLayerVersion\": true,\n    \"@aws-cdk/core:checkSecretUsage\": true,\n    \"@aws-cdk/core:target-partitions\": [\n      \"aws\",\n      \"aws-cn\"\n    ],\n    \"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver\": true,\n    \"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName\": true,\n    \"@aws-cdk/aws-ecs:arnFormatIncludesClusterName\": true,\n    \"@aws-cdk/aws-iam:minimizePolicies\": true,\n    \"@aws-cdk/core:validateSnapshotRemovalPolicy\": true,\n    \"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName\": true,\n    \"@aws-cdk/aws-s3:createDefaultLoggingPolicy\": true,\n    \"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption\": true,\n    \"@aws-cdk/aws-apigateway:disableCloudWatchRole\": true,\n    \"@aws-cdk/core:enablePartitionLiterals\": true,\n    \"@aws-cdk/aws-events:eventsTargetQueueSameAccount\": true,\n    \"@aws-cdk/aws-iam:standardizedServicePrincipals\": true,\n    \"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker\": true,\n    \"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName\": true,\n    \"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy\": true,\n    \"@aws-cdk/aws-route53-patters:useCertificate\": true,\n    \"@aws-cdk/customresources:installLatestAwsSdkDefault\": false,\n    \"@aws-cdk/aws-rds:databaseProxyUniqueResourceName\": true,\n    \"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup\": true,\n    \"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId\": true,\n    \"@aws-cdk/aws-ec2:launchTemplateDefaultUserData\": true,\n    \"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments\": true,\n    \"@aws-cdk/aws-redshift:columnId\": true,\n    \"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2\": true\n  }\n}\n"
  },
  {
    "path": "cdk/jest.config.js",
    "content": "module.exports = {\n  testEnvironment: 'node',\n  roots: ['<rootDir>/test'],\n  testMatch: ['**/*.test.ts'],\n  transform: {\n    '^.+\\\\.tsx?$': 'ts-jest'\n  },\n  snapshotSerializers: ['<rootDir>/test/snapshot-plugin.ts']\n};\n"
  },
  {
    "path": "cdk/lib/constructs/async-job.ts",
    "content": "import { Construct } from 'constructs';\nimport { CfnOutput, Duration, RemovalPolicy, TimeZone } from 'aws-cdk-lib';\nimport { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';\nimport { Architecture, DockerImageCode, DockerImageFunction, IFunction } from 'aws-cdk-lib/aws-lambda';\nimport { Platform } from 'aws-cdk-lib/aws-ecr-assets';\nimport { Database } from './database';\nimport { EventBus } from './event-bus';\nimport { PolicyStatement } from 'aws-cdk-lib/aws-iam';\nimport { join } from 'path';\nimport { Schedule, ScheduleExpression, ScheduleTargetInput } from 'aws-cdk-lib/aws-scheduler';\nimport { LambdaInvoke } from 'aws-cdk-lib/aws-scheduler-targets';\n\nexport interface AsyncJobProps {\n  readonly database: Database;\n  readonly eventBus: EventBus;\n}\n\nexport class AsyncJob extends Construct {\n  readonly handler: IFunction;\n\n  constructor(scope: Construct, id: string, props: AsyncJobProps) {\n    super(scope, id);\n    const { database, eventBus } = props;\n\n    const handler = new DockerImageFunction(this, 'Handler', {\n      code: DockerImageCode.fromImageAsset(join('..', 'webapp'), {\n        cmd: ['async-job-runner.handler'],\n        platform: Platform.LINUX_ARM64,\n        file: 'job.Dockerfile',\n      }),\n      memorySize: 256,\n      timeout: Duration.minutes(10),\n      architecture: Architecture.ARM_64,\n      environment: {\n        ...database.getLambdaEnvironment('main'),\n        EVENT_HTTP_ENDPOINT: eventBus.httpEndpoint,\n      },\n      vpc: database.cluster.vpc,\n      // limit concurrency to mitigate any possible EDoS attacks\n      reservedConcurrentExecutions: 1,\n      logGroup: new LogGroup(this, 'HandlerLogs', {\n        retention: RetentionDays.ONE_WEEK,\n        removalPolicy: RemovalPolicy.DESTROY,\n      }),\n    });\n\n    handler.connections.allowToDefaultPort(database);\n    eventBus.api.grantPublish(handler);\n\n    handler.addToRolePolicy(\n      new PolicyStatement({\n        actions: ['translate:TranslateText', 'comprehend:DetectDominantLanguage'],\n        resources: ['*'],\n      }),\n    );\n\n    new CfnOutput(this, 'HandlerArn', { value: handler.functionArn });\n    this.handler = handler;\n\n    // you can add scheduled jobs here.\n    this.addSchedule(\n      'SampleJob',\n      ScheduleExpression.cron({ minute: '0', hour: '0', day: '1', timeZone: TimeZone.ETC_UTC }),\n    );\n  }\n\n  public addSchedule(jobType: string, schedule: ScheduleExpression, payload?: any) {\n    return new Schedule(this, jobType, {\n      schedule,\n      target: new LambdaInvoke(this.handler, {\n        input: ScheduleTargetInput.fromObject({ jobType, payload }),\n        retryAttempts: 5,\n      }),\n    });\n  }\n}\n"
  },
  {
    "path": "cdk/lib/constructs/auth/.gitignore",
    "content": "!prefix-generator.js\n"
  },
  {
    "path": "cdk/lib/constructs/auth/index.ts",
    "content": "import { UpdateUserPoolClientCommandInput } from '@aws-sdk/client-cognito-identity-provider';\nimport { CfnOutput, CfnResource, CustomResource, Duration, RemovalPolicy, Stack } from 'aws-cdk-lib';\nimport { ICertificate } from 'aws-cdk-lib/aws-certificatemanager';\nimport { CfnManagedLoginBranding, ManagedLoginVersion, UserPool, UserPoolClient } from 'aws-cdk-lib/aws-cognito';\nimport { Code, Runtime, SingletonFunction } from 'aws-cdk-lib/aws-lambda';\nimport { CnameRecord, IHostedZone } from 'aws-cdk-lib/aws-route53';\nimport { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';\nimport { Construct } from 'constructs';\nimport { readFileSync } from 'fs';\nimport { join } from 'path';\n\nexport interface AuthProps {\n  /**\n   * Route 53 hosted zone for custom domain.\n   *\n   * @default No custom domain. A random prefix will be automatically generated for the Cognito domain.\n   */\n  readonly hostedZone?: IHostedZone;\n  /**\n   * ACM certificate for custom domain (must be in us-east-1 for Cognito).\n   *\n   * @default No custom domain.\n   */\n  readonly sharedCertificate?: ICertificate;\n}\n\nexport class Auth extends Construct {\n  readonly userPool: UserPool;\n  readonly client: UserPoolClient;\n  readonly domainName: string;\n\n  private callbackUrlCount = 0;\n\n  constructor(scope: Construct, id: string, props: AuthProps) {\n    super(scope, id);\n    const { hostedZone } = props;\n    const subDomain = 'auth';\n    let domainPrefix = '';\n    if (!hostedZone) {\n      // When we do not use a custom domain, we must make domainPrefix unique in the AWS region.\n      // To avoid a collision, we generate a random string with CFn custom resource.\n      // This allows the stack to work without requiring a custom domain setup.\n      const generator = new SingletonFunction(this, 'RandomStringGenerator', {\n        runtime: Runtime.NODEJS_22_X,\n        handler: 'index.handler',\n        timeout: Duration.seconds(5),\n        lambdaPurpose: 'RandomStringGenerator',\n        uuid: '11e9c903-f11a-4989-833c-985dddef5eb2',\n        code: Code.fromInline(readFileSync(join(__dirname, 'prefix-generator.js')).toString()),\n      });\n\n      const domainPrefixResource = new CustomResource(this, 'DomainPrefix', {\n        serviceToken: generator.functionArn,\n        resourceType: 'Custom::RandomString',\n        properties: { prefix: 'webapp-', length: 10 },\n        serviceTimeout: Duration.seconds(10),\n      });\n      domainPrefix = domainPrefixResource.getAttString('generated');\n    }\n\n    this.domainName = hostedZone\n      ? `${subDomain}.${hostedZone.zoneName}`\n      : `${domainPrefix}.auth.${Stack.of(this).region}.amazoncognito.com`;\n\n    const userPool = new UserPool(this, 'UserPool', {\n      passwordPolicy: {\n        requireUppercase: true,\n        requireSymbols: true,\n        requireDigits: true,\n        minLength: 8,\n      },\n      // Set to true to allow self sign-up.\n      // When false, administrators must create users via the Cognito console or API.\n      selfSignUpEnabled: false,\n      signInAliases: {\n        username: false,\n        email: true,\n      },\n      removalPolicy: RemovalPolicy.DESTROY,\n    });\n\n    const client = userPool.addClient(`Client`, {\n      idTokenValidity: Duration.days(1),\n      authFlows: {\n        userPassword: true,\n        userSrp: true,\n      },\n      oAuth: {\n        flows: {\n          authorizationCodeGrant: true,\n        },\n        callbackUrls: ['http://localhost/dummy'],\n        logoutUrls: ['http://localhost/dummy'],\n      },\n    });\n\n    this.client = client;\n    this.userPool = userPool;\n\n    const domain = userPool.addDomain('CognitoDomain', {\n      ...(hostedZone && props.sharedCertificate\n        ? {\n            customDomain: {\n              domainName: this.domainName,\n              certificate: props.sharedCertificate,\n            },\n          }\n        : {\n            cognitoDomain: {\n              domainPrefix,\n            },\n          }),\n      managedLoginVersion: ManagedLoginVersion.NEWER_MANAGED_LOGIN,\n    });\n\n    if (hostedZone) {\n      new CnameRecord(this, 'CognitoDomainRecord', {\n        zone: hostedZone,\n        recordName: subDomain,\n        domainName: domain.cloudFrontEndpoint,\n      });\n    }\n\n    new CfnManagedLoginBranding(this, 'Branding', {\n      userPoolId: this.userPool.userPoolId,\n      clientId: client.userPoolClientId,\n      useCognitoProvidedValues: true,\n    });\n\n    new CfnOutput(this, 'UserPoolId', { value: userPool.userPoolId });\n    new CfnOutput(this, 'UserPoolClientId', { value: client.userPoolClientId });\n    new CfnOutput(this, 'UserPoolDomainName', { value: this.domainName });\n  }\n\n  public addAllowedCallbackUrls(callbackUrl: string, logoutUrl: string) {\n    const resource = this.client.node.defaultChild;\n    if (!CfnResource.isCfnResource(resource)) {\n      throw new Error('Expected CfnResource');\n    }\n    resource.addPropertyOverride(`CallbackURLs.${this.callbackUrlCount}`, callbackUrl);\n    resource.addPropertyOverride(`LogoutURLs.${this.callbackUrlCount}`, logoutUrl);\n    this.callbackUrlCount += 1;\n  }\n\n  public updateAllowedCallbackUrls(callbackUrls: string[], logoutUrls: string[]) {\n    // Lambda depends on userPoolClientId but userPoolClient depends on the CloudFront domain name (callback URL) which depends on Lambda (fURL).\n    // To avoid the circular dependency, we update the callback URL after a userPoolClientId is created.\n    // We only use this when custom domain is not used.\n    new AwsCustomResource(this, 'UpdateCallbackUrls', {\n      onUpdate: {\n        service: '@aws-sdk/client-cognito-identity-provider',\n        action: 'updateUserPoolClient',\n        parameters: {\n          ClientId: this.client.userPoolClientId,\n          UserPoolId: this.userPool.userPoolId,\n          AllowedOAuthFlows: ['code'],\n          AllowedOAuthFlowsUserPoolClient: true,\n          AllowedOAuthScopes: ['profile', 'phone', 'email', 'openid', 'aws.cognito.signin.user.admin'],\n          ExplicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_USER_SRP_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'],\n          CallbackURLs: callbackUrls,\n          LogoutURLs: logoutUrls,\n          SupportedIdentityProviders: ['COGNITO'],\n          TokenValidityUnits: {\n            IdToken: 'minutes',\n          },\n          IdTokenValidity: 1440,\n        } satisfies UpdateUserPoolClientCommandInput,\n        physicalResourceId: PhysicalResourceId.of(this.userPool.userPoolId),\n      },\n      policy: AwsCustomResourcePolicy.fromSdkCalls({\n        resources: [this.userPool.userPoolArn],\n      }),\n    });\n  }\n}\n"
  },
  {
    "path": "cdk/lib/constructs/auth/prefix-generator.js",
    "content": "const response = require('cfn-response');\nconst crypto = require('crypto');\n\nexports.handler = async function (event, context) {\n  try {\n    console.log(event);\n    if (event.RequestType == 'Delete') {\n      return await response.send(event, context, response.SUCCESS);\n    }\n\n    const prefix = event.ResourceProperties.prefix ?? '';\n    const length = event.ResourceProperties.length ?? '8';\n    const generate = () => {\n      const random = crypto.randomBytes(parseInt(length)).toString('hex');\n      return `${prefix}${random.slice(0, length)}`;\n    };\n\n    if (event.RequestType == 'Create') {\n      const generated = generate();\n      return await response.send(event, context, response.SUCCESS, { generated }, generated);\n    }\n    if (event.RequestType == 'Update') {\n      const current = event.PhysicalResourceId;\n      if (current.startsWith(prefix)) {\n        return await response.send(event, context, response.SUCCESS, { generated: current }, current);\n      }\n      const generated = generate();\n      return await response.send(event, context, response.SUCCESS, { generated }, generated);\n    }\n  } catch (e) {\n    console.log(e);\n    await response.send(event, context, response.FAILED);\n  }\n};\n"
  },
  {
    "path": "cdk/lib/constructs/cf-lambda-furl-service/edge-function.ts",
    "content": "import { Stack } from 'aws-cdk-lib';\nimport { PolicyStatement, ServicePrincipal, Role } from 'aws-cdk-lib/aws-iam';\nimport { Runtime, IVersion, Version, Architecture } from 'aws-cdk-lib/aws-lambda';\nimport { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';\nimport { StringParameter } from 'aws-cdk-lib/aws-ssm';\nimport { AwsCustomResource, PhysicalResourceId, AwsCustomResourcePolicy } from 'aws-cdk-lib/custom-resources';\nimport { Construct } from 'constructs';\n\n// L@E can only be deployed to us-east-1 region.\nconst StackRegion = 'us-east-1';\n\nexport interface EdgeFunctionProps {\n  entryPath: string;\n}\n\nexport class EdgeFunction extends Construct {\n  private readonly functionVersionParameter: StringParameter;\n\n  constructor(scope: Construct, id: string, props: EdgeFunctionProps) {\n    super(scope, id);\n\n    if (Stack.of(this).region !== StackRegion) {\n      throw new Error('EdgeFunction can only be deployed to us-east-1 region.');\n    }\n\n    const handler = new NodejsFunction(this, 'Handler', {\n      runtime: Runtime.NODEJS_22_X,\n      entry: props.entryPath,\n    });\n    handler.currentVersion;\n    this.functionVersionParameter = new StringParameter(this, 'FunctionVersion', {\n      stringValue: handler.currentVersion.edgeArn,\n    });\n\n    const statement = new PolicyStatement();\n    const edgeLambdaServicePrincipal = new ServicePrincipal('edgelambda.amazonaws.com');\n    statement.addPrincipals(edgeLambdaServicePrincipal);\n    statement.addActions(edgeLambdaServicePrincipal.assumeRoleAction);\n    (handler.role as Role).assumeRolePolicy!.addStatements(statement);\n  }\n\n  public versionArn(scope: Construct) {\n    const id = `VersionArn${this.functionVersionParameter.node.addr}`;\n    const existing = Stack.of(scope).node.tryFindChild(id) as IVersion;\n    if (existing) {\n      return existing;\n    }\n\n    const lookup = new AwsCustomResource(Stack.of(scope), `Lookup${id}`, {\n      onUpdate: {\n        // will also be called for a CREATE event\n        service: 'SSM',\n        action: 'getParameter',\n        parameters: {\n          Name: this.functionVersionParameter.parameterName,\n        },\n        // it is impossible to know when the parameter is updated.\n        // so we need to get the value on every deployment.\n        physicalResourceId: PhysicalResourceId.of(`${Date.now()}`),\n        region: StackRegion,\n      },\n      policy: AwsCustomResourcePolicy.fromSdkCalls({\n        resources: [this.functionVersionParameter.parameterArn],\n      }),\n    });\n    return Version.fromVersionArn(Stack.of(scope), id, lookup.getResponseField('Parameter.Value'));\n  }\n}\n"
  },
  {
    "path": "cdk/lib/constructs/cf-lambda-furl-service/lambda/sign-payload.ts",
    "content": "import type { CloudFrontRequestHandler } from 'aws-lambda';\nimport { createHash } from 'crypto';\n\nconst hashPayload = (payload: Buffer) => {\n  return createHash('sha256').update(payload).digest('hex');\n};\n\nexport const handler: CloudFrontRequestHandler = async (event) => {\n  const request = event.Records[0].cf.request;\n  const body = request.body?.data ?? '';\n\n  const hashedBody = hashPayload(Buffer.from(body, 'base64'));\n  request.headers['x-amz-content-sha256'] = [{ key: 'x-amz-content-sha256', value: hashedBody }];\n\n  // LWA replaces authorization2 to authorization again\n  // if (request.headers['authorization'] != null) {\n  //   request.headers['authorization2'] = [{ key: 'authorization2', value: request.headers['authorization'][0].value }];\n  //   delete request.headers['authorization'];\n  // }\n\n  return request;\n};\n"
  },
  {
    "path": "cdk/lib/constructs/cf-lambda-furl-service/service.ts",
    "content": "import { Construct } from 'constructs';\nimport { Aws, Duration } from 'aws-cdk-lib';\nimport { FunctionUrlAuthType, Function, InvokeMode, CfnPermission } from 'aws-cdk-lib/aws-lambda';\nimport {\n  AllowedMethods,\n  CacheCookieBehavior,\n  CacheHeaderBehavior,\n  CachePolicy,\n  CacheQueryStringBehavior,\n  Distribution,\n  Function as CfFunction,\n  FunctionCode as CfFunctionCode,\n  FunctionEventType,\n  FunctionRuntime,\n  LambdaEdgeEventType,\n  OriginRequestPolicy,\n  SecurityPolicyProtocol,\n} from 'aws-cdk-lib/aws-cloudfront';\nimport { FunctionUrlOrigin } from 'aws-cdk-lib/aws-cloudfront-origins';\nimport * as path from 'path';\nimport { StringParameter } from 'aws-cdk-lib/aws-ssm';\nimport { ARecord, IHostedZone, RecordTarget } from 'aws-cdk-lib/aws-route53';\nimport { CloudFrontTarget } from 'aws-cdk-lib/aws-route53-targets';\nimport { ICertificate } from 'aws-cdk-lib/aws-certificatemanager';\nimport { Bucket } from 'aws-cdk-lib/aws-s3';\nimport { EdgeFunction } from './edge-function';\nimport { AwsCustomResource, PhysicalResourceId, AwsCustomResourcePolicy } from 'aws-cdk-lib/custom-resources';\n\nexport interface CloudFrontLambdaFunctionUrlServiceProps {\n  /**\n   * Subdomain name for the service. If not specified, the root domain will be used.\n   *\n   * @default use root domain\n   */\n  subDomain?: string;\n  handler: Function;\n\n  /**\n   * This should be unique across the app\n   */\n  serviceName: string;\n\n  /**\n   * @default basic auth is disabled\n   */\n  basicAuthUsername?: string;\n  basicAuthPassword?: string;\n\n  /**\n   * Route 53 hosted zone for custom domain.\n   *\n   * @default No custom domain. CloudFront's default domain will be used.\n   */\n  hostedZone?: IHostedZone;\n  /**\n   * ACM certificate for custom domain (must be in us-east-1 for CloudFront).\n   *\n   * @default No custom domain.\n   */\n  certificate?: ICertificate;\n  signPayloadHandler: EdgeFunction;\n  accessLogBucket: Bucket;\n}\n\nexport class CloudFrontLambdaFunctionUrlService extends Construct {\n  public readonly urlParameter: StringParameter;\n  public readonly url: string;\n  public readonly domainName: string;\n\n  constructor(scope: Construct, id: string, props: CloudFrontLambdaFunctionUrlServiceProps) {\n    super(scope, id);\n    const { handler, serviceName, subDomain, hostedZone, certificate, accessLogBucket, signPayloadHandler } = props;\n    let domainName = '';\n    if (hostedZone) {\n      domainName = subDomain ? `${subDomain}.${hostedZone.zoneName}` : hostedZone.zoneName;\n    }\n\n    const furl = handler.addFunctionUrl({\n      authType: FunctionUrlAuthType.AWS_IAM,\n      invokeMode: InvokeMode.RESPONSE_STREAM,\n    });\n    const origin = FunctionUrlOrigin.withOriginAccessControl(furl, {\n      connectionTimeout: Duration.seconds(6),\n      readTimeout: Duration.seconds(60),\n    });\n\n    const cachePolicy = new CachePolicy(this, 'SharedCachePolicy', {\n      queryStringBehavior: CacheQueryStringBehavior.all(),\n      headerBehavior: CacheHeaderBehavior.allowList(\n        // CachePolicy.USE_ORIGIN_CACHE_CONTROL_HEADERS_QUERY_STRINGS contains Host header here,\n        // making it impossible to use with API Gateway\n        'authorization',\n        'Origin',\n        'X-HTTP-Method-Override',\n        'X-HTTP-Method',\n        'X-Method-Override',\n        // Hashed Next.js RSC headers set by the CloudFront Function below.\n        // See cf-function/cache-key.js for details.\n        'x-nextjs-cache-key',\n      ),\n      defaultTtl: Duration.seconds(0),\n      cookieBehavior: CacheCookieBehavior.all(),\n      enableAcceptEncodingBrotli: true,\n      enableAcceptEncodingGzip: true,\n    });\n\n    // CloudFront Function to hash Next.js RSC headers into a single cache key header.\n    // This prevents cache poisoning between HTML and RSC flight responses while\n    // staying within CloudFront's 10-header limit on Cache Policies.\n    const cacheKeyFunction = new CfFunction(this, 'CacheKeyFunction', {\n      runtime: FunctionRuntime.JS_2_0,\n      code: CfFunctionCode.fromFile({ filePath: path.join(__dirname, 'cf-function', 'cache-key.js') }),\n    });\n\n    const distribution = new Distribution(this, 'Resource', {\n      comment: `CloudFront for ${serviceName}`,\n      defaultBehavior: {\n        origin,\n        cachePolicy,\n        allowedMethods: AllowedMethods.ALLOW_ALL,\n        originRequestPolicy: OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,\n        functionAssociations: [\n          {\n            function: cacheKeyFunction,\n            eventType: FunctionEventType.VIEWER_REQUEST,\n          },\n        ],\n        edgeLambdas: [\n          {\n            functionVersion: signPayloadHandler.versionArn(this),\n            eventType: LambdaEdgeEventType.ORIGIN_REQUEST,\n            includeBody: true,\n          },\n        ],\n      },\n      // errorResponses: [{ httpStatus: 404, responsePagePath: '/', responseHttpStatus: 200 }],\n      logBucket: accessLogBucket,\n      logFilePrefix: `${serviceName}/`,\n\n      ...(hostedZone ? { certificate: certificate, domainNames: [domainName] } : {}),\n\n      minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2021,\n    });\n\n    // Starting October 2025, new function URLs require both lambda:InvokeFunctionUrl\n    // and lambda:InvokeFunction permissions for CloudFront OAC.\n    // CDK's FunctionUrlOrigin.withOriginAccessControl only adds lambda:InvokeFunctionUrl,\n    // so we explicitly add lambda:InvokeFunction here.\n    // See: https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html\n    new CfnPermission(this, 'InvokeFunctionPermission', {\n      action: 'lambda:InvokeFunction',\n      functionName: handler.functionArn,\n      principal: 'cloudfront.amazonaws.com',\n      sourceArn: `arn:${Aws.PARTITION}:cloudfront::${Aws.ACCOUNT_ID}:distribution/${distribution.distributionId}`,\n    });\n\n    if (hostedZone) {\n      new ARecord(this, 'Record', {\n        zone: hostedZone,\n        target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n        recordName: subDomain,\n      });\n    } else {\n      domainName = distribution.domainName;\n    }\n\n    // Invalidate CloudFront when Lambda function version changes\n    new AwsCustomResource(this, 'CloudFrontInvalidation', {\n      onUpdate: {\n        service: 'cloudfront',\n        action: 'createInvalidation',\n        parameters: {\n          DistributionId: distribution.distributionId,\n          InvalidationBatch: {\n            CallerReference: `${handler.currentVersion.version}`,\n            Paths: {\n              Quantity: 1,\n              Items: ['/*'],\n            },\n          },\n        },\n        physicalResourceId: PhysicalResourceId.of('invalidation'),\n      },\n      policy: AwsCustomResourcePolicy.fromSdkCalls({\n        resources: [distribution.distributionArn],\n      }),\n    });\n\n    this.url = `https://${domainName}`;\n    this.domainName = domainName;\n  }\n}\n"
  },
  {
    "path": "cdk/lib/constructs/database.ts",
    "content": "import { CfnOutput, Stack, Token } from 'aws-cdk-lib';\nimport * as ec2 from 'aws-cdk-lib/aws-ec2';\nimport * as logs from 'aws-cdk-lib/aws-logs';\nimport * as rds from 'aws-cdk-lib/aws-rds';\nimport * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';\nimport { Construct } from 'constructs';\n\ninterface DatabaseProps {\n  vpc: ec2.IVpc;\n}\n\nexport class Database extends Construct implements ec2.IConnectable {\n  readonly cluster: rds.DatabaseCluster;\n  readonly secret: secretsmanager.ISecret;\n  readonly connections: ec2.Connections;\n\n  constructor(scope: Construct, id: string, props: DatabaseProps) {\n    super(scope, id);\n\n    const vpc = props.vpc;\n\n    const engine = rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_16_6 });\n    const cluster = new rds.DatabaseCluster(this, 'Cluster', {\n      engine,\n      writer: rds.ClusterInstance.serverlessV2('Writer', {\n        enablePerformanceInsights: true,\n        autoMinorVersionUpgrade: true,\n      }),\n      serverlessV2MinCapacity: 0,\n      vpc,\n      vpcSubnets: vpc.selectSubnets({ subnets: vpc.isolatedSubnets.concat(vpc.privateSubnets) }),\n      storageEncrypted: true,\n      // Exclude some more special characters from password string to avoid from URI encoding issue\n      // see: https://www.prisma.io/docs/orm/reference/connection-urls#special-characters\n      credentials: rds.Credentials.fromUsername(engine.defaultUsername ?? 'admin', {\n        excludeCharacters: ' %+~`#$&*()|[]{}:;<>?!\\'/@\"\\\\,=^',\n      }),\n      enableDataApi: true,\n      cloudwatchLogsExports: ['postgresql'],\n      cloudwatchLogsRetention: logs.RetentionDays.ONE_WEEK,\n      parameterGroup: new rds.ParameterGroup(this, 'ParameterGroup', {\n        engine,\n        parameters: {\n          // Close idle connection after 60 seconds for Aurora auto-pause\n          idle_session_timeout: '60000',\n          log_connections: '1',\n          log_disconnections: '1',\n        },\n      }),\n    });\n\n    this.cluster = cluster;\n    this.secret = cluster.secret!;\n    this.connections = this.cluster.connections;\n\n    const host = new ec2.BastionHostLinux(this, 'BastionHost', {\n      vpc,\n      machineImage: ec2.MachineImage.latestAmazonLinux2023({ cpuType: ec2.AmazonLinuxCpuType.ARM_64 }),\n      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.NANO),\n      blockDevices: [\n        {\n          deviceName: '/dev/sdf',\n          volume: ec2.BlockDeviceVolume.ebs(8, {\n            encrypted: true,\n          }),\n        },\n      ],\n    });\n    this.connections.allowDefaultPortFrom(host);\n\n    new CfnOutput(this, 'PortForwardCommand', {\n      value: `aws ssm start-session --region ${Stack.of(this).region} --target ${\n        host.instanceId\n      } --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{\"portNumber\":[\"${\n        cluster.clusterEndpoint.port\n      }\"], \"localPortNumber\":[\"5433\"], \"host\": [\"${cluster.clusterEndpoint.hostname}\"]}'`,\n    });\n    new CfnOutput(this, 'DatabaseSecretsCommand', {\n      value: `aws secretsmanager get-secret-value --secret-id ${cluster.secret!.secretName} --region ${\n        Stack.of(this).region\n      }`,\n    });\n  }\n\n  public getConnectionInfo() {\n    return {\n      // We use direct reference for host and port because using only secret here results in failure of refreshing values.\n      // Also refer to: https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/369\n      host: this.cluster.clusterEndpoint.hostname,\n      port: Token.asString(this.cluster.clusterEndpoint.port),\n      engine: this.secret.secretValueFromJson('engine').unsafeUnwrap(),\n      username: this.secret.secretValueFromJson('username').unsafeUnwrap(),\n      password: this.secret.secretValueFromJson('password').unsafeUnwrap(),\n    };\n  }\n\n  public getLambdaEnvironment(databaseName: string) {\n    const conn = this.getConnectionInfo();\n    // connection_limit=1: Each Lambda instance handles one request at a time\n    // connect_timeout=30: Aurora Serverless v2 auto-pause resume takes ~15s (longer after 24h+ pause)\n    // https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2-auto-pause.html\n    const option = '?connection_limit=1&connect_timeout=30';\n    return {\n      DATABASE_HOST: conn.host,\n      DATABASE_NAME: databaseName,\n      DATABASE_USER: conn.username,\n      DATABASE_PASSWORD: conn.password,\n      DATABASE_ENGINE: conn.engine,\n      DATABASE_PORT: conn.port,\n      DATABASE_OPTION: option,\n      DATABASE_URL: `${conn.engine}://${conn.username}:${conn.password}@${conn.host}:${conn.port}/${databaseName}${option}`,\n    };\n  }\n}\n"
  },
  {
    "path": "cdk/lib/constructs/event-bus/handler.mjs",
    "content": "import { util } from '@aws-appsync/utils';\n\n/**\n * Allow subscription only for the channels that\n * 1. begin with /public\n * 2. begin with /user/<userId>\n * https://docs.aws.amazon.com/appsync/latest/eventapi/channel-namespace-handlers.html\n */\nexport function onSubscribe(ctx) {\n  if (ctx.info.channel.path.startsWith(`/event-bus/public`)) {\n    return;\n  }\n  if (ctx.info.channel.path.startsWith(`/event-bus/user/${ctx.identity.username}`)) {\n    return;\n  }\n  console.log(`user ${ctx.identity.username} tried connecting to wrong channel: ${ctx.channel}`);\n  util.unauthorized();\n}\n"
  },
  {
    "path": "cdk/lib/constructs/event-bus/index.ts",
    "content": "import { Construct } from 'constructs';\nimport * as appsync from 'aws-cdk-lib/aws-appsync';\nimport { CfnOutput, CfnResource, Names, Stack } from 'aws-cdk-lib';\nimport { IUserPool } from 'aws-cdk-lib/aws-cognito';\nimport { join } from 'path';\n\nexport interface EventBusProps {}\n\nexport class EventBus extends Construct {\n  public readonly httpEndpoint: string;\n  public readonly api: appsync.EventApi;\n\n  private userPoolCount = 0;\n\n  constructor(scope: Construct, id: string, props: EventBusProps) {\n    super(scope, id);\n\n    const api = new appsync.EventApi(this, 'Api', {\n      apiName: Names.uniqueResourceName(this, { maxLength: 30 }),\n      authorizationConfig: {\n        authProviders: [\n          {\n            authorizationType: appsync.AppSyncAuthorizationType.IAM,\n          },\n        ],\n        connectionAuthModeTypes: [appsync.AppSyncAuthorizationType.IAM],\n        defaultPublishAuthModeTypes: [appsync.AppSyncAuthorizationType.IAM],\n        defaultSubscribeAuthModeTypes: [appsync.AppSyncAuthorizationType.IAM],\n      },\n    });\n\n    new appsync.ChannelNamespace(this, 'Namespace', {\n      api,\n      channelNamespaceName: 'event-bus',\n      code: appsync.Code.fromAsset(join(__dirname, 'handler.mjs')),\n    });\n\n    this.httpEndpoint = `https://${api.httpDns}`;\n    this.api = api;\n\n    new CfnOutput(this, 'HttpEndpoint', { value: this.httpEndpoint });\n  }\n\n  public addUserPoolProvider(userPool: IUserPool) {\n    if (this.userPoolCount == 0) {\n      (this.api.node.defaultChild as CfnResource).addPropertyOverride('EventConfig.ConnectionAuthModes.1', {\n        AuthType: 'AMAZON_COGNITO_USER_POOLS',\n      });\n      (this.api.node.defaultChild as CfnResource).addPropertyOverride('EventConfig.DefaultSubscribeAuthModes.1', {\n        AuthType: 'AMAZON_COGNITO_USER_POOLS',\n      });\n    }\n\n    this.userPoolCount += 1;\n    (this.api.node.defaultChild as CfnResource).addPropertyOverride(`EventConfig.AuthProviders.${this.userPoolCount}`, {\n      AuthType: 'AMAZON_COGNITO_USER_POOLS',\n      CognitoConfig: {\n        AwsRegion: Stack.of(this).region,\n        UserPoolId: userPool.userPoolId,\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "cdk/lib/constructs/webapp.ts",
    "content": "import { IgnoreMode, Duration, CfnOutput, Stack, RemovalPolicy } from 'aws-cdk-lib';\nimport { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';\nimport { Platform } from 'aws-cdk-lib/aws-ecr-assets';\nimport { DockerImageFunction, DockerImageCode, Architecture } from 'aws-cdk-lib/aws-lambda';\nimport { Construct } from 'constructs';\nimport { readFileSync } from 'fs';\nimport { CloudFrontLambdaFunctionUrlService } from './cf-lambda-furl-service/service';\nimport { IHostedZone } from 'aws-cdk-lib/aws-route53';\nimport { Bucket } from 'aws-cdk-lib/aws-s3';\nimport { Database } from './database';\nimport { EdgeFunction } from './cf-lambda-furl-service/edge-function';\nimport { ICertificate } from 'aws-cdk-lib/aws-certificatemanager';\nimport { Auth } from './auth/';\nimport { ContainerImageBuild } from 'deploy-time-build';\nimport { join } from 'path';\nimport { EventBus } from './event-bus/';\nimport { AsyncJob } from './async-job';\nimport { Trigger } from 'aws-cdk-lib/triggers';\nimport { StringParameter } from 'aws-cdk-lib/aws-ssm';\nimport { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';\n\nexport interface WebappProps {\n  database: Database;\n  signPayloadHandler: EdgeFunction;\n  accessLogBucket: Bucket;\n  auth: Auth;\n  eventBus: EventBus;\n  asyncJob: AsyncJob;\n\n  /**\n   * Route 53 hosted zone for custom domain.\n   *\n   * @default No custom domain. The webapp will use CloudFront's default domain (e.g., d1234567890.cloudfront.net).\n   */\n  hostedZone?: IHostedZone;\n  /**\n   * ACM certificate for custom domain (must be in us-east-1 for CloudFront).\n   *\n   * @default No custom domain.\n   */\n  certificate?: ICertificate;\n  /**\n   * Subdomain name for the webapp. If not specified, the root domain will be used.\n   *\n   * @default Use root domain\n   */\n  subDomain?: string;\n}\n\nexport class Webapp extends Construct {\n  public readonly baseUrl: string;\n\n  constructor(scope: Construct, id: string, props: WebappProps) {\n    super(scope, id);\n\n    const { database, hostedZone, auth, subDomain, eventBus, asyncJob } = props;\n\n    // Use ContainerImageBuild to inject deploy-time values in the build environment\n    const image = new ContainerImageBuild(this, 'Build', {\n      directory: join('..', 'webapp'),\n      platform: Platform.LINUX_ARM64,\n      ignoreMode: IgnoreMode.DOCKER,\n      exclude: readFileSync(join('..', 'webapp', '.dockerignore'))\n        .toString()\n        .split('\\n'),\n      tagPrefix: 'webapp-starter-',\n      buildArgs: {\n        ALLOWED_ORIGIN_HOST: hostedZone ? `*.${hostedZone.zoneName}` : '*.cloudfront.net',\n        SKIP_TS_BUILD: 'true',\n        NEXT_PUBLIC_EVENT_HTTP_ENDPOINT: eventBus.httpEndpoint,\n        NEXT_PUBLIC_AWS_REGION: Stack.of(this).region,\n      },\n    });\n\n    const handler = new DockerImageFunction(this, 'Handler', {\n      code: image.toLambdaDockerImageCode(),\n      timeout: Duration.minutes(3),\n      environment: {\n        ...database.getLambdaEnvironment('main'),\n        COGNITO_DOMAIN: auth.domainName,\n        USER_POOL_ID: auth.userPool.userPoolId,\n        USER_POOL_CLIENT_ID: auth.client.userPoolClientId,\n        ASYNC_JOB_HANDLER_ARN: asyncJob.handler.functionArn,\n      },\n      vpc: database.cluster.vpc,\n      memorySize: 1024,\n      architecture: Architecture.ARM_64,\n      logGroup: new LogGroup(this, 'HandlerLogs', {\n        retention: RetentionDays.ONE_WEEK,\n        removalPolicy: RemovalPolicy.DESTROY,\n      }),\n    });\n    handler.connections.allowToDefaultPort(database);\n    asyncJob.handler.grantInvoke(handler);\n\n    const service = new CloudFrontLambdaFunctionUrlService(this, 'Resource', {\n      subDomain,\n      handler,\n      serviceName: 'Webapp',\n      hostedZone,\n      certificate: props.certificate,\n      accessLogBucket: props.accessLogBucket,\n      signPayloadHandler: props.signPayloadHandler,\n    });\n    this.baseUrl = service.url;\n\n    if (hostedZone) {\n      auth.addAllowedCallbackUrls(\n        `http://localhost:3010/api/auth/sign-in-callback`,\n        `http://localhost:3010/api/auth/sign-out-callback`,\n      );\n      auth.addAllowedCallbackUrls(\n        `${this.baseUrl}/api/auth/sign-in-callback`,\n        `${this.baseUrl}/api/auth/sign-out-callback`,\n      );\n      handler.addEnvironment('AMPLIFY_APP_ORIGIN', service.url);\n    } else {\n      auth.updateAllowedCallbackUrls(\n        [`${this.baseUrl}/api/auth/sign-in-callback`, `http://localhost:3010/api/auth/sign-in-callback`],\n        [`${this.baseUrl}/api/auth/sign-out-callback`, `http://localhost:3010/api/auth/sign-out-callback`],\n      );\n\n      const originSourceParameter = new StringParameter(this, 'OriginSourceParameter', {\n        stringValue: 'dummy',\n      });\n      originSourceParameter.grantRead(handler);\n      handler.addEnvironment('AMPLIFY_APP_ORIGIN_SOURCE_PARAMETER', originSourceParameter.parameterName);\n\n      // We need to pass AMPLIFY_APP_ORIGIN environment variable for callback URL,\n      // but we cannot know CloudFront domain before deploying Lambda function.\n      // To avoid the circular dependency, we fetch the domain name on runtime.\n      new AwsCustomResource(this, 'UpdateAmplifyOriginSourceParameter', {\n        onUpdate: {\n          service: 'ssm',\n          action: 'putParameter',\n          parameters: {\n            Name: originSourceParameter.parameterName,\n            Value: service.url,\n            Overwrite: true,\n          },\n          physicalResourceId: PhysicalResourceId.of(originSourceParameter.parameterName),\n        },\n        policy: AwsCustomResourcePolicy.fromSdkCalls({\n          resources: [originSourceParameter.parameterArn],\n        }),\n      });\n    }\n\n    const migrationRunner = new DockerImageFunction(this, 'MigrationRunner', {\n      code: DockerImageCode.fromImageAsset(join('..', 'webapp'), {\n        platform: Platform.LINUX_ARM64,\n        cmd: ['migration-runner.handler'],\n        file: 'job.Dockerfile',\n      }),\n      architecture: Architecture.ARM_64,\n      timeout: Duration.minutes(5),\n      environment: {\n        ...database.getLambdaEnvironment('main'),\n      },\n      vpc: database.cluster.vpc,\n      memorySize: 256,\n      logGroup: new LogGroup(this, 'MigrationRunnerLogs', {\n        retention: RetentionDays.ONE_WEEK,\n        removalPolicy: RemovalPolicy.DESTROY,\n      }),\n    });\n    migrationRunner.connections.allowToDefaultPort(database);\n\n    // Run database migration during CDK deployment\n    // The Trigger construct automatically invokes the migration runner with default payload (command: 'deploy')\n    // To manually run migrations with different commands (e.g., 'force'), use the AWS CLI command shown in the CDK output below\n    const trigger = new Trigger(this, 'MigrationTrigger', {\n      handler: migrationRunner,\n    });\n    // make sure migration is executed after the database cluster is available.\n    trigger.node.addDependency(database.cluster);\n\n    // Output migration-related information for manual invocation\n    // Available commands: \"deploy\" (default), \"force\" (with --accept-data-loss)\n    // Example: aws lambda invoke --function-name <FUNCTION_NAME> --payload '{\"command\":\"force\"}' --cli-binary-format raw-in-base64-out /dev/stdout\n    new CfnOutput(Stack.of(this), 'MigrationFunctionName', { value: migrationRunner.functionName });\n    new CfnOutput(Stack.of(this), 'MigrationCommand', {\n      value: `aws lambda invoke --function-name ${migrationRunner.functionName} --payload '{\"command\":\"deploy\"}' --cli-binary-format raw-in-base64-out /dev/stdout`,\n    });\n  }\n}\n"
  },
  {
    "path": "cdk/lib/main-stack.ts",
    "content": "import { CfnOutput, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';\nimport { BlockPublicAccess, Bucket, BucketEncryption, ObjectOwnership } from 'aws-cdk-lib/aws-s3';\nimport { Construct } from 'constructs';\nimport { AsyncJob } from './constructs/async-job';\nimport { Auth } from './constructs/auth/';\nimport { Database } from './constructs/database';\nimport { InstanceClass, InstanceSize, InstanceType, NatProvider, UserData, Vpc } from 'aws-cdk-lib/aws-ec2';\nimport { HostedZone } from 'aws-cdk-lib/aws-route53';\nimport { ICertificate } from 'aws-cdk-lib/aws-certificatemanager';\nimport { Webapp } from './constructs/webapp';\nimport { EdgeFunction } from './constructs/cf-lambda-furl-service/edge-function';\nimport { EventBus } from './constructs/event-bus/';\n\ninterface MainStackProps extends StackProps {\n  readonly signPayloadHandler: EdgeFunction;\n\n  /**\n   * Custom domain name for the webapp and Cognito.\n   *\n   * @default No custom domain. CloudFront and Cognito will use their default domains.\n   */\n  readonly domainName?: string;\n  /**\n   * ACM certificate for custom domain (must be in us-east-1).\n   *\n   * @default No custom domain.\n   */\n  readonly sharedCertificate?: ICertificate;\n\n  /**\n   * Use a NAT instance instead of NAT Gateways for cost optimization.\n   *\n   * @default true\n   */\n  readonly useNatInstance?: boolean;\n}\n\nexport class MainStack extends Stack {\n  constructor(scope: Construct, id: string, props: MainStackProps) {\n    super(scope, id, { description: 'Serverless fullstack webapp stack (uksb-1tupboc47)', ...props });\n\n    const { useNatInstance = true } = props;\n\n    const hostedZone = props.domainName\n      ? HostedZone.fromLookup(this, 'HostedZone', {\n          domainName: props.domainName,\n        })\n      : undefined;\n\n    const accessLogBucket = new Bucket(this, 'AccessLogBucket', {\n      encryption: BucketEncryption.S3_MANAGED,\n      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n      enforceSSL: true,\n      removalPolicy: RemovalPolicy.DESTROY,\n      objectOwnership: ObjectOwnership.OBJECT_WRITER,\n      autoDeleteObjects: true,\n    });\n\n    // Custom user data for NAT instance to support Amazon Linux 2023.\n    // CDK's default user data uses `route` command which requires net-tools package,\n    // but AL2023 doesn't have net-tools pre-installed. We use `ip route` instead.\n    // Retry yum install to handle RPM lock conflicts during boot.\n    const natUserData = UserData.forLinux();\n    natUserData.addCommands(\n      // Retry yum install up to 5 times with 10 second intervals\n      'for i in {1..5}; do yum install iptables-services -y && break || sleep 10; done',\n      'systemctl enable iptables',\n      'systemctl start iptables',\n      'echo \"net.ipv4.ip_forward=1\" > /etc/sysctl.d/custom-ip-forwarding.conf',\n      'sysctl -p /etc/sysctl.d/custom-ip-forwarding.conf',\n      \"IFACE=$(ip route show default | awk '{print $5}')\",\n      '/sbin/iptables -t nat -A POSTROUTING -o $IFACE -j MASQUERADE',\n      '/sbin/iptables -F FORWARD',\n      'service iptables save',\n    );\n\n    const vpc = new Vpc(this, `Vpc`, {\n      ...(useNatInstance\n        ? {\n            natGatewayProvider: NatProvider.instanceV2({\n              instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.NANO),\n              associatePublicIpAddress: true,\n              userData: natUserData,\n            }),\n            natGateways: 1,\n          }\n        : {}),\n    });\n\n    const database = new Database(this, 'Database', { vpc });\n\n    const auth = new Auth(this, 'Auth', {\n      hostedZone,\n      sharedCertificate: props.sharedCertificate,\n    });\n\n    const eventBus = new EventBus(this, 'EventBus', {});\n    eventBus.addUserPoolProvider(auth.userPool);\n\n    const asyncJob = new AsyncJob(this, 'AsyncJob', { database: database, eventBus });\n\n    const webapp = new Webapp(this, 'Webapp', {\n      database,\n      hostedZone,\n      certificate: props.sharedCertificate,\n      signPayloadHandler: props.signPayloadHandler,\n      accessLogBucket,\n      auth,\n      eventBus,\n      asyncJob,\n      subDomain: 'web',\n    });\n\n    new CfnOutput(this, 'FrontendDomainName', {\n      value: webapp.baseUrl,\n    });\n  }\n}\n"
  },
  {
    "path": "cdk/lib/us-east-1-stack.ts",
    "content": "import * as cdk from 'aws-cdk-lib';\nimport { Certificate, CertificateValidation, ICertificate } from 'aws-cdk-lib/aws-certificatemanager';\nimport { ARecord, HostedZone, RecordTarget } from 'aws-cdk-lib/aws-route53';\nimport { Construct } from 'constructs';\nimport { EdgeFunction } from './constructs/cf-lambda-furl-service/edge-function';\nimport { join } from 'path';\n\ninterface UsEast1StackProps extends cdk.StackProps {\n  /**\n   * Custom domain name for the webapp and Cognito.\n   *\n   * @default No custom domain. CloudFront and Cognito will use their default domains.\n   */\n  domainName?: string;\n}\n\nexport class UsEast1Stack extends cdk.Stack {\n  /**\n   * the ACM certificate for CloudFront (it must be deployed in us-east-1).\n   * undefined if domainName is not set.\n   */\n  public readonly certificate: ICertificate | undefined = undefined;\n  /**\n   * the signer L@E function (it must be deployed in us-east-1).\n   */\n  public readonly signPayloadHandler: EdgeFunction;\n\n  constructor(scope: Construct, id: string, props: UsEast1StackProps) {\n    super(scope, id, props);\n\n    if (props.domainName) {\n      const hostedZone = HostedZone.fromLookup(this, 'HostedZone', {\n        domainName: props.domainName,\n      });\n\n      // cognito requires A record for Hosted UI custom domain\n      // https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html#cognito-user-pools-add-custom-domain-adding\n      // > Its parent domain must have a valid DNS A record. You can assign any value to this record.\n      new ARecord(this, 'Record', {\n        zone: hostedZone,\n        target: RecordTarget.fromIpAddresses('8.8.8.8'),\n      });\n\n      const cert = new Certificate(this, 'CertificateV2', {\n        domainName: `*.${hostedZone.zoneName}`,\n        validation: CertificateValidation.fromDns(hostedZone),\n        subjectAlternativeNames: [hostedZone.zoneName],\n      });\n      this.certificate = cert;\n    }\n\n    const signPayloadHandler = new EdgeFunction(this, 'SignPayloadHandler', {\n      entryPath: join(__dirname, 'constructs', 'cf-lambda-furl-service', 'lambda', 'sign-payload.ts'),\n    });\n\n    this.signPayloadHandler = signPayloadHandler;\n  }\n}\n"
  },
  {
    "path": "cdk/package.json",
    "content": "{\n  \"name\": \"@aws-samples/serverless-fullstack-webapp-starter-kit\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"watch\": \"tsc -w\",\n    \"test\": \"jest\",\n    \"format\": \"prettier --write './**/*.{ts,tsx,mjs,mts}'\",\n    \"format:check\": \"prettier --check './**/*.{ts,tsx,mjs,mts}'\",\n    \"cdk\": \"cdk\"\n  },\n  \"devDependencies\": {\n    \"@types/jest\": \"^27.5.0\",\n    \"@types/node\": \"^22.14.1\",\n    \"@types/prettier\": \"2.6.0\",\n    \"aws-cdk\": \"^2.1007.0\",\n    \"esbuild\": \"^0.25.4\",\n    \"jest\": \"^30.1.3\",\n    \"prettier\": \"^3.5.3\",\n    \"ts-jest\": \"^29.4.4\",\n    \"ts-node\": \"^10.7.0\",\n    \"typescript\": \"^5.9.2\"\n  },\n  \"dependencies\": {\n    \"@aws-appsync/utils\": \"^1.12.0\",\n    \"@aws-sdk/client-cognito-identity-provider\": \"^3.987.0\",\n    \"@types/aws-lambda\": \"^8.10.149\",\n    \"aws-cdk-lib\": \"^2.189.1\",\n    \"cdk-nag\": \"^2.14.29\",\n    \"constructs\": \"^10.0.0\",\n    \"deploy-time-build\": \"^0.3.32\",\n    \"source-map-support\": \"^0.5.21\"\n  }\n}\n"
  },
  {
    "path": "cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit-without-domain.test.ts.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Snapshot test 1`] = `\n{\n  \"Parameters\": {\n    \"BootstrapVersion\": {\n      \"Default\": \"/cdk-bootstrap/hnb659fds/version\",\n      \"Description\": \"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]\",\n      \"Type\": \"AWS::SSM::Parameter::Value<String>\",\n    },\n  },\n  \"Resources\": {\n    \"CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A\": {\n      \"DependsOn\": [\n        \"CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-east-1\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"__entrypoint__.handler\",\n        \"MemorySize\": 128,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Sub\": \"arn:\\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n          },\n        ],\n        \"Policies\": [\n          {\n            \"PolicyDocument\": {\n              \"Statement\": [\n                {\n                  \"Action\": [\n                    \"ssm:DeleteParameters\",\n                    \"ssm:ListTagsForResource\",\n                    \"ssm:GetParameters\",\n                    \"ssm:PutParameter\",\n                  ],\n                  \"Effect\": \"Allow\",\n                  \"Resource\": [\n                    {\n                      \"Fn::Join\": [\n                        \"\",\n                        [\n                          \"arn:\",\n                          {\n                            \"Ref\": \"AWS::Partition\",\n                          },\n                          \":ssm:us-west-2:123456789012:parameter/cdk/exports/*\",\n                        ],\n                      ],\n                    },\n                  ],\n                },\n              ],\n              \"Version\": \"2012-10-17\",\n            },\n            \"PolicyName\": \"Inline\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"ExportsWriteruswest209BD44F0A7CF058B\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A\",\n            \"Arn\",\n          ],\n        },\n        \"WriterProps\": {\n          \"exports\": {\n            \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\": {\n              \"Ref\": \"SignPayloadHandlerFunctionVersionF9FE430A\",\n            },\n          },\n          \"region\": \"us-west-2\",\n        },\n      },\n      \"Type\": \"Custom::CrossRegionExportWriter\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"SignPayloadHandlerCFEAA14C\": {\n      \"DependsOn\": [\n        \"SignPayloadHandlerServiceRole29261232\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-east-1\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"SignPayloadHandlerServiceRole29261232\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"SignPayloadHandlerCurrentVersionREDACTED\": {\n      \"Properties\": {\n        \"FunctionName\": {\n          \"Ref\": \"SignPayloadHandlerCFEAA14C\",\n        },\n      },\n      \"Type\": \"AWS::Lambda::Version\",\n    },\n    \"SignPayloadHandlerFunctionVersionF9FE430A\": {\n      \"Properties\": {\n        \"Type\": \"String\",\n        \"Value\": {\n          \"Ref\": \"SignPayloadHandlerCurrentVersionREDACTED\",\n        },\n      },\n      \"Type\": \"AWS::SSM::Parameter\",\n    },\n    \"SignPayloadHandlerServiceRole29261232\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"edgelambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n  },\n  \"Rules\": {\n    \"CheckBootstrapVersion\": {\n      \"Assertions\": [\n        {\n          \"Assert\": {\n            \"Fn::Not\": [\n              {\n                \"Fn::Contains\": [\n                  [\n                    \"1\",\n                    \"2\",\n                    \"3\",\n                    \"4\",\n                    \"5\",\n                  ],\n                  {\n                    \"Ref\": \"BootstrapVersion\",\n                  },\n                ],\n              },\n            ],\n          },\n          \"AssertDescription\": \"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.\",\n        },\n      ],\n    },\n  },\n}\n`;\n\nexports[`Snapshot test 2`] = `\n{\n  \"Description\": \"Serverless fullstack webapp stack (uksb-1tupboc47)\",\n  \"Outputs\": {\n    \"AsyncJobHandlerArnCA46B385\": {\n      \"Value\": {\n        \"Fn::GetAtt\": [\n          \"AsyncJobHandler438266BD\",\n          \"Arn\",\n        ],\n      },\n    },\n    \"AuthUserPoolClientId8216BF9A\": {\n      \"Value\": {\n        \"Ref\": \"AuthUserPoolClientC635291F\",\n      },\n    },\n    \"AuthUserPoolDomainName8D4A2606\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            {\n              \"Fn::GetAtt\": [\n                \"AuthDomainPrefixE1742B23\",\n                \"generated\",\n              ],\n            },\n            \".auth.us-west-2.amazoncognito.com\",\n          ],\n        ],\n      },\n    },\n    \"AuthUserPoolIdC0605E59\": {\n      \"Value\": {\n        \"Ref\": \"AuthUserPool8115E87F\",\n      },\n    },\n    \"DatabaseBastionHostBastionHostId1600F37C\": {\n      \"Description\": \"Instance ID of the bastion host. Use this to connect via SSM Session Manager\",\n      \"Value\": {\n        \"Ref\": \"DatabaseBastionHost4C4FAD9C\",\n      },\n    },\n    \"DatabaseDatabaseSecretsCommandF4A622EB\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"aws secretsmanager get-secret-value --secret-id \",\n            {\n              \"Fn::Join\": [\n                \"-\",\n                [\n                  {\n                    \"Fn::Select\": [\n                      0,\n                      {\n                        \"Fn::Split\": [\n                          \"-\",\n                          {\n                            \"Fn::Select\": [\n                              6,\n                              {\n                                \"Fn::Split\": [\n                                  \":\",\n                                  {\n                                    \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n                                  },\n                                ],\n                              },\n                            ],\n                          },\n                        ],\n                      },\n                    ],\n                  },\n                  {\n                    \"Fn::Select\": [\n                      1,\n                      {\n                        \"Fn::Split\": [\n                          \"-\",\n                          {\n                            \"Fn::Select\": [\n                              6,\n                              {\n                                \"Fn::Split\": [\n                                  \":\",\n                                  {\n                                    \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n                                  },\n                                ],\n                              },\n                            ],\n                          },\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              ],\n            },\n            \" --region us-west-2\",\n          ],\n        ],\n      },\n    },\n    \"DatabasePortForwardCommandC3718B89\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"aws ssm start-session --region us-west-2 --target \",\n            {\n              \"Ref\": \"DatabaseBastionHost4C4FAD9C\",\n            },\n            \" --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{\"portNumber\":[\"\",\n            {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Port\",\n              ],\n            },\n            \"\"], \"localPortNumber\":[\"5433\"], \"host\": [\"\",\n            {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Address\",\n              ],\n            },\n            \"\"]}'\",\n          ],\n        ],\n      },\n    },\n    \"EventBusHttpEndpoint1C68A807\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"https://\",\n            {\n              \"Fn::GetAtt\": [\n                \"EventBusApi6E8C7C94\",\n                \"Dns.Http\",\n              ],\n            },\n          ],\n        ],\n      },\n    },\n    \"FrontendDomainName\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"https://\",\n            {\n              \"Fn::GetAtt\": [\n                \"Webapp107041BD\",\n                \"DomainName\",\n              ],\n            },\n          ],\n        ],\n      },\n    },\n    \"MigrationCommand\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"aws lambda invoke --function-name \",\n            {\n              \"Ref\": \"WebappMigrationRunnerAC67C012\",\n            },\n            \" --payload '{\"command\":\"deploy\"}' --cli-binary-format raw-in-base64-out /dev/stdout\",\n          ],\n        ],\n      },\n    },\n    \"MigrationFunctionName\": {\n      \"Value\": {\n        \"Ref\": \"WebappMigrationRunnerAC67C012\",\n      },\n    },\n  },\n  \"Parameters\": {\n    \"BootstrapVersion\": {\n      \"Default\": \"/cdk-bootstrap/hnb659fds/version\",\n      \"Description\": \"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]\",\n      \"Type\": \"AWS::SSM::Parameter::Value<String>\",\n    },\n    \"SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61arm64C96584B6F00A464EAD1953AFF4B05118Parameter\": {\n      \"Default\": \"/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64\",\n      \"Type\": \"AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>\",\n    },\n  },\n  \"Resources\": {\n    \"AWS679f53fac002430cb0da5b7982bd22872D164C4C\": {\n      \"DependsOn\": [\n        \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 120,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91\": {\n      \"DependsOn\": [\n        \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"__entrypoint__.handler\",\n        \"MemorySize\": 128,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Sub\": \"arn:\\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n          },\n        ],\n        \"Policies\": [\n          {\n            \"PolicyDocument\": {\n              \"Statement\": [\n                {\n                  \"Action\": [\n                    \"lambda:InvokeFunction\",\n                  ],\n                  \"Effect\": \"Allow\",\n                  \"Resource\": [\n                    {\n                      \"Fn::Join\": [\n                        \"\",\n                        [\n                          {\n                            \"Fn::GetAtt\": [\n                              \"WebappMigrationRunnerAC67C012\",\n                              \"Arn\",\n                            ],\n                          },\n                          \":*\",\n                        ],\n                      ],\n                    },\n                  ],\n                },\n              ],\n              \"Version\": \"2012-10-17\",\n            },\n            \"PolicyName\": \"Inline\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"AccessLogBucketAutoDeleteObjectsCustomResource01AB31E8\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"AccessLogBucketPolicyF52D2D01\",\n      ],\n      \"Properties\": {\n        \"BucketName\": {\n          \"Ref\": \"AccessLogBucketDA470295\",\n        },\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F\",\n            \"Arn\",\n          ],\n        },\n      },\n      \"Type\": \"Custom::S3AutoDeleteObjects\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AccessLogBucketDA470295\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"BucketEncryption\": {\n          \"ServerSideEncryptionConfiguration\": [\n            {\n              \"ServerSideEncryptionByDefault\": {\n                \"SSEAlgorithm\": \"AES256\",\n              },\n            },\n          ],\n        },\n        \"OwnershipControls\": {\n          \"Rules\": [\n            {\n              \"ObjectOwnership\": \"ObjectWriter\",\n            },\n          ],\n        },\n        \"PublicAccessBlockConfiguration\": {\n          \"BlockPublicAcls\": true,\n          \"BlockPublicPolicy\": true,\n          \"IgnorePublicAcls\": true,\n          \"RestrictPublicBuckets\": true,\n        },\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:auto-delete-objects\",\n            \"Value\": \"true\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::S3::Bucket\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AccessLogBucketPolicyF52D2D01\": {\n      \"Properties\": {\n        \"Bucket\": {\n          \"Ref\": \"AccessLogBucketDA470295\",\n        },\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"s3:*\",\n              \"Condition\": {\n                \"Bool\": {\n                  \"aws:SecureTransport\": \"false\",\n                },\n              },\n              \"Effect\": \"Deny\",\n              \"Principal\": {\n                \"AWS\": \"*\",\n              },\n              \"Resource\": [\n                {\n                  \"Fn::GetAtt\": [\n                    \"AccessLogBucketDA470295\",\n                    \"Arn\",\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      {\n                        \"Fn::GetAtt\": [\n                          \"AccessLogBucketDA470295\",\n                          \"Arn\",\n                        ],\n                      },\n                      \"/*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n            {\n              \"Action\": [\n                \"s3:PutBucketPolicy\",\n                \"s3:GetBucket*\",\n                \"s3:List*\",\n                \"s3:DeleteObject*\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"AWS\": {\n                  \"Fn::GetAtt\": [\n                    \"CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092\",\n                    \"Arn\",\n                  ],\n                },\n              },\n              \"Resource\": [\n                {\n                  \"Fn::GetAtt\": [\n                    \"AccessLogBucketDA470295\",\n                    \"Arn\",\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      {\n                        \"Fn::GetAtt\": [\n                          \"AccessLogBucketDA470295\",\n                          \"Arn\",\n                        ],\n                      },\n                      \"/*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n      },\n      \"Type\": \"AWS::S3::BucketPolicy\",\n    },\n    \"AsyncJobHandler438266BD\": {\n      \"DependsOn\": [\n        \"AsyncJobHandlerServiceRoleDefaultPolicy0B2DEDB5\",\n        \"AsyncJobHandlerServiceRoleFE9F530F\",\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"Architectures\": [\n          \"arm64\",\n        ],\n        \"Code\": {\n          \"ImageUri\": {\n            \"Fn::Sub\": \"REDACTED\",\n          },\n        },\n        \"Environment\": {\n          \"Variables\": {\n            \"DATABASE_ENGINE\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_HOST\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Address\",\n              ],\n            },\n            \"DATABASE_NAME\": \"main\",\n            \"DATABASE_OPTION\": \"?connection_limit=1&connect_timeout=30\",\n            \"DATABASE_PASSWORD\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_PORT\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Port\",\n              ],\n            },\n            \"DATABASE_URL\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}://{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}:{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}@\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Address\",\n                    ],\n                  },\n                  \":\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Port\",\n                    ],\n                  },\n                  \"/main?connection_limit=1&connect_timeout=30\",\n                ],\n              ],\n            },\n            \"DATABASE_USER\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}\",\n                ],\n              ],\n            },\n            \"EVENT_HTTP_ENDPOINT\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"https://\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"EventBusApi6E8C7C94\",\n                      \"Dns.Http\",\n                    ],\n                  },\n                ],\n              ],\n            },\n          },\n        },\n        \"ImageConfig\": {\n          \"Command\": [\n            \"async-job-runner.handler\",\n          ],\n        },\n        \"LoggingConfig\": {\n          \"LogGroup\": {\n            \"Ref\": \"AsyncJobHandlerLogs20DFEE3E\",\n          },\n        },\n        \"MemorySize\": 256,\n        \"PackageType\": \"Image\",\n        \"ReservedConcurrentExecutions\": 1,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"AsyncJobHandlerServiceRoleFE9F530F\",\n            \"Arn\",\n          ],\n        },\n        \"Timeout\": 600,\n        \"VpcConfig\": {\n          \"SecurityGroupIds\": [\n            {\n              \"Fn::GetAtt\": [\n                \"AsyncJobHandlerSecurityGroupF59812E6\",\n                \"GroupId\",\n              ],\n            },\n          ],\n          \"SubnetIds\": [\n            {\n              \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n            },\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"AsyncJobHandlerLogs20DFEE3E\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"RetentionInDays\": 7,\n      },\n      \"Type\": \"AWS::Logs::LogGroup\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AsyncJobHandlerSecurityGroupF59812E6\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"GroupDescription\": \"Automatic security group for Lambda Function ServerlessWebappStarterKitStackAsyncJobHandlerF20B94EA\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"AsyncJobHandlerServiceRoleDefaultPolicy0B2DEDB5\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"appsync:EventPublish\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":appsync:us-west-2:123456789012:apis/\",\n                    {\n                      \"Fn::GetAtt\": [\n                        \"EventBusApi6E8C7C94\",\n                        \"ApiId\",\n                      ],\n                    },\n                    \"/channelNamespace/*\",\n                  ],\n                ],\n              },\n            },\n            {\n              \"Action\": [\n                \"translate:TranslateText\",\n                \"comprehend:DetectDominantLanguage\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": \"*\",\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"AsyncJobHandlerServiceRoleDefaultPolicy0B2DEDB5\",\n        \"Roles\": [\n          {\n            \"Ref\": \"AsyncJobHandlerServiceRoleFE9F530F\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"AsyncJobHandlerServiceRoleFE9F530F\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"AsyncJobSampleJob3C1EBA2C\": {\n      \"Properties\": {\n        \"FlexibleTimeWindow\": {\n          \"Mode\": \"OFF\",\n        },\n        \"ScheduleExpression\": \"cron(0 0 1 * ? *)\",\n        \"ScheduleExpressionTimezone\": \"Etc/UTC\",\n        \"State\": \"ENABLED\",\n        \"Target\": {\n          \"Arn\": {\n            \"Fn::GetAtt\": [\n              \"AsyncJobHandler438266BD\",\n              \"Arn\",\n            ],\n          },\n          \"Input\": \"{\"jobType\":\"SampleJob\"}\",\n          \"RetryPolicy\": {\n            \"MaximumEventAgeInSeconds\": 86400,\n            \"MaximumRetryAttempts\": 5,\n          },\n          \"RoleArn\": {\n            \"Fn::GetAtt\": [\n              \"SchedulerRoleForTarget44ece2CFC6840F\",\n              \"Arn\",\n            ],\n          },\n        },\n      },\n      \"Type\": \"AWS::Scheduler::Schedule\",\n    },\n    \"AuthBranding34BB87FD\": {\n      \"Properties\": {\n        \"ClientId\": {\n          \"Ref\": \"AuthUserPoolClientC635291F\",\n        },\n        \"UseCognitoProvidedValues\": true,\n        \"UserPoolId\": {\n          \"Ref\": \"AuthUserPool8115E87F\",\n        },\n      },\n      \"Type\": \"AWS::Cognito::ManagedLoginBranding\",\n    },\n    \"AuthDomainPrefixE1742B23\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"ServiceTimeout\": \"10\",\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"RandomStringGenerator11e9c903f11a4989833c985dddef5eb28C5103D0\",\n            \"Arn\",\n          ],\n        },\n        \"length\": 10,\n        \"prefix\": \"webapp-\",\n      },\n      \"Type\": \"Custom::RandomString\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AuthUpdateCallbackUrlsA55622E9\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"AuthUpdateCallbackUrlsCustomResourcePolicy14EEB23D\",\n      ],\n      \"Properties\": {\n        \"Create\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"@aws-sdk/client-cognito-identity-provider\",\"action\":\"updateUserPoolClient\",\"parameters\":{\"ClientId\":\"\",\n              {\n                \"Ref\": \"AuthUserPoolClientC635291F\",\n              },\n              \"\",\"UserPoolId\":\"\",\n              {\n                \"Ref\": \"AuthUserPool8115E87F\",\n              },\n              \"\",\"AllowedOAuthFlows\":[\"code\"],\"AllowedOAuthFlowsUserPoolClient\":true,\"AllowedOAuthScopes\":[\"profile\",\"phone\",\"email\",\"openid\",\"aws.cognito.signin.user.admin\"],\"ExplicitAuthFlows\":[\"ALLOW_USER_PASSWORD_AUTH\",\"ALLOW_USER_SRP_AUTH\",\"ALLOW_REFRESH_TOKEN_AUTH\"],\"CallbackURLs\":[\"https://\",\n              {\n                \"Fn::GetAtt\": [\n                  \"Webapp107041BD\",\n                  \"DomainName\",\n                ],\n              },\n              \"/api/auth/sign-in-callback\",\"http://localhost:3010/api/auth/sign-in-callback\"],\"LogoutURLs\":[\"https://\",\n              {\n                \"Fn::GetAtt\": [\n                  \"Webapp107041BD\",\n                  \"DomainName\",\n                ],\n              },\n              \"/api/auth/sign-out-callback\",\"http://localhost:3010/api/auth/sign-out-callback\"],\"SupportedIdentityProviders\":[\"COGNITO\"],\"TokenValidityUnits\":{\"IdToken\":\"minutes\"},\"IdTokenValidity\":1440},\"physicalResourceId\":{\"id\":\"\",\n              {\n                \"Ref\": \"AuthUserPool8115E87F\",\n              },\n              \"\"}}\",\n            ],\n          ],\n        },\n        \"InstallLatestAwsSdk\": true,\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"AWS679f53fac002430cb0da5b7982bd22872D164C4C\",\n            \"Arn\",\n          ],\n        },\n        \"Update\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"@aws-sdk/client-cognito-identity-provider\",\"action\":\"updateUserPoolClient\",\"parameters\":{\"ClientId\":\"\",\n              {\n                \"Ref\": \"AuthUserPoolClientC635291F\",\n              },\n              \"\",\"UserPoolId\":\"\",\n              {\n                \"Ref\": \"AuthUserPool8115E87F\",\n              },\n              \"\",\"AllowedOAuthFlows\":[\"code\"],\"AllowedOAuthFlowsUserPoolClient\":true,\"AllowedOAuthScopes\":[\"profile\",\"phone\",\"email\",\"openid\",\"aws.cognito.signin.user.admin\"],\"ExplicitAuthFlows\":[\"ALLOW_USER_PASSWORD_AUTH\",\"ALLOW_USER_SRP_AUTH\",\"ALLOW_REFRESH_TOKEN_AUTH\"],\"CallbackURLs\":[\"https://\",\n              {\n                \"Fn::GetAtt\": [\n                  \"Webapp107041BD\",\n                  \"DomainName\",\n                ],\n              },\n              \"/api/auth/sign-in-callback\",\"http://localhost:3010/api/auth/sign-in-callback\"],\"LogoutURLs\":[\"https://\",\n              {\n                \"Fn::GetAtt\": [\n                  \"Webapp107041BD\",\n                  \"DomainName\",\n                ],\n              },\n              \"/api/auth/sign-out-callback\",\"http://localhost:3010/api/auth/sign-out-callback\"],\"SupportedIdentityProviders\":[\"COGNITO\"],\"TokenValidityUnits\":{\"IdToken\":\"minutes\"},\"IdTokenValidity\":1440},\"physicalResourceId\":{\"id\":\"\",\n              {\n                \"Ref\": \"AuthUserPool8115E87F\",\n              },\n              \"\"}}\",\n            ],\n          ],\n        },\n      },\n      \"Type\": \"Custom::AWS\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AuthUpdateCallbackUrlsCustomResourcePolicy14EEB23D\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"cognito-idp:UpdateUserPoolClient\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::GetAtt\": [\n                  \"AuthUserPool8115E87F\",\n                  \"Arn\",\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"AuthUpdateCallbackUrlsCustomResourcePolicy14EEB23D\",\n        \"Roles\": [\n          {\n            \"Ref\": \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"AuthUserPool8115E87F\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"AccountRecoverySetting\": {\n          \"RecoveryMechanisms\": [\n            {\n              \"Name\": \"verified_phone_number\",\n              \"Priority\": 1,\n            },\n            {\n              \"Name\": \"verified_email\",\n              \"Priority\": 2,\n            },\n          ],\n        },\n        \"AdminCreateUserConfig\": {\n          \"AllowAdminCreateUserOnly\": true,\n        },\n        \"AutoVerifiedAttributes\": [\n          \"email\",\n        ],\n        \"EmailVerificationMessage\": \"The verification code to your new account is {####}\",\n        \"EmailVerificationSubject\": \"Verify your new account\",\n        \"Policies\": {\n          \"PasswordPolicy\": {\n            \"MinimumLength\": 8,\n            \"RequireNumbers\": true,\n            \"RequireSymbols\": true,\n            \"RequireUppercase\": true,\n          },\n        },\n        \"SmsVerificationMessage\": \"The verification code to your new account is {####}\",\n        \"UsernameAttributes\": [\n          \"email\",\n        ],\n        \"VerificationMessageTemplate\": {\n          \"DefaultEmailOption\": \"CONFIRM_WITH_CODE\",\n          \"EmailMessage\": \"The verification code to your new account is {####}\",\n          \"EmailSubject\": \"Verify your new account\",\n          \"SmsMessage\": \"The verification code to your new account is {####}\",\n        },\n      },\n      \"Type\": \"AWS::Cognito::UserPool\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AuthUserPoolClientC635291F\": {\n      \"Properties\": {\n        \"AllowedOAuthFlows\": [\n          \"code\",\n        ],\n        \"AllowedOAuthFlowsUserPoolClient\": true,\n        \"AllowedOAuthScopes\": [\n          \"profile\",\n          \"phone\",\n          \"email\",\n          \"openid\",\n          \"aws.cognito.signin.user.admin\",\n        ],\n        \"CallbackURLs\": [\n          \"http://localhost/dummy\",\n        ],\n        \"ExplicitAuthFlows\": [\n          \"ALLOW_USER_PASSWORD_AUTH\",\n          \"ALLOW_USER_SRP_AUTH\",\n          \"ALLOW_REFRESH_TOKEN_AUTH\",\n        ],\n        \"IdTokenValidity\": 1440,\n        \"LogoutURLs\": [\n          \"http://localhost/dummy\",\n        ],\n        \"SupportedIdentityProviders\": [\n          \"COGNITO\",\n        ],\n        \"TokenValidityUnits\": {\n          \"IdToken\": \"minutes\",\n        },\n        \"UserPoolId\": {\n          \"Ref\": \"AuthUserPool8115E87F\",\n        },\n      },\n      \"Type\": \"AWS::Cognito::UserPoolClient\",\n    },\n    \"AuthUserPoolCognitoDomainAD9D79E1\": {\n      \"Properties\": {\n        \"Domain\": {\n          \"Fn::GetAtt\": [\n            \"AuthDomainPrefixE1742B23\",\n            \"generated\",\n          ],\n        },\n        \"ManagedLoginVersion\": 2,\n        \"UserPoolId\": {\n          \"Ref\": \"AuthUserPool8115E87F\",\n        },\n      },\n      \"Type\": \"AWS::Cognito::UserPoolDomain\",\n    },\n    \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\": {\n      \"Properties\": {\n        \"Artifacts\": {\n          \"Type\": \"NO_ARTIFACTS\",\n        },\n        \"Cache\": {\n          \"Type\": \"NO_CACHE\",\n        },\n        \"EncryptionKey\": \"alias/aws/s3\",\n        \"Environment\": {\n          \"ComputeType\": \"BUILD_GENERAL1_SMALL\",\n          \"Image\": \"aws/codebuild/amazonlinux2-aarch64-standard:3.0\",\n          \"ImagePullCredentialsType\": \"CODEBUILD\",\n          \"PrivilegedMode\": true,\n          \"Type\": \"ARM_CONTAINER\",\n        },\n        \"ServiceRole\": {\n          \"Fn::GetAtt\": [\n            \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleC5F7BBFE\",\n            \"Arn\",\n          ],\n        },\n        \"Source\": {\n          \"BuildSpec\": \"{\n  \"version\": \"0.2\",\n  \"phases\": {\n    \"build\": {\n      \"commands\": [\n        \"current_dir=$(pwd)\",\n        \"echo \\\\\"$input\\\\\"\",\n        \"mkdir workdir\",\n        \"cd workdir\",\n        \"aws s3 cp \\\\\"$sourceS3Url\\\\\" temp.zip\",\n        \"unzip temp.zip\",\n        \"ls -la\",\n        \"aws ecr get-login-password | docker login --username AWS --password-stdin $repositoryAuthUri\",\n        \"aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws\",\n        \"docker buildx create --use\",\n        \"docker buildx ls\",\n        \"eval \\\\\"$buildCommand\\\\\"\"\n      ]\n    },\n    \"post_build\": {\n      \"commands\": [\n        \"echo Build completed on \\`date\\`\",\n        \"\\\\nSTATUS='SUCCESS'\\\\nif [ $CODEBUILD_BUILD_SUCCEEDING -ne 1 ] # Test if the build is failing\\\\nthen\\\\nSTATUS='FAILED'\\\\nREASON=\\\\\"ContainerImageBuild failed. See CloudWatch Log stream for the detailed reason: \\\\nhttps://$AWS_REGION.console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups/log-group/\\\\\\\\$252Faws\\\\\\\\$252Fcodebuild\\\\\\\\$252F$projectName/log-events/$CODEBUILD_LOG_PATH\\\\\"\\\\nfi\\\\ncat <<EOF > payload.json\\\\n{\\\\n  \\\\\"StackId\\\\\": \\\\\"$stackId\\\\\",\\\\n  \\\\\"RequestId\\\\\": \\\\\"$requestId\\\\\",\\\\n  \\\\\"LogicalResourceId\\\\\":\\\\\"$logicalResourceId\\\\\",\\\\n  \\\\\"PhysicalResourceId\\\\\": \\\\\"$imageTag\\\\\",\\\\n  \\\\\"Status\\\\\": \\\\\"$STATUS\\\\\",\\\\n  \\\\\"Reason\\\\\": \\\\\"$REASON\\\\\",\\\\n  \\\\\"Data\\\\\": {\\\\n    \\\\\"ImageTag\\\\\": \\\\\"$imageTag\\\\\"\\\\n  }\\\\n}\\\\nEOF\\\\ncurl -v -i -X PUT -H 'Content-Type:' -d \\\\\"@payload.json\\\\\" \\\\\"$responseURL\\\\\"\\\\n              \"\n      ]\n    }\n  }\n}\",\n          \"Type\": \"NO_SOURCE\",\n        },\n      },\n      \"Type\": \"AWS::CodeBuild::Project\",\n    },\n    \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleC5F7BBFE\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"codebuild.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/AmazonElasticContainerRegistryPublicReadOnly\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleDefaultPolicy2316728F\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": [\n                \"logs:CreateLogGroup\",\n                \"logs:CreateLogStream\",\n                \"logs:PutLogEvents\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": [\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      \"arn:\",\n                      {\n                        \"Ref\": \"AWS::Partition\",\n                      },\n                      \":logs:us-west-2:123456789012:log-group:/aws/codebuild/\",\n                      {\n                        \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n                      },\n                    ],\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      \"arn:\",\n                      {\n                        \"Ref\": \"AWS::Partition\",\n                      },\n                      \":logs:us-west-2:123456789012:log-group:/aws/codebuild/\",\n                      {\n                        \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n                      },\n                      \":*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n            {\n              \"Action\": [\n                \"codebuild:CreateReportGroup\",\n                \"codebuild:CreateReport\",\n                \"codebuild:UpdateReport\",\n                \"codebuild:BatchPutTestCases\",\n                \"codebuild:BatchPutCodeCoverages\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":codebuild:us-west-2:123456789012:report-group/\",\n                    {\n                      \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n                    },\n                    \"-*\",\n                  ],\n                ],\n              },\n            },\n            {\n              \"Action\": [\n                \"ecr:BatchCheckLayerAvailability\",\n                \"ecr:GetDownloadUrlForLayer\",\n                \"ecr:BatchGetImage\",\n                \"ecr:CompleteLayerUpload\",\n                \"ecr:UploadLayerPart\",\n                \"ecr:InitiateLayerUpload\",\n                \"ecr:PutImage\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::GetAtt\": [\n                  \"WebappBuildRepository4C93D48D\",\n                  \"Arn\",\n                ],\n              },\n            },\n            {\n              \"Action\": \"ecr:GetAuthorizationToken\",\n              \"Effect\": \"Allow\",\n              \"Resource\": \"*\",\n            },\n            {\n              \"Action\": \"ecr:DescribeImages\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::GetAtt\": [\n                  \"WebappBuildRepository4C93D48D\",\n                  \"Arn\",\n                ],\n              },\n            },\n            {\n              \"Action\": [\n                \"s3:GetObject*\",\n                \"s3:GetBucket*\",\n                \"s3:List*\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": [\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      \"arn:\",\n                      {\n                        \"Ref\": \"AWS::Partition\",\n                      },\n                      \":s3:::cdk-hnb659fds-assets-123456789012-us-west-2\",\n                    ],\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      \"arn:\",\n                      {\n                        \"Ref\": \"AWS::Partition\",\n                      },\n                      \":s3:::cdk-hnb659fds-assets-123456789012-us-west-2/*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleDefaultPolicy2316728F\",\n        \"Roles\": [\n          {\n            \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleC5F7BBFE\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68\": {\n      \"DependsOn\": [\n        \"CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"__entrypoint__.handler\",\n        \"MemorySize\": 128,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Sub\": \"arn:\\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n          },\n        ],\n        \"Policies\": [\n          {\n            \"PolicyDocument\": {\n              \"Statement\": [\n                {\n                  \"Action\": [\n                    \"ssm:AddTagsToResource\",\n                    \"ssm:RemoveTagsFromResource\",\n                    \"ssm:GetParameters\",\n                  ],\n                  \"Effect\": \"Allow\",\n                  \"Resource\": {\n                    \"Fn::Join\": [\n                      \"\",\n                      [\n                        \"arn:\",\n                        {\n                          \"Ref\": \"AWS::Partition\",\n                        },\n                        \":ssm:us-west-2:123456789012:parameter/cdk/exports/ServerlessWebappStarterKitStack/*\",\n                      ],\n                    ],\n                  },\n                },\n              ],\n              \"Version\": \"2012-10-17\",\n            },\n            \"PolicyName\": \"Inline\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F\": {\n      \"DependsOn\": [\n        \"CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Description\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"Lambda function for auto-deleting objects in \",\n              {\n                \"Ref\": \"AccessLogBucketDA470295\",\n              },\n              \" S3 bucket.\",\n            ],\n          ],\n        },\n        \"Handler\": \"index.handler\",\n        \"MemorySize\": 128,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Sub\": \"arn:\\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"DatabaseBastionHost4C4FAD9C\": {\n      \"DependsOn\": [\n        \"DatabaseBastionHostInstanceRoleDefaultPolicy15D8D0EA\",\n        \"DatabaseBastionHostInstanceRole87A429B0\",\n      ],\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1a\",\n        \"BlockDeviceMappings\": [\n          {\n            \"DeviceName\": \"/dev/sdf\",\n            \"Ebs\": {\n              \"Encrypted\": true,\n              \"VolumeSize\": 8,\n            },\n          },\n        ],\n        \"IamInstanceProfile\": {\n          \"Ref\": \"DatabaseBastionHostInstanceProfile0F4F3411\",\n        },\n        \"ImageId\": {\n          \"Ref\": \"SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61arm64C96584B6F00A464EAD1953AFF4B05118Parameter\",\n        },\n        \"InstanceType\": \"t4g.nano\",\n        \"SecurityGroupIds\": [\n          {\n            \"Fn::GetAtt\": [\n              \"DatabaseBastionHostInstanceSecurityGroup39D7809A\",\n              \"GroupId\",\n            ],\n          },\n        ],\n        \"SubnetId\": {\n          \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n        },\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"BastionHost\",\n          },\n        ],\n        \"UserData\": {\n          \"Fn::Base64\": \"#!/bin/bash\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Instance\",\n    },\n    \"DatabaseBastionHostInstanceProfile0F4F3411\": {\n      \"Properties\": {\n        \"Roles\": [\n          {\n            \"Ref\": \"DatabaseBastionHostInstanceRole87A429B0\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::InstanceProfile\",\n    },\n    \"DatabaseBastionHostInstanceRole87A429B0\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"ec2.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"BastionHost\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"DatabaseBastionHostInstanceRoleDefaultPolicy15D8D0EA\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": [\n                \"ssmmessages:*\",\n                \"ssm:UpdateInstanceInformation\",\n                \"ec2messages:*\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": \"*\",\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"DatabaseBastionHostInstanceRoleDefaultPolicy15D8D0EA\",\n        \"Roles\": [\n          {\n            \"Ref\": \"DatabaseBastionHostInstanceRole87A429B0\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"DatabaseBastionHostInstanceSecurityGroup39D7809A\": {\n      \"Properties\": {\n        \"GroupDescription\": \"ServerlessWebappStarterKitStack/Database/BastionHost/Resource/InstanceSecurityGroup\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"BastionHost\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"DatabaseCluster5B53A178\": {\n      \"DeletionPolicy\": \"Snapshot\",\n      \"Properties\": {\n        \"CopyTagsToSnapshot\": true,\n        \"DBClusterParameterGroupName\": {\n          \"Ref\": \"DatabaseParameterGroup2A921026\",\n        },\n        \"DBSubnetGroupName\": {\n          \"Ref\": \"DatabaseClusterSubnets5540150D\",\n        },\n        \"EnableCloudwatchLogsExports\": [\n          \"postgresql\",\n        ],\n        \"EnableHttpEndpoint\": true,\n        \"Engine\": \"aurora-postgresql\",\n        \"EngineVersion\": \"16.6\",\n        \"MasterUserPassword\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{{resolve:secretsmanager:\",\n              {\n                \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n              },\n              \":SecretString:password::}}\",\n            ],\n          ],\n        },\n        \"MasterUsername\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{{resolve:secretsmanager:\",\n              {\n                \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n              },\n              \":SecretString:username::}}\",\n            ],\n          ],\n        },\n        \"Port\": 5432,\n        \"ServerlessV2ScalingConfiguration\": {\n          \"MaxCapacity\": 2,\n          \"MinCapacity\": 0,\n        },\n        \"StorageEncrypted\": true,\n        \"VpcSecurityGroupIds\": [\n          {\n            \"Fn::GetAtt\": [\n              \"DatabaseClusterSecurityGroupFEF1426A\",\n              \"GroupId\",\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::RDS::DBCluster\",\n      \"UpdateReplacePolicy\": \"Snapshot\",\n    },\n    \"DatabaseClusterLogRetentionpostgresql025D39CE\": {\n      \"Properties\": {\n        \"LogGroupName\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"/aws/rds/cluster/\",\n              {\n                \"Ref\": \"DatabaseCluster5B53A178\",\n              },\n              \"/postgresql\",\n            ],\n          ],\n        },\n        \"RetentionInDays\": 7,\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A\",\n            \"Arn\",\n          ],\n        },\n      },\n      \"Type\": \"Custom::LogRetention\",\n    },\n    \"DatabaseClusterSecretAttachmentDC8466C0\": {\n      \"Properties\": {\n        \"SecretId\": {\n          \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n        },\n        \"TargetId\": {\n          \"Ref\": \"DatabaseCluster5B53A178\",\n        },\n        \"TargetType\": \"AWS::RDS::DBCluster\",\n      },\n      \"Type\": \"AWS::SecretsManager::SecretTargetAttachment\",\n    },\n    \"DatabaseClusterSecretD1FB634F\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"Description\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"Generated by the CDK for stack: \",\n              {\n                \"Ref\": \"AWS::StackName\",\n              },\n            ],\n          ],\n        },\n        \"GenerateSecretString\": {\n          \"ExcludeCharacters\": \" %+~\\`#$&*()|[]{}:;<>?!'/@\"\\\\,=^\",\n          \"GenerateStringKey\": \"password\",\n          \"PasswordLength\": 30,\n          \"SecretStringTemplate\": \"{\"username\":\"postgres\"}\",\n        },\n      },\n      \"Type\": \"AWS::SecretsManager::Secret\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"DatabaseClusterSecurityGroupFEF1426A\": {\n      \"Properties\": {\n        \"GroupDescription\": \"RDS security group\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackAsyncJobHandlerSecurityGroup5220DFB3IndirectPort9323962E\": {\n      \"Properties\": {\n        \"Description\": \"from ServerlessWebappStarterKitStackAsyncJobHandlerSecurityGroup5220DFB3:{IndirectPort}\",\n        \"FromPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n        \"GroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseClusterSecurityGroupFEF1426A\",\n            \"GroupId\",\n          ],\n        },\n        \"IpProtocol\": \"tcp\",\n        \"SourceSecurityGroupId\": {\n          \"Fn::GetAtt\": [\n            \"AsyncJobHandlerSecurityGroupF59812E6\",\n            \"GroupId\",\n          ],\n        },\n        \"ToPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroupIngress\",\n    },\n    \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackDatabaseBastionHostInstanceSecurityGroup4F0DD25BIndirectPort8AFD9922\": {\n      \"Properties\": {\n        \"Description\": \"from ServerlessWebappStarterKitStackDatabaseBastionHostInstanceSecurityGroup4F0DD25B:{IndirectPort}\",\n        \"FromPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n        \"GroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseClusterSecurityGroupFEF1426A\",\n            \"GroupId\",\n          ],\n        },\n        \"IpProtocol\": \"tcp\",\n        \"SourceSecurityGroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseBastionHostInstanceSecurityGroup39D7809A\",\n            \"GroupId\",\n          ],\n        },\n        \"ToPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroupIngress\",\n    },\n    \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackWebappHandlerSecurityGroupA009CF4AIndirectPort7311063E\": {\n      \"Properties\": {\n        \"Description\": \"from ServerlessWebappStarterKitStackWebappHandlerSecurityGroupA009CF4A:{IndirectPort}\",\n        \"FromPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n        \"GroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseClusterSecurityGroupFEF1426A\",\n            \"GroupId\",\n          ],\n        },\n        \"IpProtocol\": \"tcp\",\n        \"SourceSecurityGroupId\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandlerSecurityGroup5451B519\",\n            \"GroupId\",\n          ],\n        },\n        \"ToPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroupIngress\",\n    },\n    \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackWebappMigrationRunnerSecurityGroupC0959349IndirectPortDCF4A356\": {\n      \"Properties\": {\n        \"Description\": \"from ServerlessWebappStarterKitStackWebappMigrationRunnerSecurityGroupC0959349:{IndirectPort}\",\n        \"FromPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n        \"GroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseClusterSecurityGroupFEF1426A\",\n            \"GroupId\",\n          ],\n        },\n        \"IpProtocol\": \"tcp\",\n        \"SourceSecurityGroupId\": {\n          \"Fn::GetAtt\": [\n            \"WebappMigrationRunnerSecurityGroup7F0DF264\",\n            \"GroupId\",\n          ],\n        },\n        \"ToPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroupIngress\",\n    },\n    \"DatabaseClusterSubnets5540150D\": {\n      \"Properties\": {\n        \"DBSubnetGroupDescription\": \"Subnets for Cluster database\",\n        \"SubnetIds\": [\n          {\n            \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n          },\n          {\n            \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n          },\n          {\n            \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::RDS::DBSubnetGroup\",\n    },\n    \"DatabaseClusterWriterD43085C6\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AutoMinorVersionUpgrade\": true,\n        \"DBClusterIdentifier\": {\n          \"Ref\": \"DatabaseCluster5B53A178\",\n        },\n        \"DBInstanceClass\": \"db.serverless\",\n        \"EnablePerformanceInsights\": true,\n        \"Engine\": \"aurora-postgresql\",\n        \"PerformanceInsightsRetentionPeriod\": 7,\n        \"PromotionTier\": 0,\n        \"PubliclyAccessible\": false,\n      },\n      \"Type\": \"AWS::RDS::DBInstance\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"DatabaseParameterGroup2A921026\": {\n      \"Properties\": {\n        \"Description\": \"Cluster parameter group for aurora-postgresql16\",\n        \"Family\": \"aurora-postgresql16\",\n        \"Parameters\": {\n          \"idle_session_timeout\": \"60000\",\n          \"log_connections\": \"1\",\n          \"log_disconnections\": \"1\",\n        },\n      },\n      \"Type\": \"AWS::RDS::DBClusterParameterGroup\",\n    },\n    \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f306AEFF37\": {\n      \"DependsOn\": [\n        \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleDefaultPolicyFECC51DC\",\n        \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleB008BAA4\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleB008BAA4\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs20.x\",\n        \"Timeout\": 300,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleB008BAA4\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleDefaultPolicyFECC51DC\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"codebuild:StartBuild\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::GetAtt\": [\n                  \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n                  \"Arn\",\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleDefaultPolicyFECC51DC\",\n        \"Roles\": [\n          {\n            \"Ref\": \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleB008BAA4\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"EventBusApi6E8C7C94\": {\n      \"Properties\": {\n        \"EventConfig\": {\n          \"AuthProviders\": [\n            {\n              \"AuthType\": \"AWS_IAM\",\n            },\n            {\n              \"AuthType\": \"AMAZON_COGNITO_USER_POOLS\",\n              \"CognitoConfig\": {\n                \"AwsRegion\": \"us-west-2\",\n                \"UserPoolId\": {\n                  \"Ref\": \"AuthUserPool8115E87F\",\n                },\n              },\n            },\n          ],\n          \"ConnectionAuthModes\": [\n            {\n              \"AuthType\": \"AWS_IAM\",\n            },\n            {\n              \"AuthType\": \"AMAZON_COGNITO_USER_POOLS\",\n            },\n          ],\n          \"DefaultPublishAuthModes\": [\n            {\n              \"AuthType\": \"AWS_IAM\",\n            },\n          ],\n          \"DefaultSubscribeAuthModes\": [\n            {\n              \"AuthType\": \"AWS_IAM\",\n            },\n            {\n              \"AuthType\": \"AMAZON_COGNITO_USER_POOLS\",\n            },\n          ],\n        },\n        \"Name\": \"ServerlessWackEventBus8815362F\",\n      },\n      \"Type\": \"AWS::AppSync::Api\",\n    },\n    \"EventBusNamespaceA69F015E\": {\n      \"Properties\": {\n        \"ApiId\": {\n          \"Fn::GetAtt\": [\n            \"EventBusApi6E8C7C94\",\n            \"ApiId\",\n          ],\n        },\n        \"CodeS3Location\": \"s3://cdk-hnb659fds-assets-123456789012-us-west-2/REDACTED\",\n        \"HandlerConfigs\": {},\n        \"Name\": \"event-bus\",\n      },\n      \"Type\": \"AWS::AppSync::ChannelNamespace\",\n    },\n    \"ExportsReader8B249524\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"ReaderProps\": {\n          \"imports\": {\n            \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\": \"{{resolve:ssm:/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA}}\",\n          },\n          \"prefix\": \"ServerlessWebappStarterKitStack\",\n          \"region\": \"us-west-2\",\n        },\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68\",\n            \"Arn\",\n          ],\n        },\n      },\n      \"Type\": \"Custom::CrossRegionExportReader\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A\": {\n      \"DependsOn\": [\n        \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB\",\n        \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": [\n                \"logs:PutRetentionPolicy\",\n                \"logs:DeleteRetentionPolicy\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": \"*\",\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB\",\n        \"Roles\": [\n          {\n            \"Ref\": \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b0087110E04D0A\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b00871CustomResourcePolicy7CD24C5F\",\n      ],\n      \"Properties\": {\n        \"Create\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"SSM\",\"action\":\"getParameter\",\"parameters\":{\"Name\":\"\",\n              {\n                \"Fn::GetAtt\": [\n                  \"ExportsReader8B249524\",\n                  \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\",\n                ],\n              },\n              \"\"},\"physicalResourceId\":{\"id\":\"1577836800000\"},\"region\":\"us-east-1\"}\",\n            ],\n          ],\n        },\n        \"InstallLatestAwsSdk\": true,\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"AWS679f53fac002430cb0da5b7982bd22872D164C4C\",\n            \"Arn\",\n          ],\n        },\n        \"Update\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"SSM\",\"action\":\"getParameter\",\"parameters\":{\"Name\":\"\",\n              {\n                \"Fn::GetAtt\": [\n                  \"ExportsReader8B249524\",\n                  \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\",\n                ],\n              },\n              \"\"},\"physicalResourceId\":{\"id\":\"1577836800000\"},\"region\":\"us-east-1\"}\",\n            ],\n          ],\n        },\n      },\n      \"Type\": \"Custom::AWS\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b00871CustomResourcePolicy7CD24C5F\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"ssm:GetParameter\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":ssm:us-east-1:123456789012:parameter/\",\n                    {\n                      \"Fn::GetAtt\": [\n                        \"ExportsReader8B249524\",\n                        \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\",\n                      ],\n                    },\n                  ],\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b00871CustomResourcePolicy7CD24C5F\",\n        \"Roles\": [\n          {\n            \"Ref\": \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"RandomStringGenerator11e9c903f11a4989833c985dddef5eb28C5103D0\": {\n      \"DependsOn\": [\n        \"RandomStringGenerator11e9c903f11a4989833c985dddef5eb2ServiceRoleAB6B57A9\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"ZipFile\": \"const response = require('cfn-response');\nconst crypto = require('crypto');\n\nexports.handler = async function (event, context) {\n  try {\n    console.log(event);\n    if (event.RequestType == 'Delete') {\n      return await response.send(event, context, response.SUCCESS);\n    }\n\n    const prefix = event.ResourceProperties.prefix ?? '';\n    const length = event.ResourceProperties.length ?? '8';\n    const generate = () => {\n      const random = crypto.randomBytes(parseInt(length)).toString('hex');\n      return \\`\\${prefix}\\${random.slice(0, length)}\\`;\n    };\n\n    if (event.RequestType == 'Create') {\n      const generated = generate();\n      return await response.send(event, context, response.SUCCESS, { generated }, generated);\n    }\n    if (event.RequestType == 'Update') {\n      const current = event.PhysicalResourceId;\n      if (current.startsWith(prefix)) {\n        return await response.send(event, context, response.SUCCESS, { generated: current }, current);\n      }\n      const generated = generate();\n      return await response.send(event, context, response.SUCCESS, { generated }, generated);\n    }\n  } catch (e) {\n    console.log(e);\n    await response.send(event, context, response.FAILED);\n  }\n};\n\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"RandomStringGenerator11e9c903f11a4989833c985dddef5eb2ServiceRoleAB6B57A9\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 5,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"RandomStringGenerator11e9c903f11a4989833c985dddef5eb2ServiceRoleAB6B57A9\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"SchedulerRoleForTarget44ece2CFC6840F\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Condition\": {\n                \"StringEquals\": {\n                  \"aws:SourceAccount\": \"123456789012\",\n                  \"aws:SourceArn\": {\n                    \"Fn::Join\": [\n                      \"\",\n                      [\n                        \"arn:\",\n                        {\n                          \"Ref\": \"AWS::Partition\",\n                        },\n                        \":scheduler:us-west-2:123456789012:schedule-group/default\",\n                      ],\n                    ],\n                  },\n                },\n              },\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"scheduler.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"SchedulerRoleForTarget44ece2DefaultPolicyFDF3E159\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"lambda:InvokeFunction\",\n              \"Effect\": \"Allow\",\n              \"Resource\": [\n                {\n                  \"Fn::GetAtt\": [\n                    \"AsyncJobHandler438266BD\",\n                    \"Arn\",\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      {\n                        \"Fn::GetAtt\": [\n                          \"AsyncJobHandler438266BD\",\n                          \"Arn\",\n                        ],\n                      },\n                      \":*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"SchedulerRoleForTarget44ece2DefaultPolicyFDF3E159\",\n        \"Roles\": [\n          {\n            \"Ref\": \"SchedulerRoleForTarget44ece2CFC6840F\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"Vpc8378EB38\": {\n      \"Properties\": {\n        \"CidrBlock\": \"10.0.0.0/16\",\n        \"EnableDnsHostnames\": true,\n        \"EnableDnsSupport\": true,\n        \"InstanceTenancy\": \"default\",\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::EC2::VPC\",\n    },\n    \"VpcIGWD7BA715C\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::EC2::InternetGateway\",\n    },\n    \"VpcNatSecurityGroup8DA26EDC\": {\n      \"Properties\": {\n        \"GroupDescription\": \"Security Group for NAT instances\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"SecurityGroupIngress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"from 0.0.0.0/0:ALL TRAFFIC\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"VpcPrivateSubnet1DefaultRouteBE02A9ED\": {\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"InstanceId\": {\n          \"Ref\": \"VpcPublicSubnet1NatInstance57B636B8\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet1RouteTableB2C5B500\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet1RouteTableB2C5B500\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPrivateSubnet1RouteTableB2C5B500\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPrivateSubnet1Subnet536B997A\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1a\",\n        \"CidrBlock\": \"10.0.96.0/19\",\n        \"MapPublicIpOnLaunch\": false,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPrivateSubnet2DefaultRoute060D2087\": {\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"InstanceId\": {\n          \"Ref\": \"VpcPublicSubnet1NatInstance57B636B8\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet2RouteTableA678073B\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPrivateSubnet2RouteTableA678073B\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet2\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet2RouteTableA678073B\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPrivateSubnet2Subnet3788AAA1\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1b\",\n        \"CidrBlock\": \"10.0.128.0/19\",\n        \"MapPublicIpOnLaunch\": false,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet2\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPrivateSubnet3DefaultRoute94B74F0D\": {\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"InstanceId\": {\n          \"Ref\": \"VpcPublicSubnet1NatInstance57B636B8\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet3RouteTableD98824C7\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet3RouteTableD98824C7\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPrivateSubnet3RouteTableD98824C7\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet3\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPrivateSubnet3SubnetF258B56E\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1c\",\n        \"CidrBlock\": \"10.0.160.0/19\",\n        \"MapPublicIpOnLaunch\": false,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet3\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPublicSubnet1DefaultRoute3DA9E72A\": {\n      \"DependsOn\": [\n        \"VpcVPCGWBF912B6E\",\n      ],\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"GatewayId\": {\n          \"Ref\": \"VpcIGWD7BA715C\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet1RouteTable6C95E38E\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPublicSubnet1NatInstance57B636B8\": {\n      \"DependsOn\": [\n        \"VpcPublicSubnet1DefaultRoute3DA9E72A\",\n        \"VpcPublicSubnet1NatInstanceInstanceRole9D835E32\",\n        \"VpcPublicSubnet1RouteTableAssociation97140677\",\n      ],\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1a\",\n        \"IamInstanceProfile\": {\n          \"Ref\": \"VpcPublicSubnet1NatInstanceInstanceProfileEE10C485\",\n        },\n        \"ImageId\": {\n          \"Ref\": \"SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61arm64C96584B6F00A464EAD1953AFF4B05118Parameter\",\n        },\n        \"InstanceType\": \"t4g.nano\",\n        \"NetworkInterfaces\": [\n          {\n            \"AssociatePublicIpAddress\": true,\n            \"DeviceIndex\": \"0\",\n            \"GroupSet\": [\n              {\n                \"Fn::GetAtt\": [\n                  \"VpcNatSecurityGroup8DA26EDC\",\n                  \"GroupId\",\n                ],\n              },\n            ],\n            \"SubnetId\": {\n              \"Ref\": \"VpcPublicSubnet1Subnet5C2D37C4\",\n            },\n          },\n        ],\n        \"SourceDestCheck\": false,\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet1/NatInstance\",\n          },\n        ],\n        \"UserData\": {\n          \"Fn::Base64\": \"#!/bin/bash\nfor i in {1..5}; do yum install iptables-services -y && break || sleep 10; done\nsystemctl enable iptables\nsystemctl start iptables\necho \"net.ipv4.ip_forward=1\" > /etc/sysctl.d/custom-ip-forwarding.conf\nsysctl -p /etc/sysctl.d/custom-ip-forwarding.conf\nIFACE=$(ip route show default | awk '{print $5}')\n/sbin/iptables -t nat -A POSTROUTING -o $IFACE -j MASQUERADE\n/sbin/iptables -F FORWARD\nservice iptables save\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Instance\",\n    },\n    \"VpcPublicSubnet1NatInstanceInstanceProfileEE10C485\": {\n      \"Properties\": {\n        \"Roles\": [\n          {\n            \"Ref\": \"VpcPublicSubnet1NatInstanceInstanceRole9D835E32\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::InstanceProfile\",\n    },\n    \"VpcPublicSubnet1NatInstanceInstanceRole9D835E32\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"ec2.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet1/NatInstance\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"VpcPublicSubnet1RouteTable6C95E38E\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPublicSubnet1RouteTableAssociation97140677\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet1RouteTable6C95E38E\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPublicSubnet1Subnet5C2D37C4\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPublicSubnet1Subnet5C2D37C4\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1a\",\n        \"CidrBlock\": \"10.0.0.0/19\",\n        \"MapPublicIpOnLaunch\": true,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPublicSubnet2DefaultRoute97F91067\": {\n      \"DependsOn\": [\n        \"VpcVPCGWBF912B6E\",\n      ],\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"GatewayId\": {\n          \"Ref\": \"VpcIGWD7BA715C\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet2RouteTable94F7E489\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPublicSubnet2RouteTable94F7E489\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet2\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPublicSubnet2RouteTableAssociationDD5762D8\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet2RouteTable94F7E489\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPublicSubnet2Subnet691E08A3\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPublicSubnet2Subnet691E08A3\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1b\",\n        \"CidrBlock\": \"10.0.32.0/19\",\n        \"MapPublicIpOnLaunch\": true,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet2\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPublicSubnet3DefaultRoute4697774F\": {\n      \"DependsOn\": [\n        \"VpcVPCGWBF912B6E\",\n      ],\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"GatewayId\": {\n          \"Ref\": \"VpcIGWD7BA715C\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet3RouteTable93458DBB\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPublicSubnet3RouteTable93458DBB\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet3\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPublicSubnet3RouteTableAssociation1F1EDF02\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet3RouteTable93458DBB\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPublicSubnet3SubnetBE12F0B6\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPublicSubnet3SubnetBE12F0B6\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1c\",\n        \"CidrBlock\": \"10.0.64.0/19\",\n        \"MapPublicIpOnLaunch\": true,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet3\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcVPCGWBF912B6E\": {\n      \"Properties\": {\n        \"InternetGatewayId\": {\n          \"Ref\": \"VpcIGWD7BA715C\",\n        },\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::VPCGatewayAttachment\",\n    },\n    \"Webapp107041BD\": {\n      \"Properties\": {\n        \"DistributionConfig\": {\n          \"Comment\": \"CloudFront for Webapp\",\n          \"DefaultCacheBehavior\": {\n            \"AllowedMethods\": [\n              \"GET\",\n              \"HEAD\",\n              \"OPTIONS\",\n              \"PUT\",\n              \"PATCH\",\n              \"POST\",\n              \"DELETE\",\n            ],\n            \"CachePolicyId\": {\n              \"Ref\": \"WebappSharedCachePolicy14FEE4A0\",\n            },\n            \"Compress\": true,\n            \"FunctionAssociations\": [\n              {\n                \"EventType\": \"viewer-request\",\n                \"FunctionARN\": {\n                  \"Fn::GetAtt\": [\n                    \"WebappCacheKeyFunction6C227CE2\",\n                    \"FunctionARN\",\n                  ],\n                },\n              },\n            ],\n            \"LambdaFunctionAssociations\": [\n              {\n                \"EventType\": \"origin-request\",\n                \"IncludeBody\": true,\n                \"LambdaFunctionARN\": {\n                  \"Fn::GetAtt\": [\n                    \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b0087110E04D0A\",\n                    \"Parameter.Value\",\n                  ],\n                },\n              },\n            ],\n            \"OriginRequestPolicyId\": \"b689b0a8-53d0-40ab-baf2-68738e2966ac\",\n            \"TargetOriginId\": \"ServerlessWebappStarterKitStackWebappOrigin1D7B867FF\",\n            \"ViewerProtocolPolicy\": \"allow-all\",\n          },\n          \"Enabled\": true,\n          \"HttpVersion\": \"http2\",\n          \"IPV6Enabled\": true,\n          \"Logging\": {\n            \"Bucket\": {\n              \"Fn::GetAtt\": [\n                \"AccessLogBucketDA470295\",\n                \"RegionalDomainName\",\n              ],\n            },\n            \"Prefix\": \"Webapp/\",\n          },\n          \"Origins\": [\n            {\n              \"ConnectionTimeout\": 6,\n              \"CustomOriginConfig\": {\n                \"OriginProtocolPolicy\": \"https-only\",\n                \"OriginReadTimeout\": 60,\n                \"OriginSSLProtocols\": [\n                  \"TLSv1.2\",\n                ],\n              },\n              \"DomainName\": {\n                \"Fn::Select\": [\n                  2,\n                  {\n                    \"Fn::Split\": [\n                      \"/\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappHandlerFunctionUrl7AEF8DEE\",\n                          \"FunctionUrl\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \"Id\": \"ServerlessWebappStarterKitStackWebappOrigin1D7B867FF\",\n              \"OriginAccessControlId\": {\n                \"Fn::GetAtt\": [\n                  \"WebappOrigin1FunctionUrlOriginAccessControlEA98B600\",\n                  \"Id\",\n                ],\n              },\n            },\n          ],\n        },\n      },\n      \"Type\": \"AWS::CloudFront::Distribution\",\n    },\n    \"WebappBuild348806E3\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n        \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleDefaultPolicy2316728F\",\n        \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleC5F7BBFE\",\n      ],\n      \"Properties\": {\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f306AEFF37\",\n            \"Arn\",\n          ],\n        },\n        \"buildCommand\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"docker buildx build --build-arg ALLOWED_ORIGIN_HOST=*.cloudfront.net --build-arg SKIP_TS_BUILD=true --build-arg NEXT_PUBLIC_EVENT_HTTP_ENDPOINT=https://\",\n              {\n                \"Fn::GetAtt\": [\n                  \"EventBusApi6E8C7C94\",\n                  \"Dns.Http\",\n                ],\n              },\n              \" --build-arg NEXT_PUBLIC_AWS_REGION=us-west-2 --platform linux/arm64 --output type=image,name=\",\n              {\n                \"Fn::Select\": [\n                  4,\n                  {\n                    \"Fn::Split\": [\n                      \":\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappBuildRepository4C93D48D\",\n                          \"Arn\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \".dkr.ecr.\",\n              {\n                \"Fn::Select\": [\n                  3,\n                  {\n                    \"Fn::Split\": [\n                      \":\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappBuildRepository4C93D48D\",\n                          \"Arn\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \".\",\n              {\n                \"Ref\": \"AWS::URLSuffix\",\n              },\n              \"/\",\n              {\n                \"Ref\": \"WebappBuildRepository4C93D48D\",\n              },\n              \":<IMAGE_TAG>,push=true --provenance=false .\",\n            ],\n          ],\n        },\n        \"codeBuildProjectName\": {\n          \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n        },\n        \"repositoryUri\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              {\n                \"Fn::Select\": [\n                  4,\n                  {\n                    \"Fn::Split\": [\n                      \":\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappBuildRepository4C93D48D\",\n                          \"Arn\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \".dkr.ecr.\",\n              {\n                \"Fn::Select\": [\n                  3,\n                  {\n                    \"Fn::Split\": [\n                      \":\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappBuildRepository4C93D48D\",\n                          \"Arn\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \".\",\n              {\n                \"Ref\": \"AWS::URLSuffix\",\n              },\n              \"/\",\n              {\n                \"Ref\": \"WebappBuildRepository4C93D48D\",\n              },\n            ],\n          ],\n        },\n        \"sourceS3Url\": \"s3://cdk-hnb659fds-assets-123456789012-us-west-2/REDACTED\",\n        \"tagPrefix\": \"REDACTED\",\n        \"type\": \"ContainerImageBuild\",\n      },\n      \"Type\": \"Custom::CDKContainerImageBuild\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappBuildRepository4C93D48D\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"EmptyOnDelete\": true,\n        \"RepositoryPolicyText\": {\n          \"Statement\": [\n            {\n              \"Action\": [\n                \"ecr:BatchCheckLayerAvailability\",\n                \"ecr:GetDownloadUrlForLayer\",\n                \"ecr:BatchGetImage\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n      },\n      \"Type\": \"AWS::ECR::Repository\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappCacheKeyFunction6C227CE2\": {\n      \"Properties\": {\n        \"AutoPublish\": true,\n        \"FunctionCode\": \"// CloudFront Functions JS 2.0\n// Combines Next.js RSC-related headers into a single hashed cache key header\n// to prevent cache poisoning between HTML and RSC flight responses.\n//\n// Next.js App Router sets Vary: rsc, next-router-state-tree, next-router-prefetch,\n// next-router-segment-prefetch (and next-url for interception routes).\n// CloudFront ignores Vary and requires explicit cache key configuration,\n// but its Cache Policy has a 10-header limit. This function hashes all\n// RSC headers into one header to stay within the limit.\nasync function handler(event) {\n  var h = event.request.headers;\n  var parts = [\n    'rsc',\n    'next-router-prefetch',\n    'next-router-state-tree',\n    'next-router-segment-prefetch',\n    'next-url',\n  ];\n  var key = '';\n  for (var i = 0; i < parts.length; i++) {\n    if (h[parts[i]]) {\n      key += parts[i] + '=' + h[parts[i]].value + ';';\n    }\n  }\n  if (key) {\n    // FNV-1a hash (32-bit). Cryptographic strength is unnecessary;\n    // we only need distinct cache keys for distinct header combinations.\n    // See: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function\n    var FNV_OFFSET_BASIS = 2166136261;\n    var FNV_PRIME = 16777619;\n    var hash = FNV_OFFSET_BASIS;\n    for (var j = 0; j < key.length; j++) {\n      hash ^= key.charCodeAt(j);\n      hash = (hash * FNV_PRIME) | 0;\n    }\n    event.request.headers['x-nextjs-cache-key'] = { value: String(hash >>> 0) };\n  }\n  return event.request;\n}\n\",\n        \"FunctionConfig\": {\n          \"Comment\": \"us-west-2ServerlessWebappCacheKeyFunction86D1ABE9\",\n          \"Runtime\": \"cloudfront-js-2.0\",\n        },\n        \"Name\": \"us-west-2ServerlessWebappCacheKeyFunction86D1ABE9\",\n      },\n      \"Type\": \"AWS::CloudFront::Function\",\n    },\n    \"WebappCloudFrontInvalidation588CF152\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"WebappCloudFrontInvalidationCustomResourcePolicy18C215D6\",\n      ],\n      \"Properties\": {\n        \"Create\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"cloudfront\",\"action\":\"createInvalidation\",\"parameters\":{\"DistributionId\":\"\",\n              {\n                \"Ref\": \"Webapp107041BD\",\n              },\n              \"\",\"InvalidationBatch\":{\"CallerReference\":\"\",\n              {\n                \"Fn::GetAtt\": [\n                  \"WebappHandlerCurrentVersionREDACTED\",\n                  \"Version\",\n                ],\n              },\n              \"\",\"Paths\":{\"Quantity\":1,\"Items\":[\"/*\"]}}},\"physicalResourceId\":{\"id\":\"invalidation\"}}\",\n            ],\n          ],\n        },\n        \"InstallLatestAwsSdk\": true,\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"AWS679f53fac002430cb0da5b7982bd22872D164C4C\",\n            \"Arn\",\n          ],\n        },\n        \"Update\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"cloudfront\",\"action\":\"createInvalidation\",\"parameters\":{\"DistributionId\":\"\",\n              {\n                \"Ref\": \"Webapp107041BD\",\n              },\n              \"\",\"InvalidationBatch\":{\"CallerReference\":\"\",\n              {\n                \"Fn::GetAtt\": [\n                  \"WebappHandlerCurrentVersionREDACTED\",\n                  \"Version\",\n                ],\n              },\n              \"\",\"Paths\":{\"Quantity\":1,\"Items\":[\"/*\"]}}},\"physicalResourceId\":{\"id\":\"invalidation\"}}\",\n            ],\n          ],\n        },\n      },\n      \"Type\": \"Custom::AWS\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappCloudFrontInvalidationCustomResourcePolicy18C215D6\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"cloudfront:CreateInvalidation\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":cloudfront::123456789012:distribution/\",\n                    {\n                      \"Ref\": \"Webapp107041BD\",\n                    },\n                  ],\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"WebappCloudFrontInvalidationCustomResourcePolicy18C215D6\",\n        \"Roles\": [\n          {\n            \"Ref\": \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"WebappHandler8DD158A3\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n        \"WebappHandlerServiceRoleDefaultPolicy7D06F4EA\",\n        \"WebappHandlerServiceRole4F4D1ACD\",\n      ],\n      \"Properties\": {\n        \"Architectures\": [\n          \"arm64\",\n        ],\n        \"Code\": {\n          \"ImageUri\": {\n            \"Fn::Join\": [\n              \"\",\n              [\n                {\n                  \"Fn::Select\": [\n                    4,\n                    {\n                      \"Fn::Split\": [\n                        \":\",\n                        {\n                          \"Fn::GetAtt\": [\n                            \"WebappBuildRepository4C93D48D\",\n                            \"Arn\",\n                          ],\n                        },\n                      ],\n                    },\n                  ],\n                },\n                \".dkr.ecr.\",\n                {\n                  \"Fn::Select\": [\n                    3,\n                    {\n                      \"Fn::Split\": [\n                        \":\",\n                        {\n                          \"Fn::GetAtt\": [\n                            \"WebappBuildRepository4C93D48D\",\n                            \"Arn\",\n                          ],\n                        },\n                      ],\n                    },\n                  ],\n                },\n                \".\",\n                {\n                  \"Ref\": \"AWS::URLSuffix\",\n                },\n                \"/\",\n                {\n                  \"Ref\": \"WebappBuildRepository4C93D48D\",\n                },\n                \":\",\n                {\n                  \"Fn::GetAtt\": [\n                    \"WebappBuild348806E3\",\n                    \"ImageTag\",\n                  ],\n                },\n              ],\n            ],\n          },\n        },\n        \"Environment\": {\n          \"Variables\": {\n            \"AMPLIFY_APP_ORIGIN_SOURCE_PARAMETER\": {\n              \"Ref\": \"WebappOriginSourceParameterD87E143B\",\n            },\n            \"ASYNC_JOB_HANDLER_ARN\": {\n              \"Fn::GetAtt\": [\n                \"AsyncJobHandler438266BD\",\n                \"Arn\",\n              ],\n            },\n            \"COGNITO_DOMAIN\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  {\n                    \"Fn::GetAtt\": [\n                      \"AuthDomainPrefixE1742B23\",\n                      \"generated\",\n                    ],\n                  },\n                  \".auth.us-west-2.amazoncognito.com\",\n                ],\n              ],\n            },\n            \"DATABASE_ENGINE\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_HOST\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Address\",\n              ],\n            },\n            \"DATABASE_NAME\": \"main\",\n            \"DATABASE_OPTION\": \"?connection_limit=1&connect_timeout=30\",\n            \"DATABASE_PASSWORD\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_PORT\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Port\",\n              ],\n            },\n            \"DATABASE_URL\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}://{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}:{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}@\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Address\",\n                    ],\n                  },\n                  \":\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Port\",\n                    ],\n                  },\n                  \"/main?connection_limit=1&connect_timeout=30\",\n                ],\n              ],\n            },\n            \"DATABASE_USER\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}\",\n                ],\n              ],\n            },\n            \"USER_POOL_CLIENT_ID\": {\n              \"Ref\": \"AuthUserPoolClientC635291F\",\n            },\n            \"USER_POOL_ID\": {\n              \"Ref\": \"AuthUserPool8115E87F\",\n            },\n          },\n        },\n        \"LoggingConfig\": {\n          \"LogGroup\": {\n            \"Ref\": \"WebappHandlerLogs87A6D2D7\",\n          },\n        },\n        \"MemorySize\": 1024,\n        \"PackageType\": \"Image\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandlerServiceRole4F4D1ACD\",\n            \"Arn\",\n          ],\n        },\n        \"Timeout\": 180,\n        \"VpcConfig\": {\n          \"SecurityGroupIds\": [\n            {\n              \"Fn::GetAtt\": [\n                \"WebappHandlerSecurityGroup5451B519\",\n                \"GroupId\",\n              ],\n            },\n          ],\n          \"SubnetIds\": [\n            {\n              \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n            },\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"WebappHandlerCurrentVersionREDACTED\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"FunctionName\": {\n          \"Ref\": \"WebappHandler8DD158A3\",\n        },\n      },\n      \"Type\": \"AWS::Lambda::Version\",\n    },\n    \"WebappHandlerFunctionUrl7AEF8DEE\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AuthType\": \"AWS_IAM\",\n        \"InvokeMode\": \"RESPONSE_STREAM\",\n        \"TargetFunctionArn\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandler8DD158A3\",\n            \"Arn\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Url\",\n    },\n    \"WebappHandlerLogs87A6D2D7\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"RetentionInDays\": 7,\n      },\n      \"Type\": \"AWS::Logs::LogGroup\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappHandlerSecurityGroup5451B519\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"GroupDescription\": \"Automatic security group for Lambda Function ServerlessWebappStarterKitStackWebappHandlerF1A4ACC9\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"WebappHandlerServiceRole4F4D1ACD\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"WebappHandlerServiceRoleDefaultPolicy7D06F4EA\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"lambda:InvokeFunction\",\n              \"Effect\": \"Allow\",\n              \"Resource\": [\n                {\n                  \"Fn::GetAtt\": [\n                    \"AsyncJobHandler438266BD\",\n                    \"Arn\",\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      {\n                        \"Fn::GetAtt\": [\n                          \"AsyncJobHandler438266BD\",\n                          \"Arn\",\n                        ],\n                      },\n                      \":*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n            {\n              \"Action\": [\n                \"ssm:DescribeParameters\",\n                \"ssm:GetParameters\",\n                \"ssm:GetParameter\",\n                \"ssm:GetParameterHistory\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":ssm:us-west-2:123456789012:parameter/\",\n                    {\n                      \"Ref\": \"WebappOriginSourceParameterD87E143B\",\n                    },\n                  ],\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"WebappHandlerServiceRoleDefaultPolicy7D06F4EA\",\n        \"Roles\": [\n          {\n            \"Ref\": \"WebappHandlerServiceRole4F4D1ACD\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"WebappInvokeFunctionPermission8F3F2610\": {\n      \"Properties\": {\n        \"Action\": \"lambda:InvokeFunction\",\n        \"FunctionName\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandler8DD158A3\",\n            \"Arn\",\n          ],\n        },\n        \"Principal\": \"cloudfront.amazonaws.com\",\n        \"SourceArn\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"arn:\",\n              {\n                \"Ref\": \"AWS::Partition\",\n              },\n              \":cloudfront::\",\n              {\n                \"Ref\": \"AWS::AccountId\",\n              },\n              \":distribution/\",\n              {\n                \"Ref\": \"Webapp107041BD\",\n              },\n            ],\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Permission\",\n    },\n    \"WebappMigrationRunnerAC67C012\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n        \"WebappMigrationRunnerServiceRoleE27E1F7A\",\n      ],\n      \"Properties\": {\n        \"Architectures\": [\n          \"arm64\",\n        ],\n        \"Code\": {\n          \"ImageUri\": {\n            \"Fn::Sub\": \"REDACTED\",\n          },\n        },\n        \"Environment\": {\n          \"Variables\": {\n            \"DATABASE_ENGINE\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_HOST\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Address\",\n              ],\n            },\n            \"DATABASE_NAME\": \"main\",\n            \"DATABASE_OPTION\": \"?connection_limit=1&connect_timeout=30\",\n            \"DATABASE_PASSWORD\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_PORT\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Port\",\n              ],\n            },\n            \"DATABASE_URL\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}://{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}:{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}@\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Address\",\n                    ],\n                  },\n                  \":\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Port\",\n                    ],\n                  },\n                  \"/main?connection_limit=1&connect_timeout=30\",\n                ],\n              ],\n            },\n            \"DATABASE_USER\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}\",\n                ],\n              ],\n            },\n          },\n        },\n        \"ImageConfig\": {\n          \"Command\": [\n            \"migration-runner.handler\",\n          ],\n        },\n        \"LoggingConfig\": {\n          \"LogGroup\": {\n            \"Ref\": \"WebappMigrationRunnerLogsD9A84B90\",\n          },\n        },\n        \"MemorySize\": 256,\n        \"PackageType\": \"Image\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"WebappMigrationRunnerServiceRoleE27E1F7A\",\n            \"Arn\",\n          ],\n        },\n        \"Timeout\": 300,\n        \"VpcConfig\": {\n          \"SecurityGroupIds\": [\n            {\n              \"Fn::GetAtt\": [\n                \"WebappMigrationRunnerSecurityGroup7F0DF264\",\n                \"GroupId\",\n              ],\n            },\n          ],\n          \"SubnetIds\": [\n            {\n              \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n            },\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"WebappMigrationRunnerCurrentVersionREDACTED\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"FunctionName\": {\n          \"Ref\": \"WebappMigrationRunnerAC67C012\",\n        },\n      },\n      \"Type\": \"AWS::Lambda::Version\",\n    },\n    \"WebappMigrationRunnerLogsD9A84B90\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"RetentionInDays\": 7,\n      },\n      \"Type\": \"AWS::Logs::LogGroup\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappMigrationRunnerSecurityGroup7F0DF264\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"GroupDescription\": \"Automatic security group for Lambda Function ServerlessWebappStarterKitStackWebappMigrationRunner45EAC73E\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"WebappMigrationRunnerServiceRoleE27E1F7A\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"WebappMigrationTrigger42AFC1D9\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"DatabaseClusterLogRetentionpostgresql025D39CE\",\n        \"DatabaseCluster5B53A178\",\n        \"DatabaseClusterSecretAttachmentDC8466C0\",\n        \"DatabaseClusterSecretD1FB634F\",\n        \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackAsyncJobHandlerSecurityGroup5220DFB3IndirectPort9323962E\",\n        \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackDatabaseBastionHostInstanceSecurityGroup4F0DD25BIndirectPort8AFD9922\",\n        \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackWebappHandlerSecurityGroupA009CF4AIndirectPort7311063E\",\n        \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackWebappMigrationRunnerSecurityGroupC0959349IndirectPortDCF4A356\",\n        \"DatabaseClusterSecurityGroupFEF1426A\",\n        \"DatabaseClusterSubnets5540150D\",\n        \"DatabaseClusterWriterD43085C6\",\n      ],\n      \"Properties\": {\n        \"ExecuteOnHandlerChange\": true,\n        \"HandlerArn\": {\n          \"Ref\": \"WebappMigrationRunnerCurrentVersionREDACTED\",\n        },\n        \"InvocationType\": \"RequestResponse\",\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91\",\n            \"Arn\",\n          ],\n        },\n        \"Timeout\": \"120000\",\n      },\n      \"Type\": \"Custom::Trigger\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappOrigin1FunctionUrlOriginAccessControlEA98B600\": {\n      \"Properties\": {\n        \"OriginAccessControlConfig\": {\n          \"Name\": \"ServerlessWebappStarterKitStnctionUrlOriginAccessControl17EB4E66\",\n          \"OriginAccessControlOriginType\": \"lambda\",\n          \"SigningBehavior\": \"always\",\n          \"SigningProtocol\": \"sigv4\",\n        },\n      },\n      \"Type\": \"AWS::CloudFront::OriginAccessControl\",\n    },\n    \"WebappOrigin1InvokeFromApiForServerlessWebappStarterKitStackWebappOrigin1D7B867FF58DBB477\": {\n      \"Properties\": {\n        \"Action\": \"lambda:InvokeFunctionUrl\",\n        \"FunctionName\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandlerFunctionUrl7AEF8DEE\",\n            \"FunctionArn\",\n          ],\n        },\n        \"Principal\": \"cloudfront.amazonaws.com\",\n        \"SourceArn\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"arn:\",\n              {\n                \"Ref\": \"AWS::Partition\",\n              },\n              \":cloudfront::\",\n              {\n                \"Ref\": \"AWS::AccountId\",\n              },\n              \":distribution/\",\n              {\n                \"Ref\": \"Webapp107041BD\",\n              },\n            ],\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Permission\",\n    },\n    \"WebappOriginSourceParameterD87E143B\": {\n      \"Properties\": {\n        \"Type\": \"String\",\n        \"Value\": \"dummy\",\n      },\n      \"Type\": \"AWS::SSM::Parameter\",\n    },\n    \"WebappSharedCachePolicy14FEE4A0\": {\n      \"Properties\": {\n        \"CachePolicyConfig\": {\n          \"DefaultTTL\": 0,\n          \"MaxTTL\": 31536000,\n          \"MinTTL\": 0,\n          \"Name\": \"ServerlessWebappStarterKitStackWebappSharedCachePolicy211E133B-us-west-2\",\n          \"ParametersInCacheKeyAndForwardedToOrigin\": {\n            \"CookiesConfig\": {\n              \"CookieBehavior\": \"all\",\n            },\n            \"EnableAcceptEncodingBrotli\": true,\n            \"EnableAcceptEncodingGzip\": true,\n            \"HeadersConfig\": {\n              \"HeaderBehavior\": \"whitelist\",\n              \"Headers\": [\n                \"authorization\",\n                \"Origin\",\n                \"X-HTTP-Method-Override\",\n                \"X-HTTP-Method\",\n                \"X-Method-Override\",\n                \"x-nextjs-cache-key\",\n              ],\n            },\n            \"QueryStringsConfig\": {\n              \"QueryStringBehavior\": \"all\",\n            },\n          },\n        },\n      },\n      \"Type\": \"AWS::CloudFront::CachePolicy\",\n    },\n    \"WebappUpdateAmplifyOriginSourceParameter3F609A08\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"WebappUpdateAmplifyOriginSourceParameterCustomResourcePolicy5D80E122\",\n      ],\n      \"Properties\": {\n        \"Create\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"ssm\",\"action\":\"putParameter\",\"parameters\":{\"Name\":\"\",\n              {\n                \"Ref\": \"WebappOriginSourceParameterD87E143B\",\n              },\n              \"\",\"Value\":\"https://\",\n              {\n                \"Fn::GetAtt\": [\n                  \"Webapp107041BD\",\n                  \"DomainName\",\n                ],\n              },\n              \"\",\"Overwrite\":true},\"physicalResourceId\":{\"id\":\"\",\n              {\n                \"Ref\": \"WebappOriginSourceParameterD87E143B\",\n              },\n              \"\"}}\",\n            ],\n          ],\n        },\n        \"InstallLatestAwsSdk\": true,\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"AWS679f53fac002430cb0da5b7982bd22872D164C4C\",\n            \"Arn\",\n          ],\n        },\n        \"Update\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"ssm\",\"action\":\"putParameter\",\"parameters\":{\"Name\":\"\",\n              {\n                \"Ref\": \"WebappOriginSourceParameterD87E143B\",\n              },\n              \"\",\"Value\":\"https://\",\n              {\n                \"Fn::GetAtt\": [\n                  \"Webapp107041BD\",\n                  \"DomainName\",\n                ],\n              },\n              \"\",\"Overwrite\":true},\"physicalResourceId\":{\"id\":\"\",\n              {\n                \"Ref\": \"WebappOriginSourceParameterD87E143B\",\n              },\n              \"\"}}\",\n            ],\n          ],\n        },\n      },\n      \"Type\": \"Custom::AWS\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappUpdateAmplifyOriginSourceParameterCustomResourcePolicy5D80E122\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"ssm:PutParameter\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":ssm:us-west-2:123456789012:parameter/\",\n                    {\n                      \"Ref\": \"WebappOriginSourceParameterD87E143B\",\n                    },\n                  ],\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"WebappUpdateAmplifyOriginSourceParameterCustomResourcePolicy5D80E122\",\n        \"Roles\": [\n          {\n            \"Ref\": \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n  },\n  \"Rules\": {\n    \"CheckBootstrapVersion\": {\n      \"Assertions\": [\n        {\n          \"Assert\": {\n            \"Fn::Not\": [\n              {\n                \"Fn::Contains\": [\n                  [\n                    \"1\",\n                    \"2\",\n                    \"3\",\n                    \"4\",\n                    \"5\",\n                  ],\n                  {\n                    \"Ref\": \"BootstrapVersion\",\n                  },\n                ],\n              },\n            ],\n          },\n          \"AssertDescription\": \"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.\",\n        },\n      ],\n    },\n  },\n}\n`;\n"
  },
  {
    "path": "cdk/test/__snapshots__/serverless-fullstack-webapp-starter-kit.test.ts.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Snapshot test 1`] = `\n{\n  \"Parameters\": {\n    \"BootstrapVersion\": {\n      \"Default\": \"/cdk-bootstrap/hnb659fds/version\",\n      \"Description\": \"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]\",\n      \"Type\": \"AWS::SSM::Parameter::Value<String>\",\n    },\n  },\n  \"Resources\": {\n    \"CertificateV29EE77EAF\": {\n      \"Properties\": {\n        \"DomainName\": \"*.example.com\",\n        \"DomainValidationOptions\": [\n          {\n            \"DomainName\": \"example.com\",\n            \"HostedZoneId\": \"DUMMY\",\n          },\n        ],\n        \"SubjectAlternativeNames\": [\n          \"example.com\",\n        ],\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitUsEast1Stack/CertificateV2\",\n          },\n        ],\n        \"ValidationMethod\": \"DNS\",\n      },\n      \"Type\": \"AWS::CertificateManager::Certificate\",\n    },\n    \"CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A\": {\n      \"DependsOn\": [\n        \"CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-east-1\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"__entrypoint__.handler\",\n        \"MemorySize\": 128,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Sub\": \"arn:\\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n          },\n        ],\n        \"Policies\": [\n          {\n            \"PolicyDocument\": {\n              \"Statement\": [\n                {\n                  \"Action\": [\n                    \"ssm:DeleteParameters\",\n                    \"ssm:ListTagsForResource\",\n                    \"ssm:GetParameters\",\n                    \"ssm:PutParameter\",\n                  ],\n                  \"Effect\": \"Allow\",\n                  \"Resource\": [\n                    {\n                      \"Fn::Join\": [\n                        \"\",\n                        [\n                          \"arn:\",\n                          {\n                            \"Ref\": \"AWS::Partition\",\n                          },\n                          \":ssm:us-west-2:123456789012:parameter/cdk/exports/*\",\n                        ],\n                      ],\n                    },\n                  ],\n                },\n              ],\n              \"Version\": \"2012-10-17\",\n            },\n            \"PolicyName\": \"Inline\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"ExportsWriteruswest209BD44F0A7CF058B\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A\",\n            \"Arn\",\n          ],\n        },\n        \"WriterProps\": {\n          \"exports\": {\n            \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefCertificateV29EE77EAF5C85697F\": {\n              \"Ref\": \"CertificateV29EE77EAF\",\n            },\n            \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\": {\n              \"Ref\": \"SignPayloadHandlerFunctionVersionF9FE430A\",\n            },\n          },\n          \"region\": \"us-west-2\",\n        },\n      },\n      \"Type\": \"Custom::CrossRegionExportWriter\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"Record83264F3E\": {\n      \"Properties\": {\n        \"HostedZoneId\": \"DUMMY\",\n        \"Name\": \"example.com.\",\n        \"ResourceRecords\": [\n          \"8.8.8.8\",\n        ],\n        \"TTL\": \"1800\",\n        \"Type\": \"A\",\n      },\n      \"Type\": \"AWS::Route53::RecordSet\",\n    },\n    \"SignPayloadHandlerCFEAA14C\": {\n      \"DependsOn\": [\n        \"SignPayloadHandlerServiceRole29261232\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-east-1\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"SignPayloadHandlerServiceRole29261232\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"SignPayloadHandlerCurrentVersionREDACTED\": {\n      \"Properties\": {\n        \"FunctionName\": {\n          \"Ref\": \"SignPayloadHandlerCFEAA14C\",\n        },\n      },\n      \"Type\": \"AWS::Lambda::Version\",\n    },\n    \"SignPayloadHandlerFunctionVersionF9FE430A\": {\n      \"Properties\": {\n        \"Type\": \"String\",\n        \"Value\": {\n          \"Ref\": \"SignPayloadHandlerCurrentVersionREDACTED\",\n        },\n      },\n      \"Type\": \"AWS::SSM::Parameter\",\n    },\n    \"SignPayloadHandlerServiceRole29261232\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"edgelambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n  },\n  \"Rules\": {\n    \"CheckBootstrapVersion\": {\n      \"Assertions\": [\n        {\n          \"Assert\": {\n            \"Fn::Not\": [\n              {\n                \"Fn::Contains\": [\n                  [\n                    \"1\",\n                    \"2\",\n                    \"3\",\n                    \"4\",\n                    \"5\",\n                  ],\n                  {\n                    \"Ref\": \"BootstrapVersion\",\n                  },\n                ],\n              },\n            ],\n          },\n          \"AssertDescription\": \"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.\",\n        },\n      ],\n    },\n  },\n}\n`;\n\nexports[`Snapshot test 2`] = `\n{\n  \"Description\": \"Serverless fullstack webapp stack (uksb-1tupboc47)\",\n  \"Mappings\": {\n    \"AWSCloudFrontPartitionHostedZoneIdMap\": {\n      \"aws\": {\n        \"zoneId\": \"Z2FDTNDATAQYW2\",\n      },\n      \"aws-cn\": {\n        \"zoneId\": \"Z3RFFRIM2A3IF5\",\n      },\n    },\n  },\n  \"Outputs\": {\n    \"AsyncJobHandlerArnCA46B385\": {\n      \"Value\": {\n        \"Fn::GetAtt\": [\n          \"AsyncJobHandler438266BD\",\n          \"Arn\",\n        ],\n      },\n    },\n    \"AuthUserPoolClientId8216BF9A\": {\n      \"Value\": {\n        \"Ref\": \"AuthUserPoolClientC635291F\",\n      },\n    },\n    \"AuthUserPoolDomainName8D4A2606\": {\n      \"Value\": \"auth.example.com\",\n    },\n    \"AuthUserPoolIdC0605E59\": {\n      \"Value\": {\n        \"Ref\": \"AuthUserPool8115E87F\",\n      },\n    },\n    \"DatabaseBastionHostBastionHostId1600F37C\": {\n      \"Description\": \"Instance ID of the bastion host. Use this to connect via SSM Session Manager\",\n      \"Value\": {\n        \"Ref\": \"DatabaseBastionHost4C4FAD9C\",\n      },\n    },\n    \"DatabaseDatabaseSecretsCommandF4A622EB\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"aws secretsmanager get-secret-value --secret-id \",\n            {\n              \"Fn::Join\": [\n                \"-\",\n                [\n                  {\n                    \"Fn::Select\": [\n                      0,\n                      {\n                        \"Fn::Split\": [\n                          \"-\",\n                          {\n                            \"Fn::Select\": [\n                              6,\n                              {\n                                \"Fn::Split\": [\n                                  \":\",\n                                  {\n                                    \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n                                  },\n                                ],\n                              },\n                            ],\n                          },\n                        ],\n                      },\n                    ],\n                  },\n                  {\n                    \"Fn::Select\": [\n                      1,\n                      {\n                        \"Fn::Split\": [\n                          \"-\",\n                          {\n                            \"Fn::Select\": [\n                              6,\n                              {\n                                \"Fn::Split\": [\n                                  \":\",\n                                  {\n                                    \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n                                  },\n                                ],\n                              },\n                            ],\n                          },\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              ],\n            },\n            \" --region us-west-2\",\n          ],\n        ],\n      },\n    },\n    \"DatabasePortForwardCommandC3718B89\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"aws ssm start-session --region us-west-2 --target \",\n            {\n              \"Ref\": \"DatabaseBastionHost4C4FAD9C\",\n            },\n            \" --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{\"portNumber\":[\"\",\n            {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Port\",\n              ],\n            },\n            \"\"], \"localPortNumber\":[\"5433\"], \"host\": [\"\",\n            {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Address\",\n              ],\n            },\n            \"\"]}'\",\n          ],\n        ],\n      },\n    },\n    \"EventBusHttpEndpoint1C68A807\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"https://\",\n            {\n              \"Fn::GetAtt\": [\n                \"EventBusApi6E8C7C94\",\n                \"Dns.Http\",\n              ],\n            },\n          ],\n        ],\n      },\n    },\n    \"FrontendDomainName\": {\n      \"Value\": \"https://web.example.com\",\n    },\n    \"MigrationCommand\": {\n      \"Value\": {\n        \"Fn::Join\": [\n          \"\",\n          [\n            \"aws lambda invoke --function-name \",\n            {\n              \"Ref\": \"WebappMigrationRunnerAC67C012\",\n            },\n            \" --payload '{\"command\":\"deploy\"}' --cli-binary-format raw-in-base64-out /dev/stdout\",\n          ],\n        ],\n      },\n    },\n    \"MigrationFunctionName\": {\n      \"Value\": {\n        \"Ref\": \"WebappMigrationRunnerAC67C012\",\n      },\n    },\n  },\n  \"Parameters\": {\n    \"BootstrapVersion\": {\n      \"Default\": \"/cdk-bootstrap/hnb659fds/version\",\n      \"Description\": \"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]\",\n      \"Type\": \"AWS::SSM::Parameter::Value<String>\",\n    },\n    \"SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61arm64C96584B6F00A464EAD1953AFF4B05118Parameter\": {\n      \"Default\": \"/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64\",\n      \"Type\": \"AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>\",\n    },\n  },\n  \"Resources\": {\n    \"AWS679f53fac002430cb0da5b7982bd22872D164C4C\": {\n      \"DependsOn\": [\n        \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 120,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91\": {\n      \"DependsOn\": [\n        \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"__entrypoint__.handler\",\n        \"MemorySize\": 128,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderRoleE18FAF0A\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Sub\": \"arn:\\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n          },\n        ],\n        \"Policies\": [\n          {\n            \"PolicyDocument\": {\n              \"Statement\": [\n                {\n                  \"Action\": [\n                    \"lambda:InvokeFunction\",\n                  ],\n                  \"Effect\": \"Allow\",\n                  \"Resource\": [\n                    {\n                      \"Fn::Join\": [\n                        \"\",\n                        [\n                          {\n                            \"Fn::GetAtt\": [\n                              \"WebappMigrationRunnerAC67C012\",\n                              \"Arn\",\n                            ],\n                          },\n                          \":*\",\n                        ],\n                      ],\n                    },\n                  ],\n                },\n              ],\n              \"Version\": \"2012-10-17\",\n            },\n            \"PolicyName\": \"Inline\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"AccessLogBucketAutoDeleteObjectsCustomResource01AB31E8\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"AccessLogBucketPolicyF52D2D01\",\n      ],\n      \"Properties\": {\n        \"BucketName\": {\n          \"Ref\": \"AccessLogBucketDA470295\",\n        },\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F\",\n            \"Arn\",\n          ],\n        },\n      },\n      \"Type\": \"Custom::S3AutoDeleteObjects\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AccessLogBucketDA470295\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"BucketEncryption\": {\n          \"ServerSideEncryptionConfiguration\": [\n            {\n              \"ServerSideEncryptionByDefault\": {\n                \"SSEAlgorithm\": \"AES256\",\n              },\n            },\n          ],\n        },\n        \"OwnershipControls\": {\n          \"Rules\": [\n            {\n              \"ObjectOwnership\": \"ObjectWriter\",\n            },\n          ],\n        },\n        \"PublicAccessBlockConfiguration\": {\n          \"BlockPublicAcls\": true,\n          \"BlockPublicPolicy\": true,\n          \"IgnorePublicAcls\": true,\n          \"RestrictPublicBuckets\": true,\n        },\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:auto-delete-objects\",\n            \"Value\": \"true\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::S3::Bucket\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AccessLogBucketPolicyF52D2D01\": {\n      \"Properties\": {\n        \"Bucket\": {\n          \"Ref\": \"AccessLogBucketDA470295\",\n        },\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"s3:*\",\n              \"Condition\": {\n                \"Bool\": {\n                  \"aws:SecureTransport\": \"false\",\n                },\n              },\n              \"Effect\": \"Deny\",\n              \"Principal\": {\n                \"AWS\": \"*\",\n              },\n              \"Resource\": [\n                {\n                  \"Fn::GetAtt\": [\n                    \"AccessLogBucketDA470295\",\n                    \"Arn\",\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      {\n                        \"Fn::GetAtt\": [\n                          \"AccessLogBucketDA470295\",\n                          \"Arn\",\n                        ],\n                      },\n                      \"/*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n            {\n              \"Action\": [\n                \"s3:PutBucketPolicy\",\n                \"s3:GetBucket*\",\n                \"s3:List*\",\n                \"s3:DeleteObject*\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"AWS\": {\n                  \"Fn::GetAtt\": [\n                    \"CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092\",\n                    \"Arn\",\n                  ],\n                },\n              },\n              \"Resource\": [\n                {\n                  \"Fn::GetAtt\": [\n                    \"AccessLogBucketDA470295\",\n                    \"Arn\",\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      {\n                        \"Fn::GetAtt\": [\n                          \"AccessLogBucketDA470295\",\n                          \"Arn\",\n                        ],\n                      },\n                      \"/*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n      },\n      \"Type\": \"AWS::S3::BucketPolicy\",\n    },\n    \"AsyncJobHandler438266BD\": {\n      \"DependsOn\": [\n        \"AsyncJobHandlerServiceRoleDefaultPolicy0B2DEDB5\",\n        \"AsyncJobHandlerServiceRoleFE9F530F\",\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"Architectures\": [\n          \"arm64\",\n        ],\n        \"Code\": {\n          \"ImageUri\": {\n            \"Fn::Sub\": \"REDACTED\",\n          },\n        },\n        \"Environment\": {\n          \"Variables\": {\n            \"DATABASE_ENGINE\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_HOST\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Address\",\n              ],\n            },\n            \"DATABASE_NAME\": \"main\",\n            \"DATABASE_OPTION\": \"?connection_limit=1&connect_timeout=30\",\n            \"DATABASE_PASSWORD\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_PORT\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Port\",\n              ],\n            },\n            \"DATABASE_URL\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}://{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}:{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}@\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Address\",\n                    ],\n                  },\n                  \":\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Port\",\n                    ],\n                  },\n                  \"/main?connection_limit=1&connect_timeout=30\",\n                ],\n              ],\n            },\n            \"DATABASE_USER\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}\",\n                ],\n              ],\n            },\n            \"EVENT_HTTP_ENDPOINT\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"https://\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"EventBusApi6E8C7C94\",\n                      \"Dns.Http\",\n                    ],\n                  },\n                ],\n              ],\n            },\n          },\n        },\n        \"ImageConfig\": {\n          \"Command\": [\n            \"async-job-runner.handler\",\n          ],\n        },\n        \"LoggingConfig\": {\n          \"LogGroup\": {\n            \"Ref\": \"AsyncJobHandlerLogs20DFEE3E\",\n          },\n        },\n        \"MemorySize\": 256,\n        \"PackageType\": \"Image\",\n        \"ReservedConcurrentExecutions\": 1,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"AsyncJobHandlerServiceRoleFE9F530F\",\n            \"Arn\",\n          ],\n        },\n        \"Timeout\": 600,\n        \"VpcConfig\": {\n          \"SecurityGroupIds\": [\n            {\n              \"Fn::GetAtt\": [\n                \"AsyncJobHandlerSecurityGroupF59812E6\",\n                \"GroupId\",\n              ],\n            },\n          ],\n          \"SubnetIds\": [\n            {\n              \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n            },\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"AsyncJobHandlerLogs20DFEE3E\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"RetentionInDays\": 7,\n      },\n      \"Type\": \"AWS::Logs::LogGroup\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AsyncJobHandlerSecurityGroupF59812E6\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"GroupDescription\": \"Automatic security group for Lambda Function ServerlessWebappStarterKitStackAsyncJobHandlerF20B94EA\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"AsyncJobHandlerServiceRoleDefaultPolicy0B2DEDB5\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"appsync:EventPublish\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":appsync:us-west-2:123456789012:apis/\",\n                    {\n                      \"Fn::GetAtt\": [\n                        \"EventBusApi6E8C7C94\",\n                        \"ApiId\",\n                      ],\n                    },\n                    \"/channelNamespace/*\",\n                  ],\n                ],\n              },\n            },\n            {\n              \"Action\": [\n                \"translate:TranslateText\",\n                \"comprehend:DetectDominantLanguage\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": \"*\",\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"AsyncJobHandlerServiceRoleDefaultPolicy0B2DEDB5\",\n        \"Roles\": [\n          {\n            \"Ref\": \"AsyncJobHandlerServiceRoleFE9F530F\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"AsyncJobHandlerServiceRoleFE9F530F\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"AsyncJobSampleJob3C1EBA2C\": {\n      \"Properties\": {\n        \"FlexibleTimeWindow\": {\n          \"Mode\": \"OFF\",\n        },\n        \"ScheduleExpression\": \"cron(0 0 1 * ? *)\",\n        \"ScheduleExpressionTimezone\": \"Etc/UTC\",\n        \"State\": \"ENABLED\",\n        \"Target\": {\n          \"Arn\": {\n            \"Fn::GetAtt\": [\n              \"AsyncJobHandler438266BD\",\n              \"Arn\",\n            ],\n          },\n          \"Input\": \"{\"jobType\":\"SampleJob\"}\",\n          \"RetryPolicy\": {\n            \"MaximumEventAgeInSeconds\": 86400,\n            \"MaximumRetryAttempts\": 5,\n          },\n          \"RoleArn\": {\n            \"Fn::GetAtt\": [\n              \"SchedulerRoleForTarget44ece2CFC6840F\",\n              \"Arn\",\n            ],\n          },\n        },\n      },\n      \"Type\": \"AWS::Scheduler::Schedule\",\n    },\n    \"AuthBranding34BB87FD\": {\n      \"Properties\": {\n        \"ClientId\": {\n          \"Ref\": \"AuthUserPoolClientC635291F\",\n        },\n        \"UseCognitoProvidedValues\": true,\n        \"UserPoolId\": {\n          \"Ref\": \"AuthUserPool8115E87F\",\n        },\n      },\n      \"Type\": \"AWS::Cognito::ManagedLoginBranding\",\n    },\n    \"AuthCognitoDomainRecordBA8AA168\": {\n      \"Properties\": {\n        \"HostedZoneId\": \"DUMMY\",\n        \"Name\": \"auth.example.com.\",\n        \"ResourceRecords\": [\n          {\n            \"Fn::GetAtt\": [\n              \"AuthUserPoolCognitoDomainAD9D79E1\",\n              \"CloudFrontDistribution\",\n            ],\n          },\n        ],\n        \"TTL\": \"1800\",\n        \"Type\": \"CNAME\",\n      },\n      \"Type\": \"AWS::Route53::RecordSet\",\n    },\n    \"AuthUserPool8115E87F\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"AccountRecoverySetting\": {\n          \"RecoveryMechanisms\": [\n            {\n              \"Name\": \"verified_phone_number\",\n              \"Priority\": 1,\n            },\n            {\n              \"Name\": \"verified_email\",\n              \"Priority\": 2,\n            },\n          ],\n        },\n        \"AdminCreateUserConfig\": {\n          \"AllowAdminCreateUserOnly\": true,\n        },\n        \"AutoVerifiedAttributes\": [\n          \"email\",\n        ],\n        \"EmailVerificationMessage\": \"The verification code to your new account is {####}\",\n        \"EmailVerificationSubject\": \"Verify your new account\",\n        \"Policies\": {\n          \"PasswordPolicy\": {\n            \"MinimumLength\": 8,\n            \"RequireNumbers\": true,\n            \"RequireSymbols\": true,\n            \"RequireUppercase\": true,\n          },\n        },\n        \"SmsVerificationMessage\": \"The verification code to your new account is {####}\",\n        \"UsernameAttributes\": [\n          \"email\",\n        ],\n        \"VerificationMessageTemplate\": {\n          \"DefaultEmailOption\": \"CONFIRM_WITH_CODE\",\n          \"EmailMessage\": \"The verification code to your new account is {####}\",\n          \"EmailSubject\": \"Verify your new account\",\n          \"SmsMessage\": \"The verification code to your new account is {####}\",\n        },\n      },\n      \"Type\": \"AWS::Cognito::UserPool\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"AuthUserPoolClientC635291F\": {\n      \"Properties\": {\n        \"AllowedOAuthFlows\": [\n          \"code\",\n        ],\n        \"AllowedOAuthFlowsUserPoolClient\": true,\n        \"AllowedOAuthScopes\": [\n          \"profile\",\n          \"phone\",\n          \"email\",\n          \"openid\",\n          \"aws.cognito.signin.user.admin\",\n        ],\n        \"CallbackURLs\": [\n          \"http://localhost:3010/api/auth/sign-in-callback\",\n          \"https://web.example.com/api/auth/sign-in-callback\",\n        ],\n        \"ExplicitAuthFlows\": [\n          \"ALLOW_USER_PASSWORD_AUTH\",\n          \"ALLOW_USER_SRP_AUTH\",\n          \"ALLOW_REFRESH_TOKEN_AUTH\",\n        ],\n        \"IdTokenValidity\": 1440,\n        \"LogoutURLs\": [\n          \"http://localhost:3010/api/auth/sign-out-callback\",\n          \"https://web.example.com/api/auth/sign-out-callback\",\n        ],\n        \"SupportedIdentityProviders\": [\n          \"COGNITO\",\n        ],\n        \"TokenValidityUnits\": {\n          \"IdToken\": \"minutes\",\n        },\n        \"UserPoolId\": {\n          \"Ref\": \"AuthUserPool8115E87F\",\n        },\n      },\n      \"Type\": \"AWS::Cognito::UserPoolClient\",\n    },\n    \"AuthUserPoolCognitoDomainAD9D79E1\": {\n      \"Properties\": {\n        \"CustomDomainConfig\": {\n          \"CertificateArn\": {\n            \"Fn::GetAtt\": [\n              \"ExportsReader8B249524\",\n              \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefCertificateV29EE77EAF5C85697F\",\n            ],\n          },\n        },\n        \"Domain\": \"auth.example.com\",\n        \"ManagedLoginVersion\": 2,\n        \"UserPoolId\": {\n          \"Ref\": \"AuthUserPool8115E87F\",\n        },\n      },\n      \"Type\": \"AWS::Cognito::UserPoolDomain\",\n    },\n    \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\": {\n      \"Properties\": {\n        \"Artifacts\": {\n          \"Type\": \"NO_ARTIFACTS\",\n        },\n        \"Cache\": {\n          \"Type\": \"NO_CACHE\",\n        },\n        \"EncryptionKey\": \"alias/aws/s3\",\n        \"Environment\": {\n          \"ComputeType\": \"BUILD_GENERAL1_SMALL\",\n          \"Image\": \"aws/codebuild/amazonlinux2-aarch64-standard:3.0\",\n          \"ImagePullCredentialsType\": \"CODEBUILD\",\n          \"PrivilegedMode\": true,\n          \"Type\": \"ARM_CONTAINER\",\n        },\n        \"ServiceRole\": {\n          \"Fn::GetAtt\": [\n            \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleC5F7BBFE\",\n            \"Arn\",\n          ],\n        },\n        \"Source\": {\n          \"BuildSpec\": \"{\n  \"version\": \"0.2\",\n  \"phases\": {\n    \"build\": {\n      \"commands\": [\n        \"current_dir=$(pwd)\",\n        \"echo \\\\\"$input\\\\\"\",\n        \"mkdir workdir\",\n        \"cd workdir\",\n        \"aws s3 cp \\\\\"$sourceS3Url\\\\\" temp.zip\",\n        \"unzip temp.zip\",\n        \"ls -la\",\n        \"aws ecr get-login-password | docker login --username AWS --password-stdin $repositoryAuthUri\",\n        \"aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws\",\n        \"docker buildx create --use\",\n        \"docker buildx ls\",\n        \"eval \\\\\"$buildCommand\\\\\"\"\n      ]\n    },\n    \"post_build\": {\n      \"commands\": [\n        \"echo Build completed on \\`date\\`\",\n        \"\\\\nSTATUS='SUCCESS'\\\\nif [ $CODEBUILD_BUILD_SUCCEEDING -ne 1 ] # Test if the build is failing\\\\nthen\\\\nSTATUS='FAILED'\\\\nREASON=\\\\\"ContainerImageBuild failed. See CloudWatch Log stream for the detailed reason: \\\\nhttps://$AWS_REGION.console.aws.amazon.com/cloudwatch/home?region=$AWS_REGION#logsV2:log-groups/log-group/\\\\\\\\$252Faws\\\\\\\\$252Fcodebuild\\\\\\\\$252F$projectName/log-events/$CODEBUILD_LOG_PATH\\\\\"\\\\nfi\\\\ncat <<EOF > payload.json\\\\n{\\\\n  \\\\\"StackId\\\\\": \\\\\"$stackId\\\\\",\\\\n  \\\\\"RequestId\\\\\": \\\\\"$requestId\\\\\",\\\\n  \\\\\"LogicalResourceId\\\\\":\\\\\"$logicalResourceId\\\\\",\\\\n  \\\\\"PhysicalResourceId\\\\\": \\\\\"$imageTag\\\\\",\\\\n  \\\\\"Status\\\\\": \\\\\"$STATUS\\\\\",\\\\n  \\\\\"Reason\\\\\": \\\\\"$REASON\\\\\",\\\\n  \\\\\"Data\\\\\": {\\\\n    \\\\\"ImageTag\\\\\": \\\\\"$imageTag\\\\\"\\\\n  }\\\\n}\\\\nEOF\\\\ncurl -v -i -X PUT -H 'Content-Type:' -d \\\\\"@payload.json\\\\\" \\\\\"$responseURL\\\\\"\\\\n              \"\n      ]\n    }\n  }\n}\",\n          \"Type\": \"NO_SOURCE\",\n        },\n      },\n      \"Type\": \"AWS::CodeBuild::Project\",\n    },\n    \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleC5F7BBFE\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"codebuild.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/AmazonElasticContainerRegistryPublicReadOnly\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleDefaultPolicy2316728F\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": [\n                \"logs:CreateLogGroup\",\n                \"logs:CreateLogStream\",\n                \"logs:PutLogEvents\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": [\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      \"arn:\",\n                      {\n                        \"Ref\": \"AWS::Partition\",\n                      },\n                      \":logs:us-west-2:123456789012:log-group:/aws/codebuild/\",\n                      {\n                        \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n                      },\n                    ],\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      \"arn:\",\n                      {\n                        \"Ref\": \"AWS::Partition\",\n                      },\n                      \":logs:us-west-2:123456789012:log-group:/aws/codebuild/\",\n                      {\n                        \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n                      },\n                      \":*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n            {\n              \"Action\": [\n                \"codebuild:CreateReportGroup\",\n                \"codebuild:CreateReport\",\n                \"codebuild:UpdateReport\",\n                \"codebuild:BatchPutTestCases\",\n                \"codebuild:BatchPutCodeCoverages\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":codebuild:us-west-2:123456789012:report-group/\",\n                    {\n                      \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n                    },\n                    \"-*\",\n                  ],\n                ],\n              },\n            },\n            {\n              \"Action\": [\n                \"ecr:BatchCheckLayerAvailability\",\n                \"ecr:GetDownloadUrlForLayer\",\n                \"ecr:BatchGetImage\",\n                \"ecr:CompleteLayerUpload\",\n                \"ecr:UploadLayerPart\",\n                \"ecr:InitiateLayerUpload\",\n                \"ecr:PutImage\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::GetAtt\": [\n                  \"WebappBuildRepository4C93D48D\",\n                  \"Arn\",\n                ],\n              },\n            },\n            {\n              \"Action\": \"ecr:GetAuthorizationToken\",\n              \"Effect\": \"Allow\",\n              \"Resource\": \"*\",\n            },\n            {\n              \"Action\": \"ecr:DescribeImages\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::GetAtt\": [\n                  \"WebappBuildRepository4C93D48D\",\n                  \"Arn\",\n                ],\n              },\n            },\n            {\n              \"Action\": [\n                \"s3:GetObject*\",\n                \"s3:GetBucket*\",\n                \"s3:List*\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": [\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      \"arn:\",\n                      {\n                        \"Ref\": \"AWS::Partition\",\n                      },\n                      \":s3:::cdk-hnb659fds-assets-123456789012-us-west-2\",\n                    ],\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      \"arn:\",\n                      {\n                        \"Ref\": \"AWS::Partition\",\n                      },\n                      \":s3:::cdk-hnb659fds-assets-123456789012-us-west-2/*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleDefaultPolicy2316728F\",\n        \"Roles\": [\n          {\n            \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleC5F7BBFE\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68\": {\n      \"DependsOn\": [\n        \"CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"__entrypoint__.handler\",\n        \"MemorySize\": 128,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Sub\": \"arn:\\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n          },\n        ],\n        \"Policies\": [\n          {\n            \"PolicyDocument\": {\n              \"Statement\": [\n                {\n                  \"Action\": [\n                    \"ssm:AddTagsToResource\",\n                    \"ssm:RemoveTagsFromResource\",\n                    \"ssm:GetParameters\",\n                  ],\n                  \"Effect\": \"Allow\",\n                  \"Resource\": {\n                    \"Fn::Join\": [\n                      \"\",\n                      [\n                        \"arn:\",\n                        {\n                          \"Ref\": \"AWS::Partition\",\n                        },\n                        \":ssm:us-west-2:123456789012:parameter/cdk/exports/ServerlessWebappStarterKitStack/*\",\n                      ],\n                    ],\n                  },\n                },\n              ],\n              \"Version\": \"2012-10-17\",\n            },\n            \"PolicyName\": \"Inline\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F\": {\n      \"DependsOn\": [\n        \"CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Description\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"Lambda function for auto-deleting objects in \",\n              {\n                \"Ref\": \"AccessLogBucketDA470295\",\n              },\n              \" S3 bucket.\",\n            ],\n          ],\n        },\n        \"Handler\": \"index.handler\",\n        \"MemorySize\": 128,\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Sub\": \"arn:\\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"DatabaseBastionHost4C4FAD9C\": {\n      \"DependsOn\": [\n        \"DatabaseBastionHostInstanceRoleDefaultPolicy15D8D0EA\",\n        \"DatabaseBastionHostInstanceRole87A429B0\",\n      ],\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1a\",\n        \"BlockDeviceMappings\": [\n          {\n            \"DeviceName\": \"/dev/sdf\",\n            \"Ebs\": {\n              \"Encrypted\": true,\n              \"VolumeSize\": 8,\n            },\n          },\n        ],\n        \"IamInstanceProfile\": {\n          \"Ref\": \"DatabaseBastionHostInstanceProfile0F4F3411\",\n        },\n        \"ImageId\": {\n          \"Ref\": \"SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61arm64C96584B6F00A464EAD1953AFF4B05118Parameter\",\n        },\n        \"InstanceType\": \"t4g.nano\",\n        \"SecurityGroupIds\": [\n          {\n            \"Fn::GetAtt\": [\n              \"DatabaseBastionHostInstanceSecurityGroup39D7809A\",\n              \"GroupId\",\n            ],\n          },\n        ],\n        \"SubnetId\": {\n          \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n        },\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"BastionHost\",\n          },\n        ],\n        \"UserData\": {\n          \"Fn::Base64\": \"#!/bin/bash\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Instance\",\n    },\n    \"DatabaseBastionHostInstanceProfile0F4F3411\": {\n      \"Properties\": {\n        \"Roles\": [\n          {\n            \"Ref\": \"DatabaseBastionHostInstanceRole87A429B0\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::InstanceProfile\",\n    },\n    \"DatabaseBastionHostInstanceRole87A429B0\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"ec2.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"BastionHost\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"DatabaseBastionHostInstanceRoleDefaultPolicy15D8D0EA\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": [\n                \"ssmmessages:*\",\n                \"ssm:UpdateInstanceInformation\",\n                \"ec2messages:*\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": \"*\",\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"DatabaseBastionHostInstanceRoleDefaultPolicy15D8D0EA\",\n        \"Roles\": [\n          {\n            \"Ref\": \"DatabaseBastionHostInstanceRole87A429B0\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"DatabaseBastionHostInstanceSecurityGroup39D7809A\": {\n      \"Properties\": {\n        \"GroupDescription\": \"ServerlessWebappStarterKitStack/Database/BastionHost/Resource/InstanceSecurityGroup\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"BastionHost\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"DatabaseCluster5B53A178\": {\n      \"DeletionPolicy\": \"Snapshot\",\n      \"Properties\": {\n        \"CopyTagsToSnapshot\": true,\n        \"DBClusterParameterGroupName\": {\n          \"Ref\": \"DatabaseParameterGroup2A921026\",\n        },\n        \"DBSubnetGroupName\": {\n          \"Ref\": \"DatabaseClusterSubnets5540150D\",\n        },\n        \"EnableCloudwatchLogsExports\": [\n          \"postgresql\",\n        ],\n        \"EnableHttpEndpoint\": true,\n        \"Engine\": \"aurora-postgresql\",\n        \"EngineVersion\": \"16.6\",\n        \"MasterUserPassword\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{{resolve:secretsmanager:\",\n              {\n                \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n              },\n              \":SecretString:password::}}\",\n            ],\n          ],\n        },\n        \"MasterUsername\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{{resolve:secretsmanager:\",\n              {\n                \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n              },\n              \":SecretString:username::}}\",\n            ],\n          ],\n        },\n        \"Port\": 5432,\n        \"ServerlessV2ScalingConfiguration\": {\n          \"MaxCapacity\": 2,\n          \"MinCapacity\": 0,\n        },\n        \"StorageEncrypted\": true,\n        \"VpcSecurityGroupIds\": [\n          {\n            \"Fn::GetAtt\": [\n              \"DatabaseClusterSecurityGroupFEF1426A\",\n              \"GroupId\",\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::RDS::DBCluster\",\n      \"UpdateReplacePolicy\": \"Snapshot\",\n    },\n    \"DatabaseClusterLogRetentionpostgresql025D39CE\": {\n      \"Properties\": {\n        \"LogGroupName\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"/aws/rds/cluster/\",\n              {\n                \"Ref\": \"DatabaseCluster5B53A178\",\n              },\n              \"/postgresql\",\n            ],\n          ],\n        },\n        \"RetentionInDays\": 7,\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A\",\n            \"Arn\",\n          ],\n        },\n      },\n      \"Type\": \"Custom::LogRetention\",\n    },\n    \"DatabaseClusterSecretAttachmentDC8466C0\": {\n      \"Properties\": {\n        \"SecretId\": {\n          \"Ref\": \"DatabaseClusterSecretD1FB634F\",\n        },\n        \"TargetId\": {\n          \"Ref\": \"DatabaseCluster5B53A178\",\n        },\n        \"TargetType\": \"AWS::RDS::DBCluster\",\n      },\n      \"Type\": \"AWS::SecretsManager::SecretTargetAttachment\",\n    },\n    \"DatabaseClusterSecretD1FB634F\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"Description\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"Generated by the CDK for stack: \",\n              {\n                \"Ref\": \"AWS::StackName\",\n              },\n            ],\n          ],\n        },\n        \"GenerateSecretString\": {\n          \"ExcludeCharacters\": \" %+~\\`#$&*()|[]{}:;<>?!'/@\"\\\\,=^\",\n          \"GenerateStringKey\": \"password\",\n          \"PasswordLength\": 30,\n          \"SecretStringTemplate\": \"{\"username\":\"postgres\"}\",\n        },\n      },\n      \"Type\": \"AWS::SecretsManager::Secret\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"DatabaseClusterSecurityGroupFEF1426A\": {\n      \"Properties\": {\n        \"GroupDescription\": \"RDS security group\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackAsyncJobHandlerSecurityGroup5220DFB3IndirectPort9323962E\": {\n      \"Properties\": {\n        \"Description\": \"from ServerlessWebappStarterKitStackAsyncJobHandlerSecurityGroup5220DFB3:{IndirectPort}\",\n        \"FromPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n        \"GroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseClusterSecurityGroupFEF1426A\",\n            \"GroupId\",\n          ],\n        },\n        \"IpProtocol\": \"tcp\",\n        \"SourceSecurityGroupId\": {\n          \"Fn::GetAtt\": [\n            \"AsyncJobHandlerSecurityGroupF59812E6\",\n            \"GroupId\",\n          ],\n        },\n        \"ToPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroupIngress\",\n    },\n    \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackDatabaseBastionHostInstanceSecurityGroup4F0DD25BIndirectPort8AFD9922\": {\n      \"Properties\": {\n        \"Description\": \"from ServerlessWebappStarterKitStackDatabaseBastionHostInstanceSecurityGroup4F0DD25B:{IndirectPort}\",\n        \"FromPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n        \"GroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseClusterSecurityGroupFEF1426A\",\n            \"GroupId\",\n          ],\n        },\n        \"IpProtocol\": \"tcp\",\n        \"SourceSecurityGroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseBastionHostInstanceSecurityGroup39D7809A\",\n            \"GroupId\",\n          ],\n        },\n        \"ToPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroupIngress\",\n    },\n    \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackWebappHandlerSecurityGroupA009CF4AIndirectPort7311063E\": {\n      \"Properties\": {\n        \"Description\": \"from ServerlessWebappStarterKitStackWebappHandlerSecurityGroupA009CF4A:{IndirectPort}\",\n        \"FromPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n        \"GroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseClusterSecurityGroupFEF1426A\",\n            \"GroupId\",\n          ],\n        },\n        \"IpProtocol\": \"tcp\",\n        \"SourceSecurityGroupId\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandlerSecurityGroup5451B519\",\n            \"GroupId\",\n          ],\n        },\n        \"ToPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroupIngress\",\n    },\n    \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackWebappMigrationRunnerSecurityGroupC0959349IndirectPortDCF4A356\": {\n      \"Properties\": {\n        \"Description\": \"from ServerlessWebappStarterKitStackWebappMigrationRunnerSecurityGroupC0959349:{IndirectPort}\",\n        \"FromPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n        \"GroupId\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseClusterSecurityGroupFEF1426A\",\n            \"GroupId\",\n          ],\n        },\n        \"IpProtocol\": \"tcp\",\n        \"SourceSecurityGroupId\": {\n          \"Fn::GetAtt\": [\n            \"WebappMigrationRunnerSecurityGroup7F0DF264\",\n            \"GroupId\",\n          ],\n        },\n        \"ToPort\": {\n          \"Fn::GetAtt\": [\n            \"DatabaseCluster5B53A178\",\n            \"Endpoint.Port\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroupIngress\",\n    },\n    \"DatabaseClusterSubnets5540150D\": {\n      \"Properties\": {\n        \"DBSubnetGroupDescription\": \"Subnets for Cluster database\",\n        \"SubnetIds\": [\n          {\n            \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n          },\n          {\n            \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n          },\n          {\n            \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::RDS::DBSubnetGroup\",\n    },\n    \"DatabaseClusterWriterD43085C6\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AutoMinorVersionUpgrade\": true,\n        \"DBClusterIdentifier\": {\n          \"Ref\": \"DatabaseCluster5B53A178\",\n        },\n        \"DBInstanceClass\": \"db.serverless\",\n        \"EnablePerformanceInsights\": true,\n        \"Engine\": \"aurora-postgresql\",\n        \"PerformanceInsightsRetentionPeriod\": 7,\n        \"PromotionTier\": 0,\n        \"PubliclyAccessible\": false,\n      },\n      \"Type\": \"AWS::RDS::DBInstance\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"DatabaseParameterGroup2A921026\": {\n      \"Properties\": {\n        \"Description\": \"Cluster parameter group for aurora-postgresql16\",\n        \"Family\": \"aurora-postgresql16\",\n        \"Parameters\": {\n          \"idle_session_timeout\": \"60000\",\n          \"log_connections\": \"1\",\n          \"log_disconnections\": \"1\",\n        },\n      },\n      \"Type\": \"AWS::RDS::DBClusterParameterGroup\",\n    },\n    \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f306AEFF37\": {\n      \"DependsOn\": [\n        \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleDefaultPolicyFECC51DC\",\n        \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleB008BAA4\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleB008BAA4\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs20.x\",\n        \"Timeout\": 300,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleB008BAA4\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleDefaultPolicyFECC51DC\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"codebuild:StartBuild\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::GetAtt\": [\n                  \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n                  \"Arn\",\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleDefaultPolicyFECC51DC\",\n        \"Roles\": [\n          {\n            \"Ref\": \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f3ServiceRoleB008BAA4\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"EventBusApi6E8C7C94\": {\n      \"Properties\": {\n        \"EventConfig\": {\n          \"AuthProviders\": [\n            {\n              \"AuthType\": \"AWS_IAM\",\n            },\n            {\n              \"AuthType\": \"AMAZON_COGNITO_USER_POOLS\",\n              \"CognitoConfig\": {\n                \"AwsRegion\": \"us-west-2\",\n                \"UserPoolId\": {\n                  \"Ref\": \"AuthUserPool8115E87F\",\n                },\n              },\n            },\n          ],\n          \"ConnectionAuthModes\": [\n            {\n              \"AuthType\": \"AWS_IAM\",\n            },\n            {\n              \"AuthType\": \"AMAZON_COGNITO_USER_POOLS\",\n            },\n          ],\n          \"DefaultPublishAuthModes\": [\n            {\n              \"AuthType\": \"AWS_IAM\",\n            },\n          ],\n          \"DefaultSubscribeAuthModes\": [\n            {\n              \"AuthType\": \"AWS_IAM\",\n            },\n            {\n              \"AuthType\": \"AMAZON_COGNITO_USER_POOLS\",\n            },\n          ],\n        },\n        \"Name\": \"ServerlessWackEventBus8815362F\",\n      },\n      \"Type\": \"AWS::AppSync::Api\",\n    },\n    \"EventBusNamespaceA69F015E\": {\n      \"Properties\": {\n        \"ApiId\": {\n          \"Fn::GetAtt\": [\n            \"EventBusApi6E8C7C94\",\n            \"ApiId\",\n          ],\n        },\n        \"CodeS3Location\": \"s3://cdk-hnb659fds-assets-123456789012-us-west-2/REDACTED\",\n        \"HandlerConfigs\": {},\n        \"Name\": \"event-bus\",\n      },\n      \"Type\": \"AWS::AppSync::ChannelNamespace\",\n    },\n    \"ExportsReader8B249524\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"ReaderProps\": {\n          \"imports\": {\n            \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefCertificateV29EE77EAF5C85697F\": \"{{resolve:ssm:/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefCertificateV29EE77EAF5C85697F}}\",\n            \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\": \"{{resolve:ssm:/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA}}\",\n          },\n          \"prefix\": \"ServerlessWebappStarterKitStack\",\n          \"region\": \"us-west-2\",\n        },\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68\",\n            \"Arn\",\n          ],\n        },\n      },\n      \"Type\": \"Custom::CrossRegionExportReader\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A\": {\n      \"DependsOn\": [\n        \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB\",\n        \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB\",\n      ],\n      \"Properties\": {\n        \"Code\": {\n          \"S3Bucket\": \"cdk-hnb659fds-assets-123456789012-us-west-2\",\n          \"S3Key\": \"REDACTED\",\n        },\n        \"Handler\": \"index.handler\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB\",\n            \"Arn\",\n          ],\n        },\n        \"Runtime\": \"nodejs22.x\",\n        \"Timeout\": 900,\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": [\n                \"logs:PutRetentionPolicy\",\n                \"logs:DeleteRetentionPolicy\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Resource\": \"*\",\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB\",\n        \"Roles\": [\n          {\n            \"Ref\": \"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b0087110E04D0A\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b00871CustomResourcePolicy7CD24C5F\",\n      ],\n      \"Properties\": {\n        \"Create\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"SSM\",\"action\":\"getParameter\",\"parameters\":{\"Name\":\"\",\n              {\n                \"Fn::GetAtt\": [\n                  \"ExportsReader8B249524\",\n                  \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\",\n                ],\n              },\n              \"\"},\"physicalResourceId\":{\"id\":\"1577836800000\"},\"region\":\"us-east-1\"}\",\n            ],\n          ],\n        },\n        \"InstallLatestAwsSdk\": true,\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"AWS679f53fac002430cb0da5b7982bd22872D164C4C\",\n            \"Arn\",\n          ],\n        },\n        \"Update\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"SSM\",\"action\":\"getParameter\",\"parameters\":{\"Name\":\"\",\n              {\n                \"Fn::GetAtt\": [\n                  \"ExportsReader8B249524\",\n                  \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\",\n                ],\n              },\n              \"\"},\"physicalResourceId\":{\"id\":\"1577836800000\"},\"region\":\"us-east-1\"}\",\n            ],\n          ],\n        },\n      },\n      \"Type\": \"Custom::AWS\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b00871CustomResourcePolicy7CD24C5F\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"ssm:GetParameter\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":ssm:us-east-1:123456789012:parameter/\",\n                    {\n                      \"Fn::GetAtt\": [\n                        \"ExportsReader8B249524\",\n                        \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefSignPayloadHandlerFunctionVersionF9FE430A3006B9FA\",\n                      ],\n                    },\n                  ],\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b00871CustomResourcePolicy7CD24C5F\",\n        \"Roles\": [\n          {\n            \"Ref\": \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"SchedulerRoleForTarget44ece2CFC6840F\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Condition\": {\n                \"StringEquals\": {\n                  \"aws:SourceAccount\": \"123456789012\",\n                  \"aws:SourceArn\": {\n                    \"Fn::Join\": [\n                      \"\",\n                      [\n                        \"arn:\",\n                        {\n                          \"Ref\": \"AWS::Partition\",\n                        },\n                        \":scheduler:us-west-2:123456789012:schedule-group/default\",\n                      ],\n                    ],\n                  },\n                },\n              },\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"scheduler.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"SchedulerRoleForTarget44ece2DefaultPolicyFDF3E159\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"lambda:InvokeFunction\",\n              \"Effect\": \"Allow\",\n              \"Resource\": [\n                {\n                  \"Fn::GetAtt\": [\n                    \"AsyncJobHandler438266BD\",\n                    \"Arn\",\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      {\n                        \"Fn::GetAtt\": [\n                          \"AsyncJobHandler438266BD\",\n                          \"Arn\",\n                        ],\n                      },\n                      \":*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"SchedulerRoleForTarget44ece2DefaultPolicyFDF3E159\",\n        \"Roles\": [\n          {\n            \"Ref\": \"SchedulerRoleForTarget44ece2CFC6840F\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"Vpc8378EB38\": {\n      \"Properties\": {\n        \"CidrBlock\": \"10.0.0.0/16\",\n        \"EnableDnsHostnames\": true,\n        \"EnableDnsSupport\": true,\n        \"InstanceTenancy\": \"default\",\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::EC2::VPC\",\n    },\n    \"VpcIGWD7BA715C\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::EC2::InternetGateway\",\n    },\n    \"VpcNatSecurityGroup8DA26EDC\": {\n      \"Properties\": {\n        \"GroupDescription\": \"Security Group for NAT instances\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"SecurityGroupIngress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"from 0.0.0.0/0:ALL TRAFFIC\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"VpcPrivateSubnet1DefaultRouteBE02A9ED\": {\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"InstanceId\": {\n          \"Ref\": \"VpcPublicSubnet1NatInstance57B636B8\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet1RouteTableB2C5B500\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet1RouteTableB2C5B500\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPrivateSubnet1RouteTableB2C5B500\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPrivateSubnet1Subnet536B997A\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1a\",\n        \"CidrBlock\": \"10.0.96.0/19\",\n        \"MapPublicIpOnLaunch\": false,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPrivateSubnet2DefaultRoute060D2087\": {\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"InstanceId\": {\n          \"Ref\": \"VpcPublicSubnet1NatInstance57B636B8\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet2RouteTableA678073B\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPrivateSubnet2RouteTableA678073B\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet2\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet2RouteTableA678073B\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPrivateSubnet2Subnet3788AAA1\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1b\",\n        \"CidrBlock\": \"10.0.128.0/19\",\n        \"MapPublicIpOnLaunch\": false,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet2\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPrivateSubnet3DefaultRoute94B74F0D\": {\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"InstanceId\": {\n          \"Ref\": \"VpcPublicSubnet1NatInstance57B636B8\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet3RouteTableD98824C7\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPrivateSubnet3RouteTableD98824C7\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPrivateSubnet3RouteTableD98824C7\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet3\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPrivateSubnet3SubnetF258B56E\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1c\",\n        \"CidrBlock\": \"10.0.160.0/19\",\n        \"MapPublicIpOnLaunch\": false,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Private\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PrivateSubnet3\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPublicSubnet1DefaultRoute3DA9E72A\": {\n      \"DependsOn\": [\n        \"VpcVPCGWBF912B6E\",\n      ],\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"GatewayId\": {\n          \"Ref\": \"VpcIGWD7BA715C\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet1RouteTable6C95E38E\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPublicSubnet1NatInstance57B636B8\": {\n      \"DependsOn\": [\n        \"VpcPublicSubnet1DefaultRoute3DA9E72A\",\n        \"VpcPublicSubnet1NatInstanceInstanceRole9D835E32\",\n        \"VpcPublicSubnet1RouteTableAssociation97140677\",\n      ],\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1a\",\n        \"IamInstanceProfile\": {\n          \"Ref\": \"VpcPublicSubnet1NatInstanceInstanceProfileEE10C485\",\n        },\n        \"ImageId\": {\n          \"Ref\": \"SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61arm64C96584B6F00A464EAD1953AFF4B05118Parameter\",\n        },\n        \"InstanceType\": \"t4g.nano\",\n        \"NetworkInterfaces\": [\n          {\n            \"AssociatePublicIpAddress\": true,\n            \"DeviceIndex\": \"0\",\n            \"GroupSet\": [\n              {\n                \"Fn::GetAtt\": [\n                  \"VpcNatSecurityGroup8DA26EDC\",\n                  \"GroupId\",\n                ],\n              },\n            ],\n            \"SubnetId\": {\n              \"Ref\": \"VpcPublicSubnet1Subnet5C2D37C4\",\n            },\n          },\n        ],\n        \"SourceDestCheck\": false,\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet1/NatInstance\",\n          },\n        ],\n        \"UserData\": {\n          \"Fn::Base64\": \"#!/bin/bash\nfor i in {1..5}; do yum install iptables-services -y && break || sleep 10; done\nsystemctl enable iptables\nsystemctl start iptables\necho \"net.ipv4.ip_forward=1\" > /etc/sysctl.d/custom-ip-forwarding.conf\nsysctl -p /etc/sysctl.d/custom-ip-forwarding.conf\nIFACE=$(ip route show default | awk '{print $5}')\n/sbin/iptables -t nat -A POSTROUTING -o $IFACE -j MASQUERADE\n/sbin/iptables -F FORWARD\nservice iptables save\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Instance\",\n    },\n    \"VpcPublicSubnet1NatInstanceInstanceProfileEE10C485\": {\n      \"Properties\": {\n        \"Roles\": [\n          {\n            \"Ref\": \"VpcPublicSubnet1NatInstanceInstanceRole9D835E32\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::InstanceProfile\",\n    },\n    \"VpcPublicSubnet1NatInstanceInstanceRole9D835E32\": {\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"ec2.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet1/NatInstance\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"VpcPublicSubnet1RouteTable6C95E38E\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPublicSubnet1RouteTableAssociation97140677\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet1RouteTable6C95E38E\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPublicSubnet1Subnet5C2D37C4\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPublicSubnet1Subnet5C2D37C4\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1a\",\n        \"CidrBlock\": \"10.0.0.0/19\",\n        \"MapPublicIpOnLaunch\": true,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPublicSubnet2DefaultRoute97F91067\": {\n      \"DependsOn\": [\n        \"VpcVPCGWBF912B6E\",\n      ],\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"GatewayId\": {\n          \"Ref\": \"VpcIGWD7BA715C\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet2RouteTable94F7E489\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPublicSubnet2RouteTable94F7E489\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet2\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPublicSubnet2RouteTableAssociationDD5762D8\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet2RouteTable94F7E489\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPublicSubnet2Subnet691E08A3\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPublicSubnet2Subnet691E08A3\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1b\",\n        \"CidrBlock\": \"10.0.32.0/19\",\n        \"MapPublicIpOnLaunch\": true,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet2\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcPublicSubnet3DefaultRoute4697774F\": {\n      \"DependsOn\": [\n        \"VpcVPCGWBF912B6E\",\n      ],\n      \"Properties\": {\n        \"DestinationCidrBlock\": \"0.0.0.0/0\",\n        \"GatewayId\": {\n          \"Ref\": \"VpcIGWD7BA715C\",\n        },\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet3RouteTable93458DBB\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Route\",\n    },\n    \"VpcPublicSubnet3RouteTable93458DBB\": {\n      \"Properties\": {\n        \"Tags\": [\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet3\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::RouteTable\",\n    },\n    \"VpcPublicSubnet3RouteTableAssociation1F1EDF02\": {\n      \"Properties\": {\n        \"RouteTableId\": {\n          \"Ref\": \"VpcPublicSubnet3RouteTable93458DBB\",\n        },\n        \"SubnetId\": {\n          \"Ref\": \"VpcPublicSubnet3SubnetBE12F0B6\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SubnetRouteTableAssociation\",\n    },\n    \"VpcPublicSubnet3SubnetBE12F0B6\": {\n      \"Properties\": {\n        \"AvailabilityZone\": \"dummy1c\",\n        \"CidrBlock\": \"10.0.64.0/19\",\n        \"MapPublicIpOnLaunch\": true,\n        \"Tags\": [\n          {\n            \"Key\": \"aws-cdk:subnet-name\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"aws-cdk:subnet-type\",\n            \"Value\": \"Public\",\n          },\n          {\n            \"Key\": \"Name\",\n            \"Value\": \"ServerlessWebappStarterKitStack/Vpc/PublicSubnet3\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::Subnet\",\n    },\n    \"VpcVPCGWBF912B6E\": {\n      \"Properties\": {\n        \"InternetGatewayId\": {\n          \"Ref\": \"VpcIGWD7BA715C\",\n        },\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::VPCGatewayAttachment\",\n    },\n    \"Webapp107041BD\": {\n      \"Properties\": {\n        \"DistributionConfig\": {\n          \"Aliases\": [\n            \"web.example.com\",\n          ],\n          \"Comment\": \"CloudFront for Webapp\",\n          \"DefaultCacheBehavior\": {\n            \"AllowedMethods\": [\n              \"GET\",\n              \"HEAD\",\n              \"OPTIONS\",\n              \"PUT\",\n              \"PATCH\",\n              \"POST\",\n              \"DELETE\",\n            ],\n            \"CachePolicyId\": {\n              \"Ref\": \"WebappSharedCachePolicy14FEE4A0\",\n            },\n            \"Compress\": true,\n            \"FunctionAssociations\": [\n              {\n                \"EventType\": \"viewer-request\",\n                \"FunctionARN\": {\n                  \"Fn::GetAtt\": [\n                    \"WebappCacheKeyFunction6C227CE2\",\n                    \"FunctionARN\",\n                  ],\n                },\n              },\n            ],\n            \"LambdaFunctionAssociations\": [\n              {\n                \"EventType\": \"origin-request\",\n                \"IncludeBody\": true,\n                \"LambdaFunctionARN\": {\n                  \"Fn::GetAtt\": [\n                    \"LookupVersionArnc8730278af02f875114ca902814c77b68f19b0087110E04D0A\",\n                    \"Parameter.Value\",\n                  ],\n                },\n              },\n            ],\n            \"OriginRequestPolicyId\": \"b689b0a8-53d0-40ab-baf2-68738e2966ac\",\n            \"TargetOriginId\": \"ServerlessWebappStarterKitStackWebappOrigin1D7B867FF\",\n            \"ViewerProtocolPolicy\": \"allow-all\",\n          },\n          \"Enabled\": true,\n          \"HttpVersion\": \"http2\",\n          \"IPV6Enabled\": true,\n          \"Logging\": {\n            \"Bucket\": {\n              \"Fn::GetAtt\": [\n                \"AccessLogBucketDA470295\",\n                \"RegionalDomainName\",\n              ],\n            },\n            \"Prefix\": \"Webapp/\",\n          },\n          \"Origins\": [\n            {\n              \"ConnectionTimeout\": 6,\n              \"CustomOriginConfig\": {\n                \"OriginProtocolPolicy\": \"https-only\",\n                \"OriginReadTimeout\": 60,\n                \"OriginSSLProtocols\": [\n                  \"TLSv1.2\",\n                ],\n              },\n              \"DomainName\": {\n                \"Fn::Select\": [\n                  2,\n                  {\n                    \"Fn::Split\": [\n                      \"/\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappHandlerFunctionUrl7AEF8DEE\",\n                          \"FunctionUrl\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \"Id\": \"ServerlessWebappStarterKitStackWebappOrigin1D7B867FF\",\n              \"OriginAccessControlId\": {\n                \"Fn::GetAtt\": [\n                  \"WebappOrigin1FunctionUrlOriginAccessControlEA98B600\",\n                  \"Id\",\n                ],\n              },\n            },\n          ],\n          \"ViewerCertificate\": {\n            \"AcmCertificateArn\": {\n              \"Fn::GetAtt\": [\n                \"ExportsReader8B249524\",\n                \"/cdk/exports/ServerlessWebappStarterKitStack/ServerlessWebappStarterKitUsEast1Stackuseast1RefCertificateV29EE77EAF5C85697F\",\n              ],\n            },\n            \"MinimumProtocolVersion\": \"TLSv1.2_2021\",\n            \"SslSupportMethod\": \"sni-only\",\n          },\n        },\n      },\n      \"Type\": \"AWS::CloudFront::Distribution\",\n    },\n    \"WebappBuild348806E3\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n        \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleDefaultPolicy2316728F\",\n        \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64RoleC5F7BBFE\",\n      ],\n      \"Properties\": {\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"DeployTimeBuildCustomResourceHandlerdb740fd554364a848a09e6dfcd01f4f306AEFF37\",\n            \"Arn\",\n          ],\n        },\n        \"buildCommand\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"docker buildx build --build-arg ALLOWED_ORIGIN_HOST=*.example.com --build-arg SKIP_TS_BUILD=true --build-arg NEXT_PUBLIC_EVENT_HTTP_ENDPOINT=https://\",\n              {\n                \"Fn::GetAtt\": [\n                  \"EventBusApi6E8C7C94\",\n                  \"Dns.Http\",\n                ],\n              },\n              \" --build-arg NEXT_PUBLIC_AWS_REGION=us-west-2 --platform linux/arm64 --output type=image,name=\",\n              {\n                \"Fn::Select\": [\n                  4,\n                  {\n                    \"Fn::Split\": [\n                      \":\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappBuildRepository4C93D48D\",\n                          \"Arn\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \".dkr.ecr.\",\n              {\n                \"Fn::Select\": [\n                  3,\n                  {\n                    \"Fn::Split\": [\n                      \":\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappBuildRepository4C93D48D\",\n                          \"Arn\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \".\",\n              {\n                \"Ref\": \"AWS::URLSuffix\",\n              },\n              \"/\",\n              {\n                \"Ref\": \"WebappBuildRepository4C93D48D\",\n              },\n              \":<IMAGE_TAG>,push=true --provenance=false .\",\n            ],\n          ],\n        },\n        \"codeBuildProjectName\": {\n          \"Ref\": \"ContainerImageBuildArm64e83729feb1564e709bec452b15847a30amd64C13E3549\",\n        },\n        \"repositoryUri\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              {\n                \"Fn::Select\": [\n                  4,\n                  {\n                    \"Fn::Split\": [\n                      \":\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappBuildRepository4C93D48D\",\n                          \"Arn\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \".dkr.ecr.\",\n              {\n                \"Fn::Select\": [\n                  3,\n                  {\n                    \"Fn::Split\": [\n                      \":\",\n                      {\n                        \"Fn::GetAtt\": [\n                          \"WebappBuildRepository4C93D48D\",\n                          \"Arn\",\n                        ],\n                      },\n                    ],\n                  },\n                ],\n              },\n              \".\",\n              {\n                \"Ref\": \"AWS::URLSuffix\",\n              },\n              \"/\",\n              {\n                \"Ref\": \"WebappBuildRepository4C93D48D\",\n              },\n            ],\n          ],\n        },\n        \"sourceS3Url\": \"s3://cdk-hnb659fds-assets-123456789012-us-west-2/REDACTED\",\n        \"tagPrefix\": \"REDACTED\",\n        \"type\": \"ContainerImageBuild\",\n      },\n      \"Type\": \"Custom::CDKContainerImageBuild\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappBuildRepository4C93D48D\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"EmptyOnDelete\": true,\n        \"RepositoryPolicyText\": {\n          \"Statement\": [\n            {\n              \"Action\": [\n                \"ecr:BatchCheckLayerAvailability\",\n                \"ecr:GetDownloadUrlForLayer\",\n                \"ecr:BatchGetImage\",\n              ],\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n      },\n      \"Type\": \"AWS::ECR::Repository\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappCacheKeyFunction6C227CE2\": {\n      \"Properties\": {\n        \"AutoPublish\": true,\n        \"FunctionCode\": \"// CloudFront Functions JS 2.0\n// Combines Next.js RSC-related headers into a single hashed cache key header\n// to prevent cache poisoning between HTML and RSC flight responses.\n//\n// Next.js App Router sets Vary: rsc, next-router-state-tree, next-router-prefetch,\n// next-router-segment-prefetch (and next-url for interception routes).\n// CloudFront ignores Vary and requires explicit cache key configuration,\n// but its Cache Policy has a 10-header limit. This function hashes all\n// RSC headers into one header to stay within the limit.\nasync function handler(event) {\n  var h = event.request.headers;\n  var parts = [\n    'rsc',\n    'next-router-prefetch',\n    'next-router-state-tree',\n    'next-router-segment-prefetch',\n    'next-url',\n  ];\n  var key = '';\n  for (var i = 0; i < parts.length; i++) {\n    if (h[parts[i]]) {\n      key += parts[i] + '=' + h[parts[i]].value + ';';\n    }\n  }\n  if (key) {\n    // FNV-1a hash (32-bit). Cryptographic strength is unnecessary;\n    // we only need distinct cache keys for distinct header combinations.\n    // See: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function\n    var FNV_OFFSET_BASIS = 2166136261;\n    var FNV_PRIME = 16777619;\n    var hash = FNV_OFFSET_BASIS;\n    for (var j = 0; j < key.length; j++) {\n      hash ^= key.charCodeAt(j);\n      hash = (hash * FNV_PRIME) | 0;\n    }\n    event.request.headers['x-nextjs-cache-key'] = { value: String(hash >>> 0) };\n  }\n  return event.request;\n}\n\",\n        \"FunctionConfig\": {\n          \"Comment\": \"us-west-2ServerlessWebappCacheKeyFunction86D1ABE9\",\n          \"Runtime\": \"cloudfront-js-2.0\",\n        },\n        \"Name\": \"us-west-2ServerlessWebappCacheKeyFunction86D1ABE9\",\n      },\n      \"Type\": \"AWS::CloudFront::Function\",\n    },\n    \"WebappCloudFrontInvalidation588CF152\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"WebappCloudFrontInvalidationCustomResourcePolicy18C215D6\",\n      ],\n      \"Properties\": {\n        \"Create\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"cloudfront\",\"action\":\"createInvalidation\",\"parameters\":{\"DistributionId\":\"\",\n              {\n                \"Ref\": \"Webapp107041BD\",\n              },\n              \"\",\"InvalidationBatch\":{\"CallerReference\":\"\",\n              {\n                \"Fn::GetAtt\": [\n                  \"WebappHandlerCurrentVersionREDACTED\",\n                  \"Version\",\n                ],\n              },\n              \"\",\"Paths\":{\"Quantity\":1,\"Items\":[\"/*\"]}}},\"physicalResourceId\":{\"id\":\"invalidation\"}}\",\n            ],\n          ],\n        },\n        \"InstallLatestAwsSdk\": true,\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"AWS679f53fac002430cb0da5b7982bd22872D164C4C\",\n            \"Arn\",\n          ],\n        },\n        \"Update\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"{\"service\":\"cloudfront\",\"action\":\"createInvalidation\",\"parameters\":{\"DistributionId\":\"\",\n              {\n                \"Ref\": \"Webapp107041BD\",\n              },\n              \"\",\"InvalidationBatch\":{\"CallerReference\":\"\",\n              {\n                \"Fn::GetAtt\": [\n                  \"WebappHandlerCurrentVersionREDACTED\",\n                  \"Version\",\n                ],\n              },\n              \"\",\"Paths\":{\"Quantity\":1,\"Items\":[\"/*\"]}}},\"physicalResourceId\":{\"id\":\"invalidation\"}}\",\n            ],\n          ],\n        },\n      },\n      \"Type\": \"Custom::AWS\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappCloudFrontInvalidationCustomResourcePolicy18C215D6\": {\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"cloudfront:CreateInvalidation\",\n              \"Effect\": \"Allow\",\n              \"Resource\": {\n                \"Fn::Join\": [\n                  \"\",\n                  [\n                    \"arn:\",\n                    {\n                      \"Ref\": \"AWS::Partition\",\n                    },\n                    \":cloudfront::123456789012:distribution/\",\n                    {\n                      \"Ref\": \"Webapp107041BD\",\n                    },\n                  ],\n                ],\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"WebappCloudFrontInvalidationCustomResourcePolicy18C215D6\",\n        \"Roles\": [\n          {\n            \"Ref\": \"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"WebappHandler8DD158A3\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n        \"WebappHandlerServiceRoleDefaultPolicy7D06F4EA\",\n        \"WebappHandlerServiceRole4F4D1ACD\",\n      ],\n      \"Properties\": {\n        \"Architectures\": [\n          \"arm64\",\n        ],\n        \"Code\": {\n          \"ImageUri\": {\n            \"Fn::Join\": [\n              \"\",\n              [\n                {\n                  \"Fn::Select\": [\n                    4,\n                    {\n                      \"Fn::Split\": [\n                        \":\",\n                        {\n                          \"Fn::GetAtt\": [\n                            \"WebappBuildRepository4C93D48D\",\n                            \"Arn\",\n                          ],\n                        },\n                      ],\n                    },\n                  ],\n                },\n                \".dkr.ecr.\",\n                {\n                  \"Fn::Select\": [\n                    3,\n                    {\n                      \"Fn::Split\": [\n                        \":\",\n                        {\n                          \"Fn::GetAtt\": [\n                            \"WebappBuildRepository4C93D48D\",\n                            \"Arn\",\n                          ],\n                        },\n                      ],\n                    },\n                  ],\n                },\n                \".\",\n                {\n                  \"Ref\": \"AWS::URLSuffix\",\n                },\n                \"/\",\n                {\n                  \"Ref\": \"WebappBuildRepository4C93D48D\",\n                },\n                \":\",\n                {\n                  \"Fn::GetAtt\": [\n                    \"WebappBuild348806E3\",\n                    \"ImageTag\",\n                  ],\n                },\n              ],\n            ],\n          },\n        },\n        \"Environment\": {\n          \"Variables\": {\n            \"AMPLIFY_APP_ORIGIN\": \"https://web.example.com\",\n            \"ASYNC_JOB_HANDLER_ARN\": {\n              \"Fn::GetAtt\": [\n                \"AsyncJobHandler438266BD\",\n                \"Arn\",\n              ],\n            },\n            \"COGNITO_DOMAIN\": \"auth.example.com\",\n            \"DATABASE_ENGINE\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_HOST\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Address\",\n              ],\n            },\n            \"DATABASE_NAME\": \"main\",\n            \"DATABASE_OPTION\": \"?connection_limit=1&connect_timeout=30\",\n            \"DATABASE_PASSWORD\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_PORT\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Port\",\n              ],\n            },\n            \"DATABASE_URL\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}://{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}:{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}@\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Address\",\n                    ],\n                  },\n                  \":\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Port\",\n                    ],\n                  },\n                  \"/main?connection_limit=1&connect_timeout=30\",\n                ],\n              ],\n            },\n            \"DATABASE_USER\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}\",\n                ],\n              ],\n            },\n            \"USER_POOL_CLIENT_ID\": {\n              \"Ref\": \"AuthUserPoolClientC635291F\",\n            },\n            \"USER_POOL_ID\": {\n              \"Ref\": \"AuthUserPool8115E87F\",\n            },\n          },\n        },\n        \"LoggingConfig\": {\n          \"LogGroup\": {\n            \"Ref\": \"WebappHandlerLogs87A6D2D7\",\n          },\n        },\n        \"MemorySize\": 1024,\n        \"PackageType\": \"Image\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandlerServiceRole4F4D1ACD\",\n            \"Arn\",\n          ],\n        },\n        \"Timeout\": 180,\n        \"VpcConfig\": {\n          \"SecurityGroupIds\": [\n            {\n              \"Fn::GetAtt\": [\n                \"WebappHandlerSecurityGroup5451B519\",\n                \"GroupId\",\n              ],\n            },\n          ],\n          \"SubnetIds\": [\n            {\n              \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n            },\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"WebappHandlerCurrentVersionREDACTED\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"FunctionName\": {\n          \"Ref\": \"WebappHandler8DD158A3\",\n        },\n      },\n      \"Type\": \"AWS::Lambda::Version\",\n    },\n    \"WebappHandlerFunctionUrl7AEF8DEE\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AuthType\": \"AWS_IAM\",\n        \"InvokeMode\": \"RESPONSE_STREAM\",\n        \"TargetFunctionArn\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandler8DD158A3\",\n            \"Arn\",\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Url\",\n    },\n    \"WebappHandlerLogs87A6D2D7\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"RetentionInDays\": 7,\n      },\n      \"Type\": \"AWS::Logs::LogGroup\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappHandlerSecurityGroup5451B519\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"GroupDescription\": \"Automatic security group for Lambda Function ServerlessWebappStarterKitStackWebappHandlerF1A4ACC9\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"WebappHandlerServiceRole4F4D1ACD\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"WebappHandlerServiceRoleDefaultPolicy7D06F4EA\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"PolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"lambda:InvokeFunction\",\n              \"Effect\": \"Allow\",\n              \"Resource\": [\n                {\n                  \"Fn::GetAtt\": [\n                    \"AsyncJobHandler438266BD\",\n                    \"Arn\",\n                  ],\n                },\n                {\n                  \"Fn::Join\": [\n                    \"\",\n                    [\n                      {\n                        \"Fn::GetAtt\": [\n                          \"AsyncJobHandler438266BD\",\n                          \"Arn\",\n                        ],\n                      },\n                      \":*\",\n                    ],\n                  ],\n                },\n              ],\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"PolicyName\": \"WebappHandlerServiceRoleDefaultPolicy7D06F4EA\",\n        \"Roles\": [\n          {\n            \"Ref\": \"WebappHandlerServiceRole4F4D1ACD\",\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Policy\",\n    },\n    \"WebappInvokeFunctionPermission8F3F2610\": {\n      \"Properties\": {\n        \"Action\": \"lambda:InvokeFunction\",\n        \"FunctionName\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandler8DD158A3\",\n            \"Arn\",\n          ],\n        },\n        \"Principal\": \"cloudfront.amazonaws.com\",\n        \"SourceArn\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"arn:\",\n              {\n                \"Ref\": \"AWS::Partition\",\n              },\n              \":cloudfront::\",\n              {\n                \"Ref\": \"AWS::AccountId\",\n              },\n              \":distribution/\",\n              {\n                \"Ref\": \"Webapp107041BD\",\n              },\n            ],\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Permission\",\n    },\n    \"WebappMigrationRunnerAC67C012\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n        \"WebappMigrationRunnerServiceRoleE27E1F7A\",\n      ],\n      \"Properties\": {\n        \"Architectures\": [\n          \"arm64\",\n        ],\n        \"Code\": {\n          \"ImageUri\": {\n            \"Fn::Sub\": \"REDACTED\",\n          },\n        },\n        \"Environment\": {\n          \"Variables\": {\n            \"DATABASE_ENGINE\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_HOST\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Address\",\n              ],\n            },\n            \"DATABASE_NAME\": \"main\",\n            \"DATABASE_OPTION\": \"?connection_limit=1&connect_timeout=30\",\n            \"DATABASE_PASSWORD\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}\",\n                ],\n              ],\n            },\n            \"DATABASE_PORT\": {\n              \"Fn::GetAtt\": [\n                \"DatabaseCluster5B53A178\",\n                \"Endpoint.Port\",\n              ],\n            },\n            \"DATABASE_URL\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:engine::}}://{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}:{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:password::}}@\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Address\",\n                    ],\n                  },\n                  \":\",\n                  {\n                    \"Fn::GetAtt\": [\n                      \"DatabaseCluster5B53A178\",\n                      \"Endpoint.Port\",\n                    ],\n                  },\n                  \"/main?connection_limit=1&connect_timeout=30\",\n                ],\n              ],\n            },\n            \"DATABASE_USER\": {\n              \"Fn::Join\": [\n                \"\",\n                [\n                  \"{{resolve:secretsmanager:\",\n                  {\n                    \"Ref\": \"DatabaseClusterSecretAttachmentDC8466C0\",\n                  },\n                  \":SecretString:username::}}\",\n                ],\n              ],\n            },\n          },\n        },\n        \"ImageConfig\": {\n          \"Command\": [\n            \"migration-runner.handler\",\n          ],\n        },\n        \"LoggingConfig\": {\n          \"LogGroup\": {\n            \"Ref\": \"WebappMigrationRunnerLogsD9A84B90\",\n          },\n        },\n        \"MemorySize\": 256,\n        \"PackageType\": \"Image\",\n        \"Role\": {\n          \"Fn::GetAtt\": [\n            \"WebappMigrationRunnerServiceRoleE27E1F7A\",\n            \"Arn\",\n          ],\n        },\n        \"Timeout\": 300,\n        \"VpcConfig\": {\n          \"SecurityGroupIds\": [\n            {\n              \"Fn::GetAtt\": [\n                \"WebappMigrationRunnerSecurityGroup7F0DF264\",\n                \"GroupId\",\n              ],\n            },\n          ],\n          \"SubnetIds\": [\n            {\n              \"Ref\": \"VpcPrivateSubnet1Subnet536B997A\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet2Subnet3788AAA1\",\n            },\n            {\n              \"Ref\": \"VpcPrivateSubnet3SubnetF258B56E\",\n            },\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Function\",\n    },\n    \"WebappMigrationRunnerCurrentVersionREDACTED\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"FunctionName\": {\n          \"Ref\": \"WebappMigrationRunnerAC67C012\",\n        },\n      },\n      \"Type\": \"AWS::Lambda::Version\",\n    },\n    \"WebappMigrationRunnerLogsD9A84B90\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"Properties\": {\n        \"RetentionInDays\": 7,\n      },\n      \"Type\": \"AWS::Logs::LogGroup\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappMigrationRunnerSecurityGroup7F0DF264\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"GroupDescription\": \"Automatic security group for Lambda Function ServerlessWebappStarterKitStackWebappMigrationRunner45EAC73E\",\n        \"SecurityGroupEgress\": [\n          {\n            \"CidrIp\": \"0.0.0.0/0\",\n            \"Description\": \"Allow all outbound traffic by default\",\n            \"IpProtocol\": \"-1\",\n          },\n        ],\n        \"VpcId\": {\n          \"Ref\": \"Vpc8378EB38\",\n        },\n      },\n      \"Type\": \"AWS::EC2::SecurityGroup\",\n    },\n    \"WebappMigrationRunnerServiceRoleE27E1F7A\": {\n      \"DependsOn\": [\n        \"VpcPrivateSubnet1DefaultRouteBE02A9ED\",\n        \"VpcPrivateSubnet1RouteTableAssociation70C59FA6\",\n        \"VpcPrivateSubnet2DefaultRoute060D2087\",\n        \"VpcPrivateSubnet2RouteTableAssociationA89CAD56\",\n        \"VpcPrivateSubnet3DefaultRoute94B74F0D\",\n        \"VpcPrivateSubnet3RouteTableAssociation16BDDC43\",\n      ],\n      \"Properties\": {\n        \"AssumeRolePolicyDocument\": {\n          \"Statement\": [\n            {\n              \"Action\": \"sts:AssumeRole\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"Service\": \"lambda.amazonaws.com\",\n              },\n            },\n          ],\n          \"Version\": \"2012-10-17\",\n        },\n        \"ManagedPolicyArns\": [\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n              ],\n            ],\n          },\n          {\n            \"Fn::Join\": [\n              \"\",\n              [\n                \"arn:\",\n                {\n                  \"Ref\": \"AWS::Partition\",\n                },\n                \":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole\",\n              ],\n            ],\n          },\n        ],\n      },\n      \"Type\": \"AWS::IAM::Role\",\n    },\n    \"WebappMigrationTrigger42AFC1D9\": {\n      \"DeletionPolicy\": \"Delete\",\n      \"DependsOn\": [\n        \"DatabaseClusterLogRetentionpostgresql025D39CE\",\n        \"DatabaseCluster5B53A178\",\n        \"DatabaseClusterSecretAttachmentDC8466C0\",\n        \"DatabaseClusterSecretD1FB634F\",\n        \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackAsyncJobHandlerSecurityGroup5220DFB3IndirectPort9323962E\",\n        \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackDatabaseBastionHostInstanceSecurityGroup4F0DD25BIndirectPort8AFD9922\",\n        \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackWebappHandlerSecurityGroupA009CF4AIndirectPort7311063E\",\n        \"DatabaseClusterSecurityGroupfromServerlessWebappStarterKitStackWebappMigrationRunnerSecurityGroupC0959349IndirectPortDCF4A356\",\n        \"DatabaseClusterSecurityGroupFEF1426A\",\n        \"DatabaseClusterSubnets5540150D\",\n        \"DatabaseClusterWriterD43085C6\",\n      ],\n      \"Properties\": {\n        \"ExecuteOnHandlerChange\": true,\n        \"HandlerArn\": {\n          \"Ref\": \"WebappMigrationRunnerCurrentVersionREDACTED\",\n        },\n        \"InvocationType\": \"RequestResponse\",\n        \"ServiceToken\": {\n          \"Fn::GetAtt\": [\n            \"AWSCDKTriggerCustomResourceProviderCustomResourceProviderHandler97BECD91\",\n            \"Arn\",\n          ],\n        },\n        \"Timeout\": \"120000\",\n      },\n      \"Type\": \"Custom::Trigger\",\n      \"UpdateReplacePolicy\": \"Delete\",\n    },\n    \"WebappOrigin1FunctionUrlOriginAccessControlEA98B600\": {\n      \"Properties\": {\n        \"OriginAccessControlConfig\": {\n          \"Name\": \"ServerlessWebappStarterKitStnctionUrlOriginAccessControl17EB4E66\",\n          \"OriginAccessControlOriginType\": \"lambda\",\n          \"SigningBehavior\": \"always\",\n          \"SigningProtocol\": \"sigv4\",\n        },\n      },\n      \"Type\": \"AWS::CloudFront::OriginAccessControl\",\n    },\n    \"WebappOrigin1InvokeFromApiForServerlessWebappStarterKitStackWebappOrigin1D7B867FF58DBB477\": {\n      \"Properties\": {\n        \"Action\": \"lambda:InvokeFunctionUrl\",\n        \"FunctionName\": {\n          \"Fn::GetAtt\": [\n            \"WebappHandlerFunctionUrl7AEF8DEE\",\n            \"FunctionArn\",\n          ],\n        },\n        \"Principal\": \"cloudfront.amazonaws.com\",\n        \"SourceArn\": {\n          \"Fn::Join\": [\n            \"\",\n            [\n              \"arn:\",\n              {\n                \"Ref\": \"AWS::Partition\",\n              },\n              \":cloudfront::\",\n              {\n                \"Ref\": \"AWS::AccountId\",\n              },\n              \":distribution/\",\n              {\n                \"Ref\": \"Webapp107041BD\",\n              },\n            ],\n          ],\n        },\n      },\n      \"Type\": \"AWS::Lambda::Permission\",\n    },\n    \"WebappRecord02DDD651\": {\n      \"Properties\": {\n        \"AliasTarget\": {\n          \"DNSName\": {\n            \"Fn::GetAtt\": [\n              \"Webapp107041BD\",\n              \"DomainName\",\n            ],\n          },\n          \"HostedZoneId\": {\n            \"Fn::FindInMap\": [\n              \"AWSCloudFrontPartitionHostedZoneIdMap\",\n              {\n                \"Ref\": \"AWS::Partition\",\n              },\n              \"zoneId\",\n            ],\n          },\n        },\n        \"HostedZoneId\": \"DUMMY\",\n        \"Name\": \"web.example.com.\",\n        \"Type\": \"A\",\n      },\n      \"Type\": \"AWS::Route53::RecordSet\",\n    },\n    \"WebappSharedCachePolicy14FEE4A0\": {\n      \"Properties\": {\n        \"CachePolicyConfig\": {\n          \"DefaultTTL\": 0,\n          \"MaxTTL\": 31536000,\n          \"MinTTL\": 0,\n          \"Name\": \"ServerlessWebappStarterKitStackWebappSharedCachePolicy211E133B-us-west-2\",\n          \"ParametersInCacheKeyAndForwardedToOrigin\": {\n            \"CookiesConfig\": {\n              \"CookieBehavior\": \"all\",\n            },\n            \"EnableAcceptEncodingBrotli\": true,\n            \"EnableAcceptEncodingGzip\": true,\n            \"HeadersConfig\": {\n              \"HeaderBehavior\": \"whitelist\",\n              \"Headers\": [\n                \"authorization\",\n                \"Origin\",\n                \"X-HTTP-Method-Override\",\n                \"X-HTTP-Method\",\n                \"X-Method-Override\",\n                \"x-nextjs-cache-key\",\n              ],\n            },\n            \"QueryStringsConfig\": {\n              \"QueryStringBehavior\": \"all\",\n            },\n          },\n        },\n      },\n      \"Type\": \"AWS::CloudFront::CachePolicy\",\n    },\n  },\n  \"Rules\": {\n    \"CheckBootstrapVersion\": {\n      \"Assertions\": [\n        {\n          \"Assert\": {\n            \"Fn::Not\": [\n              {\n                \"Fn::Contains\": [\n                  [\n                    \"1\",\n                    \"2\",\n                    \"3\",\n                    \"4\",\n                    \"5\",\n                  ],\n                  {\n                    \"Ref\": \"BootstrapVersion\",\n                  },\n                ],\n              },\n            ],\n          },\n          \"AssertDescription\": \"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.\",\n        },\n      ],\n    },\n  },\n}\n`;\n"
  },
  {
    "path": "cdk/test/serverless-fullstack-webapp-starter-kit-without-domain.test.ts",
    "content": "import * as cdk from 'aws-cdk-lib';\nimport { Template } from 'aws-cdk-lib/assertions';\nimport { MainStack } from '../lib/main-stack';\nimport { UsEast1Stack } from '../lib/us-east-1-stack';\n\ntest('Snapshot test', () => {\n  jest.useFakeTimers().setSystemTime(new Date('2020-01-01'));\n\n  const app = new cdk.App();\n  const props = {\n    account: '123456789012',\n  };\n  const virginia = new UsEast1Stack(app, 'ServerlessWebappStarterKitUsEast1Stack', {\n    env: {\n      account: props.account,\n      region: 'us-east-1',\n    },\n    crossRegionReferences: true,\n  });\n  const mainStack = new MainStack(app, 'ServerlessWebappStarterKitStack', {\n    env: {\n      account: props.account,\n      region: 'us-west-2',\n    },\n    crossRegionReferences: true,\n    signPayloadHandler: virginia.signPayloadHandler,\n  });\n  const virginiaTemplate = Template.fromStack(virginia);\n  const mainTemplate = Template.fromStack(mainStack);\n\n  expect(virginiaTemplate).toMatchSnapshot();\n  expect(mainTemplate).toMatchSnapshot();\n});\n"
  },
  {
    "path": "cdk/test/serverless-fullstack-webapp-starter-kit.test.ts",
    "content": "import * as cdk from 'aws-cdk-lib';\nimport { Template } from 'aws-cdk-lib/assertions';\nimport { MainStack } from '../lib/main-stack';\nimport { UsEast1Stack } from '../lib/us-east-1-stack';\n\ntest('Snapshot test', () => {\n  jest.useFakeTimers().setSystemTime(new Date('2020-01-01'));\n\n  const app = new cdk.App();\n  const props = {\n    account: '123456789012',\n    domainName: 'example.com',\n  };\n  const virginia = new UsEast1Stack(app, 'ServerlessWebappStarterKitUsEast1Stack', {\n    env: {\n      account: props.account,\n      region: 'us-east-1',\n    },\n    crossRegionReferences: true,\n    domainName: props.domainName,\n  });\n  const mainStack = new MainStack(app, 'ServerlessWebappStarterKitStack', {\n    env: {\n      account: props.account,\n      region: 'us-west-2',\n    },\n    crossRegionReferences: true,\n    sharedCertificate: virginia.certificate,\n    domainName: props.domainName,\n    signPayloadHandler: virginia.signPayloadHandler,\n  });\n  const virginiaTemplate = Template.fromStack(virginia);\n  const mainTemplate = Template.fromStack(mainStack);\n\n  expect(virginiaTemplate).toMatchSnapshot();\n  expect(mainTemplate).toMatchSnapshot();\n});\n"
  },
  {
    "path": "cdk/test/snapshot-plugin.ts",
    "content": "module.exports = {\n  test: (val: any) => typeof val === 'string',\n  serialize: (val: string) => {\n    return `\"${val //\n      .replace(/([A-Fa-f0-9]{64}.zip)/, 'REDACTED')\n      .replace(/([A-Fa-f0-9]{64}.mjs)/, 'REDACTED')\n      .replace(/.*cdk-hnb659fds-container-assets-.*/, 'REDACTED')\n      .replace(/webapp-starter-[0-9a-z]*/, 'REDACTED')\n      .replace(/(.*CurrentVersion).*/, '$1REDACTED')}\"`;\n  },\n};\n"
  },
  {
    "path": "cdk/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2018\",\n    \"module\": \"commonjs\",\n    \"lib\": [\n      \"es2018\"\n    ],\n    \"declaration\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitThis\": true,\n    \"alwaysStrict\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": false,\n    \"inlineSourceMap\": true,\n    \"inlineSources\": true,\n    \"experimentalDecorators\": true,\n    \"noEmit\": true,\n    \"strictPropertyInitialization\": false,\n    \"typeRoots\": [\n      \"./node_modules/@types\"\n    ]\n  },\n  \"exclude\": [\n    \"node_modules\",\n    \"cdk.out\"\n  ]\n}\n"
  },
  {
    "path": "compose.yaml",
    "content": "services:\n  postgres:\n    image: \"public.ecr.aws/docker/library/postgres:16\"\n    ports:\n      - \"5432:5432\"\n    volumes:\n      - \"postgres_data:/var/lib/postgresql/data\"\n    restart: always\n    environment:\n      POSTGRES_PASSWORD: password\n      POSTGRES_USER: root\nvolumes:\n  mysql_data:\n  postgres_data:\n"
  },
  {
    "path": "release-please-config.json",
    "content": "{\n  \"packages\": {\n    \".\": {\n      \"release-type\": \"simple\",\n      \"initial-version\": \"2.0.0\"\n    }\n  }\n}\n"
  },
  {
    "path": "webapp/.dockerignore",
    "content": "node_modules\ndist\n.next\n.env.local\n"
  },
  {
    "path": "webapp/.env.local.example",
    "content": "COGNITO_DOMAIN=auth.example.com\nAMPLIFY_APP_ORIGIN=http://localhost:3010\nUSER_POOL_CLIENT_ID=dummy\nUSER_POOL_ID=us-west-2_dummy\nNEXT_PUBLIC_EVENT_HTTP_ENDPOINT=\"\"\nNEXT_PUBLIC_AWS_REGION=\"us-west-2\"\nASYNC_JOB_HANDLER_ARN=\"\"\n"
  },
  {
    "path": "webapp/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# env files (can opt-in for committing if needed)\n.env*\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\ndist\n\n!prisma/.env\n!.env.local.example\nsrc/lib/generated\n"
  },
  {
    "path": "webapp/Dockerfile",
    "content": "FROM public.ecr.aws/lambda/nodejs:22 AS builder\nWORKDIR /build\nCOPY package*.json ./\nRUN --mount=type=cache,target=/root/.npm npm ci\nCOPY ./ ./\nRUN npx prisma generate\nCOPY prisma ./\n\nARG SKIP_TS_BUILD=\"\"\nARG ALLOWED_ORIGIN_HOST=\"\"\nARG NEXT_PUBLIC_EVENT_HTTP_ENDPOINT=\"\"\nARG NEXT_PUBLIC_AWS_REGION=\"\"\nENV USER_POOL_CLIENT_ID=\"dummy\"\nENV USER_POOL_ID=\"dummy\"\nENV AMPLIFY_APP_ORIGIN=\"https://dummy.example.com\"\nENV COGNITO_DOMAIN=\"dummy.example.com\"\nRUN --mount=type=cache,target=/build/.next/cache npm run build\n\nFROM public.ecr.aws/lambda/nodejs:22 AS runner\nCOPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.0 /lambda-adapter /opt/extensions/lambda-adapter\nENV AWS_LWA_PORT=3000\nENV AWS_LWA_READINESS_CHECK_PATH=\"/api/health\"\nENV AWS_LWA_INVOKE_MODE=\"response_stream\"\n\nCOPY --from=builder /build/.next/static ./.next/static\nCOPY --from=builder /build/.next/standalone ./\nCOPY --from=builder /build/run.sh ./run.sh\n\nRUN ln -s /tmp/cache ./.next/cache\n\nENTRYPOINT [\"sh\"]\nCMD [\"run.sh\"]\n"
  },
  {
    "path": "webapp/README.md",
    "content": "## Run locally\n\n```bash\n# Run this command in the repository root\ndocker compose up -d\n\n# Run these commands in the webapp directory\ncd webapp\nnpm ci\nnpx prisma db push\ncp .env.local.example .env.local\n# Edit .env.local with values from CDK deploy outputs\nnpm run dev\n```\n\nOpen [http://localhost:3010](http://localhost:3010) with your browser to see the result.\n\n## Environment variables\n\n- Runtime env vars (e.g. `USER_POOL_ID`, `COGNITO_DOMAIN`) are set in `.env.local` for local development and injected via CDK `environment` for deployed builds.\n- Build-time env vars prefixed with `NEXT_PUBLIC_` must be set as CDK build args in `webapp.ts` — they are baked into the Docker image at build time and cannot be changed at runtime.\n\nSee `.env.local.example` for the full list.\n\n## Development guide\n\nSee [`AGENTS.md`](../AGENTS.md) in the repository root for authentication patterns, async job setup, DB migration, coding conventions, and constraints.\n"
  },
  {
    "path": "webapp/components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"src/app/globals.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"iconLibrary\": \"lucide\"\n}"
  },
  {
    "path": "webapp/eslint.config.mjs",
    "content": "import { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { FlatCompat } from '@eslint/eslintrc';\nimport { defineConfig } from 'eslint/config';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nconst compat = new FlatCompat({\n  baseDirectory: __dirname,\n});\n\nexport default defineConfig([\n  ...compat.extends('next/core-web-vitals', 'next/typescript'),\n  {\n    rules: {\n      '@typescript-eslint/no-unused-vars': 'off',\n    },\n  },\n]);\n"
  },
  {
    "path": "webapp/job.Dockerfile",
    "content": "FROM public.ecr.aws/lambda/nodejs:22 AS builder\nWORKDIR /build\nCOPY package*.json ./\nRUN --mount=type=cache,target=/root/.npm npm ci\nCOPY ./ ./\nRUN npx prisma generate\nRUN npx esbuild src/jobs/*.ts --bundle --outdir=dist --platform=node --charset=utf8 --external:@prisma/client\n\nFROM public.ecr.aws/lambda/nodejs:22 AS runner\n\nCOPY package*.json ./\nCOPY prisma ./prisma\nRUN --mount=type=cache,target=/root/.npm npm ci --omit=dev\nRUN npx prisma generate --generator client\nCOPY --from=builder /build/dist/. ./\n\nCMD [\"migration-runner.handler\"]\n"
  },
  {
    "path": "webapp/next.config.ts",
    "content": "import type { NextConfig } from 'next';\n\nconst allowedOrigins = ['localhost:3000'];\nif (process.env.ALLOWED_ORIGIN_HOST) {\n  allowedOrigins.push(process.env.ALLOWED_ORIGIN_HOST);\n}\n\nconst nextConfig: NextConfig = {\n  output: 'standalone',\n  experimental: {\n    webpackBuildWorker: true,\n    parallelServerBuildTraces: true,\n    parallelServerCompiles: true,\n    serverActions: {\n      allowedOrigins,\n    },\n  },\n  typescript: {\n    ignoreBuildErrors: process.env.SKIP_TS_BUILD == 'true',\n  },\n};\n\nexport default nextConfig;\n"
  },
  {
    "path": "webapp/package.json",
    "content": "{\n  \"name\": \"webapp\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbopack -p 3010\",\n    \"build\": \"next build\",\n    \"format\": \"prettier --write './**/*.{ts,tsx,mjs,mts}' && npx prisma format\",\n    \"format:check\": \"prettier --check './**/*.{ts,tsx,mjs,mts}' && npx prisma format --check\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"@aws-amplify/adapter-nextjs\": \"^1.7.1\",\n    \"@aws-sdk/client-lambda\": \"^3.995.0\",\n    \"@aws-sdk/client-ssm\": \"^3.995.0\",\n    \"@aws-sdk/client-translate\": \"^3.995.0\",\n    \"@next-safe-action/adapter-react-hook-form\": \"^1.0.14\",\n    \"@prisma/client\": \"^6.6.0\",\n    \"aws-amplify\": \"^6.16.2\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.488.0\",\n    \"next\": \"^16.1.6\",\n    \"next-safe-action\": \"^7.10.5\",\n    \"next-themes\": \"^0.4.6\",\n    \"prisma\": \"^6.6.0\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"react-hook-form\": \"^7.55.0\",\n    \"sonner\": \"^2.0.3\",\n    \"tailwind-merge\": \"^3.2.0\",\n    \"tw-animate-css\": \"^1.2.5\",\n    \"zod\": \"^3.24.2\"\n  },\n  \"devDependencies\": {\n    \"@eslint/eslintrc\": \"^3\",\n    \"@tailwindcss/postcss\": \"^4\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19\",\n    \"@types/react-dom\": \"^19\",\n    \"eslint\": \"^9\",\n    \"eslint-config-next\": \"15.3.0\",\n    \"prettier\": \"^3.5.3\",\n    \"tailwindcss\": \"^4\",\n    \"typescript\": \"^5\",\n    \"zod-prisma-types\": \"^3.2.4\"\n  }\n}\n"
  },
  {
    "path": "webapp/postcss.config.mjs",
    "content": "const config = {\n  plugins: ['@tailwindcss/postcss'],\n};\n\nexport default config;\n"
  },
  {
    "path": "webapp/prisma/schema.prisma",
    "content": "generator client {\n  provider = \"prisma-client-js\"\n  output   = \"../node_modules/.prisma/client\"\n}\n\ngenerator zod {\n  provider         = \"zod-prisma-types\"\n  output           = \"../src/lib/generated/prisma/zod\"\n  useMultipleFiles = true\n  writeBarrelFiles = false\n}\n\ndatasource db {\n  provider = \"postgresql\"\n  url      = env(\"DATABASE_URL\")\n}\n\nmodel User {\n  id       String     @id\n  TodoItem TodoItem[]\n}\n\nenum TodoItemStatus {\n  PENDING\n  COMPLETED\n}\n\nmodel TodoItem {\n  id          String         @id @default(uuid())\n  title       String\n  description String         @db.Text()\n  userId      String\n  status      TodoItemStatus\n\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n\n  user User @relation(fields: [userId], references: [id])\n\n  @@index([userId, createdAt])\n}\n"
  },
  {
    "path": "webapp/run.sh",
    "content": "#!/bin/bash -x\n\n[ ! -d '/tmp/cache' ] && mkdir -p /tmp/cache\n\nexec node server.js\n"
  },
  {
    "path": "webapp/src/app/(root)/actions.ts",
    "content": "'use server';\n\nimport { authActionClient } from '@/lib/safe-action';\nimport {\n  createTodoSchema,\n  deleteTodoSchema,\n  runTranslateJobSchema,\n  updateTodoSchema,\n  updateTodoStatusSchema,\n} from './schemas';\nimport { prisma } from '@/lib/prisma';\nimport { revalidatePath } from 'next/cache';\nimport { TodoItemStatus } from '@prisma/client';\nimport { runJob } from '@/lib/jobs';\n\nexport const createTodo = authActionClient.schema(createTodoSchema).action(async ({ parsedInput, ctx }) => {\n  const { title, description } = parsedInput;\n  const { userId } = ctx;\n\n  const todo = await prisma.todoItem.create({\n    data: {\n      title,\n      description,\n      userId,\n      status: TodoItemStatus.PENDING,\n    },\n  });\n\n  revalidatePath('/');\n  return { todo };\n});\n\nexport const updateTodo = authActionClient.schema(updateTodoSchema).action(async ({ parsedInput, ctx }) => {\n  const { id, title, description, status } = parsedInput;\n  const { userId } = ctx;\n\n  const todo = await prisma.todoItem.update({\n    where: {\n      id,\n      userId,\n    },\n    data: {\n      title,\n      description,\n      status,\n    },\n  });\n\n  revalidatePath('/');\n  return { todo };\n});\n\nexport const deleteTodo = authActionClient.schema(deleteTodoSchema).action(async ({ parsedInput, ctx }) => {\n  const { id } = parsedInput;\n  const { userId } = ctx;\n\n  await prisma.todoItem.delete({\n    where: {\n      id,\n      userId,\n    },\n  });\n\n  revalidatePath('/');\n  return { success: true };\n});\n\nexport const updateTodoStatus = authActionClient.schema(updateTodoStatusSchema).action(async ({ parsedInput, ctx }) => {\n  const { id, status } = parsedInput;\n  const { userId } = ctx;\n\n  const todo = await prisma.todoItem.update({\n    where: {\n      id,\n      userId,\n    },\n    data: {\n      status,\n    },\n  });\n\n  revalidatePath('/');\n  return { todo };\n});\n\nexport const runTranslateJob = authActionClient.schema(runTranslateJobSchema).action(async ({ parsedInput, ctx }) => {\n  const { id } = parsedInput;\n  const { userId } = ctx;\n  await runJob({\n    type: 'translate',\n    todoItemId: id,\n    userId: userId,\n  });\n});\n"
  },
  {
    "path": "webapp/src/app/(root)/components/CreateTodoForm.tsx",
    "content": "'use client';\n\nimport { useState } from 'react';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { useHookFormAction } from '@next-safe-action/adapter-react-hook-form/hooks';\nimport { createTodo } from '../actions';\nimport { createTodoSchema } from '../schemas';\nimport { toast } from 'sonner';\nimport { useEventBus } from '@/hooks/use-event-bus';\nimport { useRouter } from 'next/navigation';\n\nexport default function CreateTodoForm(props: { userId: string }) {\n  const [isFormOpen, setIsFormOpen] = useState(false);\n  const router = useRouter();\n\n  useEventBus({\n    channelName: `user/${props.userId}/jobs`,\n    onReceived: (data) => {\n      console.log('received', data);\n      router.refresh();\n    },\n  });\n\n  const {\n    form: { register, formState, reset },\n    action,\n    handleSubmitWithAction,\n  } = useHookFormAction(createTodo, zodResolver(createTodoSchema), {\n    actionProps: {\n      onSuccess: () => {\n        toast.success('Todo created successfully');\n        reset();\n        setIsFormOpen(false);\n      },\n      onError: ({ error }) => {\n        toast.error(typeof error === 'string' ? error : 'Failed to create todo');\n      },\n    },\n    formProps: {\n      defaultValues: {\n        title: '',\n        description: '',\n      },\n    },\n  });\n\n  if (!isFormOpen) {\n    return (\n      <div className=\"mb-6\">\n        <button\n          onClick={() => setIsFormOpen(true)}\n          className=\"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n        >\n          + Add New Todo\n        </button>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"bg-white shadow-md rounded-lg p-6 mb-6\">\n      <h2 className=\"text-lg font-medium text-gray-900 mb-4\">Create New Todo</h2>\n      <form onSubmit={handleSubmitWithAction} className=\"space-y-4\">\n        <div>\n          <label htmlFor=\"title\" className=\"block text-sm font-medium text-gray-700\">\n            Title\n          </label>\n          <input\n            id=\"title\"\n            {...register('title')}\n            placeholder=\"Your TODO item title.\"\n            className=\"mt-1 p-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"\n          />\n          {formState.errors.title && <p className=\"mt-1 text-sm text-red-600\">{formState.errors.title.message}</p>}\n        </div>\n\n        <div>\n          <label htmlFor=\"description\" className=\"block text-sm font-medium text-gray-700\">\n            Description\n          </label>\n          <textarea\n            id=\"description\"\n            {...register('description')}\n            rows={3}\n            placeholder=\"Describe your TODO item.\"\n            className=\"mt-1 p-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"\n          />\n          {formState.errors.description && (\n            <p className=\"mt-1 text-sm text-red-600\">{formState.errors.description.message}</p>\n          )}\n        </div>\n\n        <div className=\"flex justify-end space-x-2\">\n          <button\n            type=\"button\"\n            onClick={() => setIsFormOpen(false)}\n            className=\"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-gray-700 bg-gray-200 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500\"\n          >\n            Cancel\n          </button>\n          <button\n            type=\"submit\"\n            disabled={action.isExecuting}\n            className=\"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n          >\n            {action.isExecuting ? 'Creating...' : 'Create Todo'}\n          </button>\n        </div>\n      </form>\n    </div>\n  );\n}\n"
  },
  {
    "path": "webapp/src/app/(root)/components/TodoItem.tsx",
    "content": "'use client';\n\nimport { TodoItem, TodoItemStatus } from '@prisma/client';\nimport { useState } from 'react';\nimport { useHookFormAction } from '@next-safe-action/adapter-react-hook-form/hooks';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { updateTodo, deleteTodo, updateTodoStatus, runTranslateJob } from '../actions';\nimport { updateTodoSchema } from '../schemas';\nimport { useAction } from 'next-safe-action/hooks';\nimport { toast } from 'sonner';\n\ninterface TodoItemProps {\n  todo: TodoItem;\n}\n\nexport default function TodoItemComponent({ todo }: TodoItemProps) {\n  const [isEditing, setIsEditing] = useState(false);\n  const { execute, status: translateStatus } = useAction(runTranslateJob, {\n    onSuccess: () => {\n      toast.success('Translation job started successfully');\n    },\n    onError: (error) => {\n      toast.error(typeof error === 'string' ? error : 'Failed to start translation job');\n    },\n  });\n\n  // Update Todo Form Action with React Hook Form\n  const {\n    form: { register: registerUpdate, formState: updateFormState },\n    action: updateAction,\n    handleSubmitWithAction: handleUpdate,\n  } = useHookFormAction(updateTodo, zodResolver(updateTodoSchema), {\n    actionProps: {\n      onSuccess: () => {\n        toast.success('Todo updated successfully');\n        setIsEditing(false);\n      },\n      onError: ({ error }) => {\n        toast.error(typeof error === 'string' ? error : 'Failed to update todo');\n      },\n    },\n    formProps: {\n      defaultValues: {\n        id: todo.id,\n        title: todo.title,\n        description: todo.description,\n        status: todo.status,\n      },\n    },\n  });\n\n  // Delete Todo Action - Simple action without form\n  const { execute: executeDelete, status: deleteStatus } = useAction(deleteTodo, {\n    onSuccess: () => {\n      toast.success('Todo deleted successfully');\n    },\n    onError: (error) => {\n      toast.error(typeof error === 'string' ? error : 'Failed to delete todo');\n    },\n  });\n\n  // Update Status Action - Simple action without form\n  const { execute: executeStatusUpdate, status: statusStatus } = useAction(updateTodoStatus, {\n    onSuccess: () => {\n      toast.success('Status updated successfully');\n    },\n    onError: (error) => {\n      toast.error(typeof error === 'string' ? error : 'Failed to update status');\n    },\n  });\n\n  const handleDelete = () => {\n    if (confirm('Are you sure you want to delete this todo?')) {\n      executeDelete({ id: todo.id });\n    }\n  };\n\n  const toggleStatus = () => {\n    const newStatus = todo.status === TodoItemStatus.PENDING ? TodoItemStatus.COMPLETED : TodoItemStatus.PENDING;\n\n    executeStatusUpdate({\n      id: todo.id,\n      status: newStatus,\n    });\n  };\n\n  if (isEditing) {\n    return (\n      <div className=\"border p-4 rounded-md shadow-sm mb-4 bg-white\">\n        <form onSubmit={handleUpdate} className=\"space-y-4\">\n          <input type=\"hidden\" {...registerUpdate('id')} value={todo.id} />\n          <input type=\"hidden\" {...registerUpdate('status')} value={todo.status} />\n\n          <div>\n            <label className=\"block text-sm font-medium text-gray-700\">Title</label>\n            <input\n              type=\"text\"\n              {...registerUpdate('title')}\n              className=\"mt-1 p-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"\n            />\n            {updateFormState.errors.title && (\n              <p className=\"mt-1 text-sm text-red-600\">{updateFormState.errors.title.message}</p>\n            )}\n          </div>\n\n          <div>\n            <label className=\"block text-sm font-medium text-gray-700\">Description</label>\n            <textarea\n              {...registerUpdate('description')}\n              rows={3}\n              className=\"mt-1 p-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"\n            />\n            {updateFormState.errors.description && (\n              <p className=\"mt-1 text-sm text-red-600\">{updateFormState.errors.description.message}</p>\n            )}\n          </div>\n\n          <div className=\"flex justify-end space-x-2\">\n            <button\n              type=\"button\"\n              onClick={() => setIsEditing(false)}\n              className=\"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-gray-700 bg-gray-200 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500\"\n            >\n              Cancel\n            </button>\n            <button\n              type=\"submit\"\n              disabled={updateAction.isExecuting}\n              className=\"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n            >\n              {updateAction.isExecuting ? 'Saving...' : 'Save'}\n            </button>\n          </div>\n        </form>\n      </div>\n    );\n  }\n\n  return (\n    <div\n      className={`border p-4 rounded-md shadow-sm mb-4 ${todo.status === TodoItemStatus.COMPLETED ? 'bg-gray-50' : 'bg-white'}`}\n    >\n      <div className=\"flex items-start justify-between\">\n        <div className=\"flex items-center\">\n          <input\n            type=\"checkbox\"\n            checked={todo.status === TodoItemStatus.COMPLETED}\n            onChange={toggleStatus}\n            disabled={statusStatus === 'executing'}\n            className=\"h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded\"\n          />\n          <div className=\"ml-3\">\n            <h3\n              className={`text-lg font-medium ${todo.status === TodoItemStatus.COMPLETED ? 'line-through text-gray-500' : 'text-gray-900'}`}\n            >\n              {todo.title}\n            </h3>\n            <p\n              className={`text-sm ${todo.status === TodoItemStatus.COMPLETED ? 'line-through text-gray-400' : 'text-gray-600'}`}\n            >\n              {todo.description}\n            </p>\n            <p className=\"text-xs text-gray-400 mt-1\">Created: {new Date(todo.createdAt).toLocaleDateString()}</p>\n          </div>\n        </div>\n        <div className=\"flex space-x-2\">\n          <button\n            onClick={() => setIsEditing(true)}\n            disabled={deleteStatus === 'executing' || statusStatus === 'executing' || translateStatus === 'executing'}\n            className=\"text-indigo-600 hover:text-indigo-900 disabled:opacity-50\"\n          >\n            Edit\n          </button>\n          <button\n            onClick={handleDelete}\n            disabled={deleteStatus === 'executing' || statusStatus === 'executing' || translateStatus === 'executing'}\n            className=\"text-red-600 hover:text-red-900 disabled:opacity-50\"\n          >\n            {deleteStatus === 'executing' ? 'Deleting...' : 'Delete'}\n          </button>\n          <button\n            onClick={() => execute({ id: todo.id })}\n            disabled={deleteStatus === 'executing' || statusStatus === 'executing' || translateStatus === 'executing'}\n            className=\"text-green-600 hover:text-green-900 disabled:opacity-50\"\n          >\n            {translateStatus === 'executing' ? 'Translating...' : 'Translate'}\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "webapp/src/app/(root)/page.tsx",
    "content": "import { prisma } from '@/lib/prisma';\nimport { getAuthSession } from '@/lib/auth';\nimport TodoItemComponent from './components/TodoItem';\nimport CreateTodoForm from './components/CreateTodoForm';\nimport { TodoItemStatus } from '@prisma/client';\nimport Header from '@/components/Header';\n\nexport default async function Home() {\n  const { userId } = await getAuthSession();\n\n  const todos = await prisma.todoItem.findMany({\n    where: {\n      userId,\n    },\n    orderBy: {\n      createdAt: 'desc',\n    },\n  });\n\n  const pendingTodos = todos.filter((todo) => todo.status === TodoItemStatus.PENDING);\n  const completedTodos = todos.filter((todo) => todo.status === TodoItemStatus.COMPLETED);\n\n  return (\n    <div className=\"min-h-screen flex flex-col\">\n      <Header />\n\n      <main className=\"flex-grow\">\n        <div className=\"max-w-4xl mx-auto px-4 py-8\">\n          <CreateTodoForm userId={userId} />\n\n          <div className=\"mb-8\">\n            <h2 className=\"text-xl font-semibold mb-4\">Pending Tasks ({pendingTodos.length})</h2>\n            {pendingTodos.length === 0 ? (\n              <p className=\"text-gray-500 text-center py-4\">No pending tasks. Great job!</p>\n            ) : (\n              <div>\n                {pendingTodos.map((todo) => (\n                  <TodoItemComponent key={todo.id} todo={todo} />\n                ))}\n              </div>\n            )}\n          </div>\n\n          <div>\n            <h2 className=\"text-xl font-semibold mb-4\">Completed Tasks ({completedTodos.length})</h2>\n            {completedTodos.length === 0 ? (\n              <p className=\"text-gray-500 text-center py-4\">No completed tasks yet.</p>\n            ) : (\n              <div>\n                {completedTodos.map((todo) => (\n                  <TodoItemComponent key={todo.id} todo={todo} />\n                ))}\n              </div>\n            )}\n          </div>\n        </div>\n      </main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "webapp/src/app/(root)/schemas.ts",
    "content": "import { z } from 'zod';\nimport TodoItemStatusSchema from '@/lib/generated/prisma/zod/inputTypeSchemas/TodoItemStatusSchema';\n\nexport const createTodoSchema = z.object({\n  title: z.string().min(1, 'Title is required'),\n  description: z.string().min(1, 'Description is required'),\n});\n\nexport const updateTodoSchema = z.object({\n  id: z.string().uuid(),\n  title: z.string().min(1, 'Title is required'),\n  description: z.string().min(1, 'Description is required'),\n  status: TodoItemStatusSchema,\n});\n\nexport const deleteTodoSchema = z.object({\n  id: z.string().uuid(),\n});\n\nexport const updateTodoStatusSchema = z.object({\n  id: z.string().uuid(),\n  status: TodoItemStatusSchema,\n});\n\nexport const runTranslateJobSchema = z.object({\n  id: z.string().uuid(),\n});\n"
  },
  {
    "path": "webapp/src/app/api/auth/[slug]/route.ts",
    "content": "import { createAuthRouteHandlers } from '@/lib/amplifyServerUtils';\n\nexport const GET = createAuthRouteHandlers({\n  redirectOnSignInComplete: '/auth-callback',\n  redirectOnSignOutComplete: '/sign-in',\n});\n"
  },
  {
    "path": "webapp/src/app/api/cognito-token/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { tryGetAuthSession } from '@/lib/auth';\n\nexport async function GET() {\n  try {\n    const session = await tryGetAuthSession();\n    if (!session) {\n      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n    }\n\n    return NextResponse.json({\n      accessToken: session.accessToken,\n    });\n  } catch (error) {\n    console.error('Error fetching Cognito token:', error);\n    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });\n  }\n}\n"
  },
  {
    "path": "webapp/src/app/auth-callback/page.tsx",
    "content": "import { redirect } from 'next/navigation';\nimport { getAuthSession } from '@/lib/auth';\nimport { prisma } from '@/lib/prisma';\n\nexport const dynamic = 'force-dynamic';\n\nexport default async function AuthCallbackPage() {\n  const { userId } = await getAuthSession();\n\n  const user = await prisma.user.findUnique({ where: { id: userId } });\n  if (user == null) {\n    await prisma.user.create({ data: { id: userId } });\n  }\n\n  redirect('/');\n}\n"
  },
  {
    "path": "webapp/src/app/globals.css",
    "content": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --font-sans: var(--font-geist-sans);\n  --font-mono: var(--font-geist-mono);\n  --color-sidebar-ring: var(--sidebar-ring);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar: var(--sidebar);\n  --color-chart-5: var(--chart-5);\n  --color-chart-4: var(--chart-4);\n  --color-chart-3: var(--chart-3);\n  --color-chart-2: var(--chart-2);\n  --color-chart-1: var(--chart-1);\n  --color-ring: var(--ring);\n  --color-input: var(--input);\n  --color-border: var(--border);\n  --color-destructive: var(--destructive);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-accent: var(--accent);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-muted: var(--muted);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-secondary: var(--secondary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-primary: var(--primary);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-popover: var(--popover);\n  --color-card-foreground: var(--card-foreground);\n  --color-card: var(--card);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n}\n\n:root {\n  --radius: 0.625rem;\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.205 0 0);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.577 0.245 27.325);\n  --border: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(0.708 0 0);\n  --chart-1: oklch(0.646 0.222 41.116);\n  --chart-2: oklch(0.6 0.118 184.704);\n  --chart-3: oklch(0.398 0.07 227.392);\n  --chart-4: oklch(0.828 0.189 84.429);\n  --chart-5: oklch(0.769 0.188 70.08);\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.145 0 0);\n  --sidebar-primary: oklch(0.205 0 0);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.97 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0.922 0 0);\n  --sidebar-ring: oklch(0.708 0 0);\n}\n\n.dark {\n  --background: oklch(0.145 0 0);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.205 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.922 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.269 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n  --chart-1: oklch(0.488 0.243 264.376);\n  --chart-2: oklch(0.696 0.17 162.48);\n  --chart-3: oklch(0.769 0.188 70.08);\n  --chart-4: oklch(0.627 0.265 303.9);\n  --chart-5: oklch(0.645 0.246 16.439);\n  --sidebar: oklch(0.205 0 0);\n  --sidebar-foreground: oklch(0.985 0 0);\n  --sidebar-primary: oklch(0.488 0.243 264.376);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.269 0 0);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.556 0 0);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n"
  },
  {
    "path": "webapp/src/app/layout.tsx",
    "content": "import './globals.css';\nimport { Toaster } from 'sonner';\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html>\n      <head>\n        <title>AWS Serverless TODO</title>\n        {/* Comment out this meta tag if you want to enable search engine crawling */}\n        <meta name=\"robots\" content=\"noindex, nofollow\" />\n      </head>\n      <body>\n        {children}\n        <Toaster position=\"top-right\" />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "webapp/src/app/sign-in/page.tsx",
    "content": "export default function SignInPage() {\n  return (\n    <div className=\"min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8\">\n      <div className=\"sm:mx-auto sm:w-full sm:max-w-md\">\n        <h2 className=\"mt-6 text-center text-3xl font-extrabold text-gray-900\">Todo App</h2>\n        <p className=\"mt-2 text-center text-sm text-gray-600\">Sign in to manage your tasks</p>\n      </div>\n\n      <div className=\"mt-8 sm:mx-auto sm:w-full sm:max-w-md\">\n        <div className=\"bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10\">\n          <div className=\"flex flex-col items-center\">\n            <p className=\"mb-6 text-center text-sm text-gray-600\">\n              Please sign in with your Cognito account to continue\n            </p>\n\n            {/* Use <a> instead of <Link> to trigger a full-page navigation.\n                The sign-in route returns a 302 redirect to Cognito, which\n                would cause a CORS error if fetched via client-side navigation. */}\n            {/* eslint-disable-next-line @next/next/no-html-link-for-pages */}\n            <a\n              href=\"/api/auth/sign-in\"\n              // you can add a query string to change the locale of cognito managed login page.\n              // href=\"/api/auth/sign-in?lang=ja\"\n              className=\"w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n            >\n              Sign in with Cognito\n            </a>\n          </div>\n        </div>\n      </div>\n\n      <footer className=\"mt-8 text-center text-sm text-gray-500\">\n        <p>© {new Date().getFullYear()} Todo App. All rights reserved.</p>\n      </footer>\n    </div>\n  );\n}\n"
  },
  {
    "path": "webapp/src/components/Header.tsx",
    "content": "import Link from 'next/link';\n\nexport default function Header() {\n  return (\n    <header className=\"bg-indigo-600 text-white shadow-md\">\n      <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n        <div className=\"flex justify-between h-16 items-center\">\n          <div className=\"flex-shrink-0\">\n            <Link href=\"/\" className=\"text-xl font-bold\">\n              Todo App\n            </Link>\n          </div>\n          <div>\n            {/* Use <a> instead of <Link> to trigger a full-page navigation.\n                The sign-out route returns a 302 redirect to Cognito, which\n                would cause a CORS error if fetched via client-side navigation. */}\n            {/* eslint-disable-next-line @next/next/no-html-link-for-pages */}\n            <a\n              href=\"/api/auth/sign-out\"\n              className=\"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-indigo-600 bg-white hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n            >\n              Sign Out\n            </a>\n          </div>\n        </div>\n      </div>\n    </header>\n  );\n}\n"
  },
  {
    "path": "webapp/src/components/ui/sonner.tsx",
    "content": "'use client';\n\nimport { useTheme } from 'next-themes';\nimport { Toaster as Sonner, ToasterProps } from 'sonner';\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = 'system' } = useTheme();\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps['theme']}\n      className=\"toaster group\"\n      style={\n        {\n          '--normal-bg': 'var(--popover)',\n          '--normal-text': 'var(--popover-foreground)',\n          '--normal-border': 'var(--border)',\n        } as React.CSSProperties\n      }\n      {...props}\n    />\n  );\n};\n\nexport { Toaster };\n"
  },
  {
    "path": "webapp/src/hooks/use-event-bus.ts",
    "content": "import { decodeJWT } from 'aws-amplify/auth';\nimport { Amplify } from 'aws-amplify';\nimport { events } from 'aws-amplify/data';\nimport { useEffect } from 'react';\n\nAmplify.configure(\n  {\n    API: {\n      Events: {\n        endpoint: `${process.env.NEXT_PUBLIC_EVENT_HTTP_ENDPOINT}/event`,\n        region: process.env.NEXT_PUBLIC_AWS_REGION,\n        defaultAuthMode: 'userPool',\n      },\n    },\n  },\n  {\n    Auth: {\n      tokenProvider: {\n        getTokens: async () => {\n          const res = await fetch('/api/cognito-token');\n          const { accessToken } = await res.json();\n          return {\n            accessToken: decodeJWT(accessToken),\n          };\n        },\n      },\n    },\n  },\n);\n\ntype UseEventBusProps = {\n  channelName: string;\n  onReceived: (payload: unknown) => void;\n};\n\nexport const useEventBus = ({ channelName, onReceived }: UseEventBusProps) => {\n  useEffect(() => {\n    const connectAndSubscribe = async () => {\n      const channel = await events.connect(`event-bus/${channelName}`);\n      console.log(`subscribing channel ${channelName}`);\n\n      channel.subscribe({\n        next: (data) => {\n          onReceived(data);\n        },\n        error: (err) => console.error('error', err),\n      });\n      return channel;\n    };\n\n    const pr = connectAndSubscribe();\n\n    return () => {\n      pr.then((channel) => {\n        channel.close();\n      });\n    };\n  }, [channelName, onReceived]);\n};\n"
  },
  {
    "path": "webapp/src/jobs/async-job/translate.ts",
    "content": "import { sendEvent } from '@/lib/events';\nimport { prisma } from '@/lib/prisma';\nimport { z } from 'zod';\nimport { TranslateClient, TranslateTextCommand, TranslateTextCommandInput } from '@aws-sdk/client-translate';\n\nexport const translateJobSchema = z.object({\n  type: z.literal('translate'),\n  todoItemId: z.string(),\n  userId: z.string(),\n});\n\nexport const translateJobHandler = async (params: z.infer<typeof translateJobSchema>) => {\n  const todoItem = await prisma.todoItem.findUnique({ where: { id: params.todoItemId } });\n  if (!todoItem) {\n    console.log(`item ${params.todoItemId} not found.`);\n    return;\n  }\n\n  // Use amazon translate and create a new todoItem record with translated todoItem.title\n  const translateClient = new TranslateClient({\n    region: process.env.AWS_REGION || 'ap-northeast-1',\n  });\n\n  const targetLanguage = 'ja';\n  const translateResult = await translateClient.send(\n    new TranslateTextCommand({\n      Text: todoItem.title,\n      SourceLanguageCode: 'auto',\n      TargetLanguageCode: targetLanguage,\n    }),\n  );\n\n  if (translateResult.TranslatedText) {\n    // Create a new todo item with the translated title\n    const translatedTodoItem = await prisma.todoItem.create({\n      data: {\n        title: translateResult.TranslatedText,\n        description: `Translated from: ${todoItem.title} (from ${translateResult.SourceLanguageCode} to ${targetLanguage})`,\n        userId: params.userId,\n        status: todoItem.status,\n      },\n    });\n\n    console.log(`Created translated todo item: ${translatedTodoItem.id}`);\n  }\n\n  await sendEvent(`user/${params.userId}/jobs`, { type: 'completed' });\n};\n"
  },
  {
    "path": "webapp/src/jobs/async-job-runner.ts",
    "content": "import { translateJobHandler, translateJobSchema } from '@/jobs/async-job/translate';\nimport { Handler } from 'aws-lambda';\nimport { z } from 'zod';\n\nconst jobPayloadPropsSchema = z.discriminatedUnion('type', [\n  translateJobSchema,\n  z.object({\n    type: z.literal('example'),\n  }),\n]);\n\nexport type JobPayloadProps = z.infer<typeof jobPayloadPropsSchema>;\n\nexport const handler: Handler<unknown> = async (event, context) => {\n  const { data: payload, error } = jobPayloadPropsSchema.safeParse(event);\n  if (error) {\n    console.log(error);\n    throw new Error(error.toString());\n  }\n\n  switch (payload.type) {\n    case 'translate':\n      await translateJobHandler(payload);\n      break;\n    case 'example':\n      console.log('example job processed');\n      break;\n  }\n};\n"
  },
  {
    "path": "webapp/src/jobs/migration-runner.ts",
    "content": "import { Handler } from 'aws-lambda';\nimport { execFile } from 'child_process';\nimport path from 'path';\n\nexport const handler: Handler = async (event, _) => {\n  // This Lambda function is invoked in two contexts:\n  // 1. CDK Trigger: Automatically invoked during `cdk deploy` with default payload (no command specified, defaults to 'deploy')\n  // 2. Manual Invocation: Use AWS CLI to invoke with custom commands\n  //    Example: aws lambda invoke --function-name <FUNCTION_NAME> --payload '{\"command\":\"force\"}' --cli-binary-format raw-in-base64-out /dev/stdout\n  //    The function name and command template are available in the CloudFormation stack outputs after deployment\n  //\n  // Available commands are:\n  //   deploy: create new database if absent and apply all migrations to the existing database.\n  //   reset: delete existing database, create new one, and apply all migrations. NOT for production environment.\n  // If you want to add commands, please refer to: https://www.prisma.io/docs/concepts/components/prisma-migrate\n  const command: string = event.command ?? 'deploy';\n\n  let options: string[] = [];\n\n  if (command == 'force') {\n    options = ['--accept-data-loss'];\n  } else if (command == 'reset') {\n    options = ['--force-reset'];\n    throw new Error('reset command is forbidden!');\n  }\n\n  // Currently we don't have any direct method to invoke prisma migration programmatically.\n  // As a workaround, we spawn migration script as a child process and wait for its completion.\n  // Please also refer to the following GitHub issue: https://github.com/prisma/prisma/issues/4703\n  await runPrismaDbPush(options);\n};\n\n// Aurora Serverless v2 may be resuming from auto-pause (0 ACU) during CDK deployment,\n// which takes approximately 15 seconds. Retry transient connection errors with exponential backoff.\n// https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2-auto-pause.html\nasync function runPrismaDbPush(options: string[], maxRetries = 5, baseDelay = 3000): Promise<void> {\n  for (let attempt = 1; attempt <= maxRetries; attempt++) {\n    const { exitCode, stdout, stderr } = await new Promise<{\n      exitCode: number;\n      stdout: string;\n      stderr: string;\n    }>((resolve) => {\n      execFile(\n        path.resolve('./node_modules/prisma/build/index.js'),\n        ['db', 'push', '--skip-generate'].concat(options),\n        (error, stdout, stderr) => {\n          resolve({\n            exitCode: error ? (typeof error.code === 'number' ? error.code : 1) : 0,\n            stdout,\n            stderr,\n          });\n        },\n      );\n    });\n\n    console.log(`prisma db push attempt ${attempt}/${maxRetries}`, { exitCode, stdout, stderr });\n\n    if (exitCode === 0) return;\n\n    const isRetryable =\n      stderr.includes('P1001') || stderr.includes(\"Can't reach database\") || stderr.includes('Connection refused');\n\n    if (!isRetryable || attempt === maxRetries) {\n      throw new Error(`prisma db push failed after ${attempt} attempt(s): ${stderr}`);\n    }\n\n    const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000;\n    console.log(`Retrying prisma db push in ${Math.round(delay)}ms...`);\n    await new Promise((r) => setTimeout(r, delay));\n  }\n}\n"
  },
  {
    "path": "webapp/src/lib/amplifyServerUtils.ts",
    "content": "import { createServerRunner } from '@aws-amplify/adapter-nextjs';\nimport { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';\n\nif (process.env.AMPLIFY_APP_ORIGIN_SOURCE_PARAMETER) {\n  const ssm = new SSMClient({});\n  try {\n    const res = await ssm.send(new GetParameterCommand({ Name: process.env.AMPLIFY_APP_ORIGIN_SOURCE_PARAMETER }));\n    process.env.AMPLIFY_APP_ORIGIN = res.Parameter?.Value;\n  } catch (e) {\n    console.log(e);\n  }\n}\n\nexport const { runWithAmplifyServerContext, createAuthRouteHandlers } = createServerRunner({\n  config: {\n    Auth: {\n      Cognito: {\n        userPoolId: process.env.USER_POOL_ID!,\n        userPoolClientId: process.env.USER_POOL_CLIENT_ID!,\n        loginWith: {\n          oauth: {\n            redirectSignIn: [`${process.env.AMPLIFY_APP_ORIGIN!}/api/auth/sign-in-callback`],\n            redirectSignOut: [`${process.env.AMPLIFY_APP_ORIGIN!}/api/auth/sign-out-callback`],\n            responseType: 'code',\n            domain: process.env.COGNITO_DOMAIN!,\n            scopes: ['profile', 'openid', 'aws.cognito.signin.user.admin'],\n          },\n        },\n      },\n    },\n  },\n  runtimeOptions: {\n    cookies: {\n      sameSite: 'lax',\n      maxAge: 60 * 60 * 24 * 7, // 7 days\n    },\n  },\n});\n"
  },
  {
    "path": "webapp/src/lib/auth.ts",
    "content": "import { cache } from 'react';\nimport { cookies } from 'next/headers';\nimport { fetchAuthSession } from 'aws-amplify/auth/server';\nimport { runWithAmplifyServerContext } from '@/lib/amplifyServerUtils';\nimport { prisma } from '@/lib/prisma';\n\n/**\n * Get the authenticated session without DB access.\n * Use when only userId/email/accessToken is needed.\n * Memoized per request via React cache().\n */\nexport const getAuthSession = cache(async () => {\n  const session = await runWithAmplifyServerContext({\n    nextServerContext: { cookies },\n    operation: (contextSpec) => fetchAuthSession(contextSpec),\n  });\n  if (session.userSub == null || session.tokens?.idToken == null || session.tokens?.accessToken == null) {\n    throw new Error('session not found');\n  }\n  const email = session.tokens.idToken.payload.email;\n  if (typeof email != 'string') {\n    throw new Error(`invalid email ${session.userSub}.`);\n  }\n  return {\n    userId: session.userSub,\n    email,\n    accessToken: session.tokens.accessToken.toString(),\n  };\n});\n\n/**\n * Try to get the authenticated session, returning null on failure.\n * Use in API Routes to avoid try/catch boilerplate for auth checks.\n */\nexport async function tryGetAuthSession() {\n  try {\n    return await getAuthSession();\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Get the authenticated session with the User record from DB.\n * Memoized per request via React cache().\n */\nexport const getSessionWithUser = cache(async () => {\n  const auth = await getAuthSession();\n  const user = await prisma.user.findUnique({ where: { id: auth.userId } });\n  if (user == null) {\n    throw new UserNotFoundError(auth.userId);\n  }\n  return { ...auth, user };\n});\n\nexport class UserNotFoundError {\n  constructor(public readonly userId: string) {}\n}\n"
  },
  {
    "path": "webapp/src/lib/events.ts",
    "content": "import { SignatureV4 } from '@smithy/signature-v4';\nimport { defaultProvider } from '@aws-sdk/credential-provider-node';\nimport { HttpRequest } from '@smithy/protocol-http';\nimport { Sha256 } from '@aws-crypto/sha256-js';\n\nconst httpEndpoint = process.env.EVENT_HTTP_ENDPOINT!;\nconst region = process.env.AWS_REGION!;\n\nexport async function sendEvent(channelName: string, payload: unknown) {\n  if (httpEndpoint == null) {\n    console.log(`event api is not configured!`);\n    return;\n  }\n\n  const endpoint = `${httpEndpoint}/event`;\n  const url = new URL(endpoint);\n\n  // generate request\n  const requestToBeSigned = new HttpRequest({\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      host: url.host,\n    },\n    hostname: url.host,\n    body: JSON.stringify({\n      channel: `event-bus/${channelName}`,\n      events: [JSON.stringify({ payload })],\n    }),\n    path: url.pathname,\n  });\n\n  // initialize signer\n  const signer = new SignatureV4({\n    credentials: defaultProvider(),\n    region,\n    service: 'appsync',\n    sha256: Sha256,\n  });\n\n  // sign request\n  const signed = await signer.sign(requestToBeSigned);\n  const request = new Request(endpoint, signed);\n\n  // publish event via fetch\n  const res = await fetch(request);\n\n  const t = await res.text();\n  console.log(t);\n}\n"
  },
  {
    "path": "webapp/src/lib/jobs.ts",
    "content": "import { JobPayloadProps } from '@/jobs/async-job-runner';\nimport { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda';\n\nconst lambda = new LambdaClient();\n\nconst handlerArn = process.env.ASYNC_JOB_HANDLER_ARN!;\n\nexport async function runJob(props: JobPayloadProps) {\n  await lambda.send(\n    new InvokeCommand({\n      FunctionName: handlerArn,\n      InvocationType: 'Event',\n      Payload: JSON.stringify(props),\n    }),\n  );\n}\n"
  },
  {
    "path": "webapp/src/lib/prisma.ts",
    "content": "import { Prisma, PrismaClient } from '@prisma/client';\n\n// https://www.prisma.io/docs/guides/nextjs\n\nconst globalForPrisma = global as unknown as {\n  prisma: PrismaClient;\n};\n\n// Determine if an error is a transient connection issue that may resolve on retry.\n// Aurora Serverless v2 can drop connections due to idle_session_timeout (60s) or auto-pause,\n// and resume takes approximately 15 seconds.\n// https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2-auto-pause.html\nfunction isRetryableError(error: unknown): boolean {\n  if (!(error instanceof Error)) return false;\n  const code = (error as { code?: string }).code;\n  if (\n    code === 'P2024' || // Connection pool timeout\n    code === 'P1001' || // Can't reach database server\n    code === 'P1017' // Server has closed the connection\n  ) {\n    return true;\n  }\n  const msg = error.message;\n  return (\n    msg.includes('idle-session timeout') ||\n    msg.includes('terminating connection') ||\n    msg.includes('Connection terminated') ||\n    msg.includes('Timed out fetching a new connection from the connection pool') ||\n    msg.includes('ECONNRESET')\n  );\n}\n\nconst basePrisma = new PrismaClient();\n\nasync function withRetry<T>(fn: () => Promise<T>, maxRetries = 3, baseDelay = 500): Promise<T> {\n  let lastError: unknown;\n  for (let attempt = 0; attempt <= maxRetries; attempt++) {\n    try {\n      const result = await fn();\n      if (attempt > 0) {\n        console.warn(`Prisma query succeeded after ${attempt} retry(s)`);\n      }\n      return result;\n    } catch (error) {\n      lastError = error;\n      if (attempt === maxRetries || !isRetryableError(error)) throw error;\n      // Discard stale connections before retrying\n      await basePrisma.$disconnect();\n      const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 100;\n      console.warn(`Prisma retry attempt ${attempt + 1}/${maxRetries}, waiting ${Math.round(delay)}ms`);\n      await new Promise((r) => setTimeout(r, delay));\n    }\n  }\n  throw lastError;\n}\n\nconst retryExtension = Prisma.defineExtension({\n  name: 'retry-on-connection-error',\n  query: {\n    $allModels: {\n      async $allOperations({ args, query }) {\n        return withRetry(() => query(args));\n      },\n    },\n  },\n});\n\nexport const prisma = basePrisma.$extends(retryExtension) as unknown as PrismaClient;\n\nif (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;\n"
  },
  {
    "path": "webapp/src/lib/safe-action.ts",
    "content": "import { getSessionWithUser } from '@/lib/auth';\nimport { createSafeActionClient, DEFAULT_SERVER_ERROR_MESSAGE } from 'next-safe-action';\n\nexport class MyCustomError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = 'MyCustomError';\n  }\n}\n\nconst actionClient = createSafeActionClient({\n  handleServerError(e) {\n    // Log to console.\n    console.error('Action error:', e.message);\n\n    // In this case, we can use the 'MyCustomError` class to unmask errors\n    // and return them with their actual messages to the client.\n    if (e instanceof MyCustomError) {\n      return e.message;\n    }\n\n    // Every other error that occurs will be masked with the default message.\n    return DEFAULT_SERVER_ERROR_MESSAGE;\n  },\n});\n\nexport const authActionClient = actionClient.use(async ({ next }) => {\n  const { user } = await getSessionWithUser();\n  return next({ ctx: { userId: user.id } });\n});\n"
  },
  {
    "path": "webapp/src/lib/utils.ts",
    "content": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
  },
  {
    "path": "webapp/src/proxy.ts",
    "content": "import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport { fetchAuthSession } from 'aws-amplify/auth/server';\nimport { runWithAmplifyServerContext } from '@/lib/amplifyServerUtils';\n\nexport async function proxy(request: NextRequest) {\n  const response = NextResponse.next();\n\n  const authenticated = await runWithAmplifyServerContext({\n    nextServerContext: { request, response },\n    operation: async (contextSpec) => {\n      try {\n        const session = await fetchAuthSession(contextSpec);\n        return session.tokens?.accessToken !== undefined && session.tokens?.idToken !== undefined;\n      } catch (error) {\n        console.log(error);\n        return false;\n      }\n    },\n  });\n\n  if (authenticated) {\n    return response;\n  }\n\n  return NextResponse.redirect(new URL('/sign-in', request.url));\n}\n\nexport const config = {\n  matcher: [\n    /*\n     * Match all request paths except for the ones starting with:\n     * - api (API routes)\n     * - _next/static (static files)\n     * - _next/image (image optimization files)\n     * - favicon.ico (favicon file)\n     */\n    '/((?!api|_next/static|_next/image|favicon.ico|sign-in).*)',\n  ],\n};\n"
  },
  {
    "path": "webapp/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\n        \"./src/*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  }
]