Skip to content

Instantly share code, notes, and snippets.

@aws-amplify-ops
Last active October 28, 2024 23:26
Show Gist options
  • Select an option

  • Save aws-amplify-ops/27954c421bd72930874d48c15c284807 to your computer and use it in GitHub Desktop.

Select an option

Save aws-amplify-ops/27954c421bd72930874d48c15c284807 to your computer and use it in GitHub Desktop.

Revisions

  1. aws-amplify-ops revised this gist Jan 27, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion cleanup-steps.md
    Original file line number Diff line number Diff line change
    @@ -240,7 +240,7 @@ const THIRTY_DAYS_IN_SECONDS = 30 * 24 * 60 * 60;

    exports.handler = async (event) => {
    const ownerField = 'owner'; // owner is default value but if you specified ownerField on auth rule, that must be specified here
    const identityClaim = 'cognito:username'; // cognito:username is default value but if you specified identityField on auth rule, that must be specified here
    const identityClaim = 'username'; // username is default value but if you specified identityField on auth rule, that must be specified here

    var condition = {
    [ownerField]: {
  2. aws-amplify-ops revised this gist Jan 27, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion cleanup-steps.md
    Original file line number Diff line number Diff line change
    @@ -68,7 +68,7 @@ const tableName = process.env.<USE TABLE NAME FROM LIST>;

    exports.handler = async (event) => {
    const ownerField = 'owner'; // owner is default value but if you specified ownerField on auth rule, that must be specified here
    const identityClaim = 'cognito:username'; // cognito:username is default value but if you specified identityField on auth rule, that must be specified here
    const identityClaim = 'username'; // username is default value but if you specified identityField on auth rule, that must be specified here
    var condition = {}
    condition[ownerField] = {
    ComparisonOperator: 'EQ'
  3. aws-amplify-ops revised this gist Jan 27, 2022. 1 changed file with 9 additions and 4 deletions.
    13 changes: 9 additions & 4 deletions cleanup-steps.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    ## Who is this Gist for?

    This Gist demonstrates an approach to removing a specific user's data from an Amazon DynamoDB table that was created as an AWS AppSync data source. It may be helpful for those who need to remove records that are associated with a Cognito User that will be deleted from their Cognito User Pool.

    ## Steps to clean up user data

    ### Clean up user data with DataStore disabled (conflict resolution disabled)
    @@ -134,8 +138,9 @@ type Mutation {
    ```

    **4. Run amplify push**
    **5. Invoke deleteUserData mutation from client before deleting the user**
    The returned value must be true to confirm is done. (Retry until is true in case you have a large number of records)

    **5. Invoke deleteUserData mutation from client**
    Since this operation requires authorization from the owner of the to-be removed records, it is important that the `deleteUserData` mutation be invoked **BEFORE** the user is deleted. The returned value must be true to confirm that records have been properly modified. (Retry until the return value is true in case you have a large number of records.)

    ### Delete owner from array ownership from v2 [Graphql Transformer example](https://docs.amplify.aws/cli-legacy/graphql-transformer/auth/#multiple-authorization-rules)

    @@ -221,8 +226,8 @@ var condition = {

    ## Clean up user data with DataStore enabled (conflict resolution enabled)

    When DataStore is enabled the suggested way would be instead of delete should be mark the record as deleted.
    Assuming you have the same annotaed schema, the steps are the same as above but the code should update the data in order to be compatible with DataStore.
    When DataStore is enabled, you should not permanently delete the user's records. Instead, you can mark the record as deleted in order to continue supporting conflict resolution.
    Assuming you have the same annotated schema, the steps are the same as above.

    ```javascript
    const AWS = require('aws-sdk');
  4. aws-amplify-ops created this gist Jan 26, 2022.
    309 changes: 309 additions & 0 deletions cleanup-steps.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,309 @@
    ## Steps to clean up user data

    ### Clean up user data with DataStore disabled (conflict resolution disabled)
    Assuming you have this schema

    ```graphql
    type Todo @model @auth(rules: [{ allow: owner }]){
    id: ID!
    name: String!
    description: String
    }
    ```

    **`1. amplify add function`**

    ```bash
    $ amplify add function
    ? Select which capability you want to add: Lambda function (s
    erverless function)
    ? Provide an AWS Lambda function name: deleteUserData
    ? Choose the runtime that you want to use: NodeJS
    ? Choose the function template that you want to use: Hello Wo
    rld

    Available advanced settings:
    - Resource access permissions
    - Scheduled recurring invocation
    - Lambda layers configuration
    - Environment variables configuration
    - Secret values configuration

    ? Do you want to configure advanced settings? Yes
    ? Do you want to access other resources in this project from
    your Lambda function? Yes
    ? Select the categories you want this function to have access
    to. storage
    ? Select the operations you want to permit on Todo:@model(app
    sync) create, read, update, delete

    You can access the following resource attributes as environment variables from your Lambda function
    API_TEST_GRAPHQLAPIIDOUTPUT
    API_TEST_TODOTABLE_ARN
    API_TEST_TODOTABLE_NAME
    ENV
    REGION
    ? Do you want to invoke this function on a recurring schedule
    ? No
    ? Do you want to enable Lambda layers for this function? No
    ? Do you want to configure environment variables for this fun
    ction? No
    ? Do you want to configure secret values this function can ac
    cess? No
    ? Do you want to edit the local lambda function now? Yes
    ```

    **2. Update the code of the function**

    ```javascript
    const AWS = require('aws-sdk');
    AWS.config.update({ region: process.env.REGION });

    const dynamodb = new AWS.DynamoDB.DocumentClient();
    const tableName = process.env.<USE TABLE NAME FROM LIST>;

    exports.handler = async (event) => {
    const ownerField = 'owner'; // owner is default value but if you specified ownerField on auth rule, that must be specified here
    const identityClaim = 'cognito:username'; // cognito:username is default value but if you specified identityField on auth rule, that must be specified here
    var condition = {}
    condition[ownerField] = {
    ComparisonOperator: 'EQ'
    }

    condition[ownerField]['AttributeValueList'] = [event.identity.claims[identityClaim]];

    await new Promise(async (res) => {
    let LastEvaluatedKey;

    do {
    let scanParams = {
    TableName: tableName,
    ScanFilter: condition,
    AttributesToGet: ['id', ownerField],
    ExclusiveStartKey: LastEvaluatedKey
    }

    const items = await new Promise(resolve => {
    dynamodb.scan(scanParams, (err, data) => {
    if (err) {
    console.log({ error: 'Could not load items: ' + err });
    resolve([]);
    } else {
    LastEvaluatedKey = data.LastEvaluatedKey;
    resolve(data.Items);
    }
    });
    })

    if (items.length > 0) {
    console.log(`records to be deleted: ${items.length}`);
    let deleteParams = {
    RequestItems: {
    [tableName]: items.map(item => {
    return {
    DeleteRequest: {
    Key: { id: item.id }
    }
    }
    })
    }
    }

    await new Promise(resolve => {
    dynamodb.batchWrite(deleteParams, (err, data) => {
    resolve();
    })
    })
    }

    } while(LastEvaluatedKey)

    res();
    });

    return true; // this means the user data was cleaned up
    };
    ```

    **3. Add mutation to graphql schema**

    ```graphql
    type Mutation {
    deleteUserData: Boolean! @function(name: "deleteUserData-${env}")
    }
    ```

    **4. Run amplify push**
    **5. Invoke deleteUserData mutation from client before deleting the user**
    The returned value must be true to confirm is done. (Retry until is true in case you have a large number of records)

    ### Delete owner from array ownership from v2 [Graphql Transformer example](https://docs.amplify.aws/cli-legacy/graphql-transformer/auth/#multiple-authorization-rules)

    For removing ownership from an item, this function will remove the owner from the record. `editors` field has the owners.

    ```graphql
    type Todo @model @auth(rules: [
    { allow: owner },
    { allow: owner, ownerField: "editors", operations: [update, read] }
    ]) {
    id: ID!
    name: String!
    description: String
    editors: [String]
    }

    type Mutation {
    deleteUser: Boolean! @function(name: "deleteUserData-${env}")
    }
    ```

    ```javascript
    const groupOwnershipField = 'editors';
    var condition = {
    [groupOwnershipField]: {
    ComparisonOperator: 'CONTAINS',
    AttributeValueList: [identityValue]
    }
    };

    await new Promise(async (res) => {
    let LastEvaluatedKey;

    do {
    let queryParams = {
    TableName: tableName,
    ScanFilter: condition,
    ExclusiveStartKey: LastEvaluatedKey
    }

    const items = await new Promise(resolve => {
    dynamodbDocuClient.scan(queryParams, (err, data) => {
    if (err) {
    console.log({ error: 'Could not load items: ' + err });
    resolve([]);
    } else {
    LastEvaluatedKey = data.LastEvaluatedKey;
    resolve(data.Items);
    }
    });
    })

    const dateNow = new Date();

    if (items.length > 0) {
    let updateParams = {
    RequestItems: {
    [tableName]: items.map(item => {
    return {
    PutRequest: {
    Item: {
    ...item,
    [groupOwnershipField]: item[groupOwnershipField].filter(owner => owner !== identityValue)
    }
    }
    }
    })
    }
    }

    await new Promise(resolve => {
    dynamodbDocuClient.batchWrite(updateParams, (err, data) => {
    resolve();
    })
    })
    }

    } while(LastEvaluatedKey)

    res();
    });
    ```

    ## Clean up user data with DataStore enabled (conflict resolution enabled)

    When DataStore is enabled the suggested way would be instead of delete should be mark the record as deleted.
    Assuming you have the same annotaed schema, the steps are the same as above but the code should update the data in order to be compatible with DataStore.

    ```javascript
    const AWS = require('aws-sdk');
    AWS.config.update({ region: process.env.REGION });

    const dynamodb = new AWS.DynamoDB.DocumentClient();
    const tableName = process.env.<ADD TABLE ENV PARAM HERE>;

    const THIRTY_DAYS_IN_SECONDS = 30 * 24 * 60 * 60;

    exports.handler = async (event) => {
    const ownerField = 'owner'; // owner is default value but if you specified ownerField on auth rule, that must be specified here
    const identityClaim = 'cognito:username'; // cognito:username is default value but if you specified identityField on auth rule, that must be specified here

    var condition = {
    [ownerField]: {
    ComparisonOperator: 'EQ',
    AttributeValueList: [event.identity.claims[identityClaim]]
    },
    '_deleted': {
    ComparisonOperator: 'NE',
    AttributeValueList: [true]
    }
    };

    await new Promise(async (res) => {
    let LastEvaluatedKey;

    do {
    let queryParams = {
    TableName: tableName,
    ScanFilter: condition,
    ExclusiveStartKey: LastEvaluatedKey
    }

    const items = await new Promise(resolve => {
    dynamodb.scan(queryParams, (err, data) => {
    if (err) {
    console.log({ error: 'Could not load items: ' + err });
    resolve([]);
    } else {
    LastEvaluatedKey = data.LastEvaluatedKey;
    resolve(data.Items);
    }
    });
    })

    const dateNow = new Date();

    if (items.length > 0) {
    let deleteParams = {
    RequestItems: {
    [tableName]: items.map(item => {
    return {
    PutRequest: {
    Item: {
    ...item,
    _deleted: true,
    _ttl: dateNow.getTime()/1000 + THIRTY_DAYS_IN_SECONDS,
    _version: item._version + 1,
    _lastChangedAt: dateNow.getTime(),
    updatedAt: dateNow.toISOString(),
    }
    }
    }
    })
    }
    }

    await new Promise(resolve => {
    dynamodb.batchWrite(deleteParams, (err, data) => {
    resolve();
    })
    })
    }

    } while(LastEvaluatedKey)

    res();
    });

    return true;
    };
    ```