Skip to content

Commit 599b95d

Browse files
9paceclaude
authored andcommitted
feat(gen2-migration): generate per-table stacks for multiple DDB tables (#14688)
* feat: avatar s3 feature for discussions app * feat(cli): generate per-table stacks for multiple DDB tables Previously, all DynamoDB tables were placed in a single shared storage stack, causing conflicts when multiple tables existed. Each DDB table now gets its own nested stack via backend.createStack('storage<name>'), matching the Gen1 nested stack naming convention. Also fixes resolveOutputs() crash when a Gen2 storage stack has no Outputs section (happens when no cross-stack references exist for a table). Closes #14608, #14597 * fix(discussions): replace hand-crafted snapshots with real infrastructure data Recapture discussions app snapshots from live deployments: - Gen1: amplify-discussionsblade-blade-4edfd (activity + bookmarks tables) - Gen2: amplify-d1skq8aomhb772-e2etest-branch (per-table stacks) Pre-generate inputs use real amplify-pull data for bookmarks resource. Pre-refactor templates fetched from deployed CloudFormation stacks. Post-generate and post-refactor regenerated by test framework. Also adds bookmarks to migration-config.json and fixes snapshot.ts to use TemplateStage: Original for unprocessed templates. * docs(discussions): add bookmarks table setup instructions to README * fix(discussions): sanitize Amplify App ID in Gen2 snapshot files Replace raw app ID d1skq8aomhb772 with sanitized name 'discussions' in all snapshot file contents and filenames. * docs(discussions): add S3 avatars bucket to migration config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(discussions): update snapshots with S3 avatars + bookmarks DDB Re-deployed discussions app with 3 storage types (activity DDB, bookmarks DDB, avatars S3) and captured fresh snapshots for all 4 stages. Per-table stacks refactor verified working for both DDB tables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(cli): disambiguate S3 nested stack from DDB per-table stacks The S3 forward/rollback refactorers used findNestedStack with a bare 'storage' prefix, which matched DDB per-table stacks (e.g. 'storageactivity') before the S3 stack ('storage0EC3F24A'). Fixed by using 'storage' + resourceName for Gen1 lookup and a new findS3NestedStack method that distinguishes the CDK-hashed S3 stack from lowercase-prefixed DDB stacks for Gen2 lookup. Also refreshes discussions snapshots from a clean main/gen2-main deploy. * fix(cli): address PR review comments for per-table stacks - Replace fragile char-based findS3NestedStack with template-based S3 detection (fetch template, check for AWS::S3::Bucket resource) - Export findS3NestedStack from storage-forward.ts, import in rollback - Remove dead STORAGE_RESOURCE_TYPES export - Remove storage:S3 from validateSingleResourcePerStack (only one defineStorage() possible) - Make scopeVarName required (no default) in DynamoDBRenderer - Set Outputs default empty object in cfn-output-resolver, use templateOutputs consistently - Add comments for optional Outputs in cfn-template.ts and resolver * fix(cli): restore storage:S3 case in validateSingleResourcePerStack Keep S3 validation as a defensive spot check even though the collision can't happen in practice (JSON key uniqueness prevents same-name resources within a category). * feat(cli): add S3 avatars and bookmarks DDB tests to discussions test script Extends the discussions gen1 test script to cover all stateful resources that get refactored during migration: S3 avatars bucket (upload/getUrl/remove) and bookmarks DynamoDB table (put/get/delete via AWS SDK). Verified with full forward→rollback→forward round-trip on live app. * refactor(cli): clean up bookmarks test - shared DDB client, config-based table lookup Remove ListTablesCommand discovery in favor of reading table name from amplifyconfiguration.json. Share a single DynamoDBDocumentClient across all bookmark test functions instead of creating one per call. * chore(cli): remove dev-only .gitignore from amplify-cli package Remove .gitignore that ignored local dev helper scripts (add-bookmarks.ts, deploy-gen1.ts) that no longer exist. * fix(cli): add typescript devDep to fix typecheck for bundler moduleResolution backend-only and discussions were missing typescript as a devDependency, causing npx tsc to fall back to the root tsc v4.9.5 which doesn't support moduleResolution:"bundler". Also restores monorepo metadata in discussions snapshot package.json files. * fix: use README-specified emailVerificationMessage in discussions snapshots Replace "Your verification code is {####}" with "Here is your verification code {####}" to match the discussions README instructions, preventing drift when snapshots are regenerated. * fix: replace remaining old verification message in discussions snapshots * fix: align gen2 branch verification messages with README * fix: revert SMS verification messages to original values Only email verification fields should use the README-specified message. SMS fields should retain their original values. * fix: remove trailing newlines from parameters files * fix: remove unnecessary typescript devDependency from migration apps --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c0e2c09 commit 599b95d

File tree

260 files changed

+13645
-2929
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

260 files changed

+13645
-2929
lines changed

amplify-migration-apps/discussions/README.md

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![](./images/app.png)
44

5-
A discussion application built featuring authentication, GraphQL API, Lambda functions, and DynamoDB storage.
5+
A discussion application built featuring authentication, GraphQL API, Lambda functions, multiple DynamoDB storage tables (activity + bookmarks), and S3.
66

77
> [!NOTICE]
88
> Since amplify operations add files to your local directory, its better not to operate within this repo.
@@ -116,8 +116,9 @@ amplify add api
116116

117117
### Storage
118118

119-
DynamoDB table for storing user activity logs with partition key, sort key,
120-
and global secondary index for querying by activity type.
119+
Two DynamoDB tables: `activity` for storing user activity logs, and `bookmarks` for storing user bookmarks on posts.
120+
121+
#### Activity Table
121122

122123
```console
123124
amplify add storage
@@ -169,6 +170,59 @@ https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.Core
169170
✔ Do you want to add a Lambda Trigger for your Table? (y/N) · no
170171
```
171172

173+
#### Bookmarks Table
174+
175+
```console
176+
amplify add storage
177+
```
178+
179+
```console
180+
? Select from one of the below mentioned services: NoSQL Database
181+
182+
Welcome to the NoSQL DynamoDB database wizard
183+
This wizard asks you a series of questions to help determine how to set up your NoSQL database table.
184+
185+
✔ Provide a friendly name · bookmarks
186+
✔ Provide table name · bookmarks
187+
188+
You can now add columns to the table.
189+
190+
✔ What would you like to name this column · userId
191+
✔ Choose the data type · string
192+
✔ Would you like to add another column? (Y/n) · yes
193+
✔ What would you like to name this column · postId
194+
✔ Choose the data type · string
195+
✔ Would you like to add another column? (Y/n) · no
196+
197+
✔ Choose partition key for the table · userId
198+
✔ Do you want to add a sort key to your table? (Y/n) · yes
199+
✔ Choose sort key for the table · postId
200+
201+
✔ Do you want to add global secondary indexes to your table? (Y/n) · yes
202+
✔ Provide the GSI name · byPost
203+
✔ Choose partition key for the GSI · postId
204+
✔ Do you want to add a sort key to your global secondary index? (Y/n) · no
205+
✔ Do you want to add more global secondary indexes to your table? (Y/n) · no
206+
✔ Do you want to add a Lambda Trigger for your Table? (y/N) · no
207+
```
208+
209+
### Storage (S3 Avatars)
210+
211+
S3 bucket for storing user profile pictures.
212+
213+
```console
214+
amplify add storage
215+
```
216+
217+
```console
218+
? Select from one of the below mentioned services: Content (Images, audio, video, etc.)
219+
✔ Provide a friendly name for your resource that will be used to label this category in the project: · avatars
220+
✔ Provide bucket name: · discus-avatars
221+
✔ Who should have access: · Auth users only
222+
✔ What kind of access do you want for Authenticated users? · create/update, read, delete
223+
✔ Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) · no
224+
```
225+
172226
### Function
173227

174228
**Node.js Lambda function that retrieves user activity from DynamoDB storage.**
@@ -280,6 +334,10 @@ amplify push
280334
├──────────┼─────────────────────────────┼───────────┼───────────────────┤
281335
│ Storage │ activity │ Create │ awscloudformation │
282336
├──────────┼─────────────────────────────┼───────────┼───────────────────┤
337+
│ Storage │ bookmarks │ Create │ awscloudformation │
338+
├──────────┼─────────────────────────────┼───────────┼───────────────────┤
339+
│ Storage │ avatars │ Create │ awscloudformation │
340+
├──────────┼─────────────────────────────┼───────────┼───────────────────┤
283341
│ Function │ fetchuseractivity │ Create │ awscloudformation │
284342
├──────────┼─────────────────────────────┼───────────┼───────────────────┤
285343
│ Function │ recorduseractivity │ Create │ awscloudformation │
@@ -332,7 +390,7 @@ this process for any number of users.
332390

333391
> Based on https://github.com/aws-amplify/amplify-cli/blob/gen2-migration/GEN2_MIGRATION_GUIDE.md
334392
335-
First and install the experimental CLI package the provides the new commands:
393+
First install the experimental amplify CLI package that provides the migration commands.
336394

337395
```console
338396
npm install --no-save @aws-amplify/cli-internal-gen2-migration-experimental-alpha
@@ -419,4 +477,3 @@ git push origin gen2-main
419477
```
420478

421479
Wait for the deployment to finish successfully.
422-

amplify-migration-apps/discussions/_snapshot.post.generate/amplify/backend.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
import { auth } from './auth/resource';
22
import { data } from './data/resource';
3+
import { storage } from './storage/resource';
34
import { fetchuseractivity } from './storage/fetchuseractivity/resource';
45
import { recorduseractivity } from './storage/recorduseractivity/resource';
6+
import { DynamoEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
7+
import { StartingPosition } from 'aws-cdk-lib/aws-lambda';
58
import {
69
Table,
710
AttributeType,
811
BillingMode,
912
StreamViewType,
1013
} from 'aws-cdk-lib/aws-dynamodb';
11-
import { DynamoEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
12-
import { StartingPosition } from 'aws-cdk-lib/aws-lambda';
1314
import { defineBackend } from '@aws-amplify/backend';
1415
import { Duration } from 'aws-cdk-lib';
1516

1617
const backend = defineBackend({
1718
auth,
1819
data,
20+
storage,
1921
fetchuseractivity,
2022
recorduseractivity,
2123
});
22-
const storageStack = backend.createStack('storage');
23-
const activity = new Table(storageStack, 'activity', {
24+
const storageActivityStack = backend.createStack('storageactivity');
25+
const activity = new Table(storageActivityStack, 'activity', {
2426
partitionKey: { name: 'id', type: AttributeType.STRING },
2527
billingMode: BillingMode.PROVISIONED,
2628
readCapacity: 5,
@@ -36,6 +38,22 @@ activity.addGlobalSecondaryIndex({
3638
readCapacity: 5,
3739
writeCapacity: 5,
3840
});
41+
const storageBookmarksStack = backend.createStack('storagebookmarks');
42+
const bookmarks = new Table(storageBookmarksStack, 'bookmarks', {
43+
partitionKey: { name: 'userId', type: AttributeType.STRING },
44+
billingMode: BillingMode.PROVISIONED,
45+
readCapacity: 5,
46+
writeCapacity: 5,
47+
stream: StreamViewType.NEW_IMAGE,
48+
sortKey: { name: 'postId', type: AttributeType.STRING },
49+
});
50+
// Add this property to the Table above post refactor: tableName: 'bookmarks-main'
51+
bookmarks.addGlobalSecondaryIndex({
52+
indexName: 'byPost',
53+
partitionKey: { name: 'postId', type: AttributeType.STRING },
54+
readCapacity: 5,
55+
writeCapacity: 5,
56+
});
3957
const cfnUserPool = backend.auth.resources.cfnResources.cfnUserPool;
4058
cfnUserPool.usernameAttributes = ['phone_number'];
4159
cfnUserPool.policies = {
@@ -125,3 +143,16 @@ for (const model of ['Topic', 'Post', 'Comment']) {
125143
backend.recorduseractivity.resources.lambda.role!
126144
);
127145
}
146+
const s3Bucket = backend.storage.resources.cfnResources.cfnBucket;
147+
// Use this bucket name post refactor
148+
// s3Bucket.bucketName = 'discus-avatarsc39a5-main';
149+
s3Bucket.bucketEncryption = {
150+
serverSideEncryptionConfiguration: [
151+
{
152+
serverSideEncryptionByDefault: {
153+
sseAlgorithm: 'AES256',
154+
},
155+
bucketKeyEnabled: false,
156+
},
157+
],
158+
};

amplify-migration-apps/discussions/_snapshot.post.generate/amplify/data/resource.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ export const data = defineData({
4141
//The "branchname" variable needs to be the same as your deployment branch if you want to reuse your Gen1 app tables
4242
branchName: 'main',
4343
modelNameToTableNameMapping: {
44-
Topic: 'Topic-ruiylk7rjnb4ziygno3jh4wrsq-main',
45-
Post: 'Post-ruiylk7rjnb4ziygno3jh4wrsq-main',
46-
Comment: 'Comment-ruiylk7rjnb4ziygno3jh4wrsq-main',
44+
Topic: 'Topic-u3jn2qbupzbyhc3h53673wdvim-main',
45+
Post: 'Post-u3jn2qbupzbyhc3h53673wdvim-main',
46+
Comment: 'Comment-u3jn2qbupzbyhc3h53673wdvim-main',
4747
},
4848
},
4949
],
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { defineStorage } from '@aws-amplify/backend';
2+
3+
const branchName = process.env.AWS_BRANCH ?? 'sandbox';
4+
5+
export const storage = defineStorage({
6+
name: `discus-avatarsc39a5-${branchName}`,
7+
access: (allow) => ({
8+
'public/*': [allow.authenticated.to(['write', 'read', 'delete'])],
9+
'protected/{entity_id}/*': [
10+
allow.authenticated.to(['write', 'read', 'delete']),
11+
],
12+
'private/{entity_id}/*': [
13+
allow.authenticated.to(['write', 'read', 'delete']),
14+
],
15+
}),
16+
});

amplify-migration-apps/discussions/_snapshot.post.refactor/refactor.__from__.amplify-discussions-gen2main-branch-96dfd1dc44-auth179371D7-DAPL7YOMHRB4.__to__.amplify-discussions-gen2main-branch-96dfd1dc44-auth179371D7-DAPL7YOMHRB4-holding.mappings.json

Lines changed: 0 additions & 52 deletions
This file was deleted.

amplify-migration-apps/discussions/_snapshot.post.refactor/refactor.__from__.amplify-discussions-gen2main-branch-96dfd1dc44-storage0EC3F24A-QQN18S0SITDH.__to__.amplify-discussions-gen2main-branch-96dfd1dc44-storage0EC3F24A-QQN18S0SITDH-holding.mappings.json

Lines changed: 0 additions & 12 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[
2+
{
3+
"Source": {
4+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX",
5+
"LogicalResourceId": "amplifyAuthUserPool4BA7F805"
6+
},
7+
"Destination": {
8+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX-holding",
9+
"LogicalResourceId": "amplifyAuthUserPool4BA7F805"
10+
}
11+
},
12+
{
13+
"Source": {
14+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX",
15+
"LogicalResourceId": "amplifyAuthUserPoolNativeAppClient79534448"
16+
},
17+
"Destination": {
18+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX-holding",
19+
"LogicalResourceId": "amplifyAuthUserPoolNativeAppClient79534448"
20+
}
21+
},
22+
{
23+
"Source": {
24+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX",
25+
"LogicalResourceId": "amplifyAuthUserPoolAppClient2626C6F8"
26+
},
27+
"Destination": {
28+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX-holding",
29+
"LogicalResourceId": "amplifyAuthUserPoolAppClient2626C6F8"
30+
}
31+
},
32+
{
33+
"Source": {
34+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX",
35+
"LogicalResourceId": "amplifyAuthIdentityPool3FDE84CC"
36+
},
37+
"Destination": {
38+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX-holding",
39+
"LogicalResourceId": "amplifyAuthIdentityPool3FDE84CC"
40+
}
41+
},
42+
{
43+
"Source": {
44+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX",
45+
"LogicalResourceId": "amplifyAuthIdentityPoolRoleAttachment045F17C8"
46+
},
47+
"Destination": {
48+
"StackName": "amplify-discussions-gen2main-branch-a27e51c30a-auth179371D7-6NIMXK30VQKX-holding",
49+
"LogicalResourceId": "amplifyAuthIdentityPoolRoleAttachment045F17C8"
50+
}
51+
}
52+
]

0 commit comments

Comments
 (0)