Flutter から Lambda を経由し Amazon Pinpoint で Push 通知を実装 ②

# はじめに
こちらの続きになります。

https://qiita.com/hirai-11/items/4acb61bede53805c1527


以下を行っていきます。

- Pinpointに対してセグメントとキャンペーンを更新するLambdaを作成

- Pinpointに対してキャンペーンを削除するLambdaを作成

- Lambda_Layerを利用し、メソッドをモジュール化（共通化）する
    - 作成、更新、削除をモジュール化します

- フィルタリング内容が同じ場合、セグメントを再利用するLambdaを作成


# 5. Pinpointに対してセグメントとキャンペーンを更新する
Pinpointに対してセグメントとキャンペーンを更新するLambdaを作成します。

![スクリーンショット 2022-06-19 11.51.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/5193d0f9-cae3-bc26-d4dd-4eda30b54678.png)


## 起動済のキャンペーンは更新できません。

更新できるキャンペーンは、キャンペーンが実行前のものに限ります。
実行前かどうかは、コンソール上で`キャンペーンのステータス`から確認できます。


- ↓ステータスが`完了`していないため、更新可能
![スクリーンショット 2022-06-19 12.01.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/7633211e-50e7-2331-1ad0-c7449c9a9e3b.png)
- ↓ステータスが`完了`のため、キャンペーンの更新不可
![スクリーンショット 2022-06-19 12.02.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/65d4ead2-f463-0001-1f8f-b138104ddfea.png)

**更新するキャンペーンIDはステータスが完了でないIDを使ってください。**

## Lambdaが受け取るevent内容
こちらになります。
```json
{
  "item": [
    "bags",
    "outerwear"
  ],
  "gender": "woman",
  "startTime":"2023-01-01T00:00:00+09:00"
}
```
![スクリーンショット 2022-06-19 19.32.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/13b2ce58-a61c-17d6-3509-edf62e19dffa.png)



## セグメントとキャンペーンを更新するLambdaを作成


更新する箇所以下の点です。

- セグメントのItem属性の`pants`から`outerwear`に変更
- キャンペーンのpush通知の日時を`2023年1月1日`に変更

Lambdaで必要な情報は、キャンペーンIDです。コンソールからでも確認できます。
```diff_javascript
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;

const campaign_id = '0c75d2bcf6284280822eac0892537b73';

const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });

function write_campaign_data(
  messages_per_second,
  ttl,
  push_title,
  campaign_title,
  push_body,
  schedule,
  segment_id
) {
  const APNS_row_content = {
    aps: {
      alert: {
        title: push_title,
        body: push_body,
      },
      sound: 'default',
      content_available: true,
    },
  };
  const GCM_row_content = {
    data: {
      title: push_title,
      body: push_body,
      jsonBody: {
        type: campaign_title,
      },
    },
  };
  return {
    AdditionalTreatments: [],
    Description: '',
    HoldoutPercent: 0,
    IsPaused: false,
    Limits: {
      Daily: 0,
      MaximumDuration: 10800,
      MessagesPerSecond: messages_per_second,
      Total: 0,
    },
    MessageConfiguration: {
      APNSMessage: {
        Action: 'OPEN_APP',
        TimeToLive: ttl,
        RawContent: JSON.stringify(APNS_row_content),
      },
      GCMMessage: {
        Action: 'OPEN_APP',
        TimeToLive: ttl,
        RawContent: JSON.stringify(GCM_row_content),
      },
    },
    Name: campaign_title,
    Schedule: {
      Frequency: 'ONCE',
      StartTime: schedule,
      Timezone: 'UTC+09',
    },
    SegmentId: segment_id,
    SegmentVersion: 1,
  };
}

exports.handler = async (event) => {
  console.log('event:', JSON.stringify(event, null, 2));

  const campaign_data = write_campaign_data(
    100, // int:MessagesPerSecond
    3600, // int:TimeToLive
    'test_title', // str:push_title
    'test_campaign', // str:campaign_title
    'test_body', // str:push_body
    event['startTime'] // str:schedule
  );
  let campaign_response;
  try {
    const params_campaign = {
      ApplicationId: application_id,
      WriteCampaignRequest: campaign_data,
      CampaignId: campaign_id,
    };
    campaign_response = await pinpoint.updateCampaign(params_campaign).promise();
    console.log('campaign_response:', JSON.stringify(campaign_response, null, 2));
  } catch (err) {
    console.error(err);
  }
  const segment_id = campaign_response['CampaignResponse']['SegmentId'];

  const segment_data = {
    Name: 'favorite_segment',
    Dimensions: {
      UserAttributes: {
        Item: {
          AttributeType: 'INCLUSIVE',
          Values: event['item'],
        },
        Gender: {
          AttributeType: 'INCLUSIVE',
          Values: event['gender'].split(','),
        },
      },
    },
  };

  try {
    const params_segment = {
      ApplicationId: application_id,
      WriteSegmentRequest: segment_data,
      SegmentId: segment_id,
    };
    const segment_response = await pinpoint.updateSegment(params_segment).promise();
    console.log('segment_response:', JSON.stringify(segment_response, null, 2));
  } catch (err) {
    console.error(err);
  }
};
```

前回、セグメントとキャンペーンを作成するLambdaのコードからそこまで変わっていないですね。

https://qiita.com/hirai-11/items/4acb61bede53805c1527#%E3%82%BB%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%82%AD%E3%83%A3%E3%83%B3%E3%83%9A%E3%83%BC%E3%83%B3%E3%82%92%E7%99%BB%E9%8C%B2%E3%81%99%E3%82%8Blambda%E3%82%92%E4%BD%9C%E6%88%90



実行すると、セグメントの内容、キャンペーンの内容が更新されました。

![スクリーンショット 2022-06-19 12.13.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/eff65f54-1dbe-a610-6b42-8ba4752e44c3.png)
![スクリーンショット 2022-06-19 12.14.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b1b2554e-8829-3c38-f62a-c56851ec8d8e.png)



# 6.Pinpointに対してセグメントとキャンペーンを削除する

![スクリーンショット 2022-06-19 12.17.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/f3e39747-8467-4476-6958-dcb1ebc00500.png)

## セグメントとキャンペーンを削除するLambdaを作成

削除したいキャンペーンIDがあれば、キャンペーンとセグメントどちらも削除できます。

```diff_javascript
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;
const campaign_id = '0c75d2bcf6284280822eac0892537b73';

const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });

exports.handler = async (event) => {
  console.log('event:', JSON.stringify(event, null, 2));

  let campaign_response;
  try {
    const params_campaign = {
      ApplicationId: application_id,
      CampaignId: campaign_id,
    };
    const campaign_response = await pinpoint.deleteCampaign(params_campaign).promise();
    console.log('campaign_response:', JSON.stringify(campaign_response, null, 2));
  } catch (err) {
    console.error(err);
  }
  
  const segment_id = segment_response['CampaignResponse']['SegmentId'];
  
  try {
    const params_segment = {
      ApplicationId: application_id,
      SegmentId: segment_id,
    };
    await pinpoint.deleteSegment(params_segment).promise();
  } catch (err) {
    console.error(err);
  }
};
```

# Lambda_Layerを利用し、メソッドをモジュール化（共通化）する
下記、4つのモジュールを作成し、3つのLambdaを作成します。

- セグメントとキャンペーンを作成するモジュール
- キャンペーンデータのモジュール
- セグメントとキャンペーンを削除するモジュール
- セグメントとキャンペーンを更新するモジュール

1. セグメントとキャンペーンを作成するLambdaを作成
1. セグメントとキャンペーンを削除するLambdaを作成
1. セグメントとキャンペーンを更新するLambdaを作成




Lambda_Layerの利用方法はこちらの記事を参考にしてください。

https://qiita.com/hirai-11/items/58a968cc940051bd2fbd

### セグメントとキャンペーンを作成するモジュール
ファイル名を`create_action_pinpoint.js`とします

```js:create_action_pinpoint.js
module.exports = async (action, write_request, application_id) => {
  const AWS = require('aws-sdk');
  const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });

  try {
    // create_campaign
    if (action == 'campaign') {
      const params_campaign = {
        ApplicationId: application_id,
        WriteCampaignRequest: write_request,
      };
      const campaign_response = await pinpoint.createCampaign(params_campaign).promise();
      console.log('campaign_response:', JSON.stringify(campaign_response, null, 2));

      return campaign_response;
    }
    // create_segment
    const params_segment = {
      ApplicationId: application_id,
      WriteSegmentRequest: write_request,
    };
    const segment_response = await pinpoint.createSegment(params_segment).promise();
    console.log('segment_response:', JSON.stringify(segment_response, null, 2));

    return segment_response;
  } catch (err) {
    console.error(err);
  }
};
```

### キャンペーンデータのモジュール
ファイル名を`write_campaign_data.js`とします
```js:write_campaign_data.js
module.exports = (
  messages_per_second,
  ttl,
  push_title,
  campaign_title,
  push_body,
  schedule,
  segment_id
) => {

  const APNS_row_content = {
    aps: {
      alert: {
        title: push_title,
        body: push_body,
      },
      sound: 'default',
      content_available: true,
    },
  };
  const GCM_row_content = {
    data: {
      title: push_title,
      body: push_body,
      jsonBody: {
        type: campaign_title,
      },
    },
  };
  return {
    AdditionalTreatments: [],
    Description: '',
    HoldoutPercent: 0,
    IsPaused: false,
    Limits: {
      Daily: 0,
      MaximumDuration: 10800,
      MessagesPerSecond: messages_per_second,
      Total: 0,
    },
    MessageConfiguration: {
      APNSMessage: {
        Action: 'OPEN_APP',
        TimeToLive: ttl,
        RawContent: JSON.stringify(APNS_row_content),
      },
      GCMMessage: {
        Action: 'OPEN_APP',
        TimeToLive: ttl,
        RawContent: JSON.stringify(GCM_row_content),
      },
    },
    Name: campaign_title,
    Schedule: {
      Frequency: 'ONCE',
      StartTime: schedule,
      Timezone: 'UTC+09',
    },
    SegmentId: segment_id,
    SegmentVersion: 1,
  };
};
```

## 1. セグメントとキャンペーンを作成するLambdaを作成
上記の２つのモジュールを利用して、セグメントとキャンペーンを作成するLambdaを作成しましょう。

下記のようにローカルでディレクトリとファイルを作成し、`nodejs`をzip化して、LambdaLayerにアップします。
```terminal
self_modules % tree
.
└── nodejs
   └── node_modules
       ├── create_action_pinpoint.js
       └── write_campaign_data.js
```

LambdaLayerにアップします。
![スクリーンショット 2022-06-19 17.54.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/cecadb8b-70c4-c8b4-2fb5-439c2a74e7e8.png)
Lambdaのコンソールからレイヤーを追加します。

![スクリーンショット 2022-06-19 17.57.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/a9a85fac-189b-22fb-4177-cedb423688f3.png)

Lambdaのコードは以下の通りになります。
```js
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;

const create_ = require('create_action_pinpoint');
const write_campaign_data = require('write_campaign_data');

exports.handler = async (event) => {
  console.log('event:', JSON.stringify(event, null, 2));

  const segment_data = {
    Name: 'favorite_segment',
    Dimensions: {
      UserAttributes: {
        Item: {
          AttributeType: 'INCLUSIVE',
          Values: event['item'],
        },
        Gender: {
          AttributeType: 'INCLUSIVE',
          Values: event['gender'].split(','),
        },
      },
    },
  };

  const pinpoint_response = await create_('segment', segment_data, application_id);
  const response_segment_id = pinpoint_response['SegmentResponse']['Id'];
  
  const campaign_data = write_campaign_data(
    100, // int:MessagesPerSecond
    3600, // int:TimeToLive
    'test_title', // str:push_title
    'test_campaign', // str:campaign_title
    'test_body', // str:push_body
    'IMMEDIATE', // str:schedule
    response_segment_id // str:segment_id
  );

  await create_('campaign', campaign_data, application_id);
};
```
実行すると、セグメントとキャンペーンが作成されました。

LambdaLayerにして、モジュール化することで複数のLambdaで使用でき、コード量を減らすことができます。

以下のようにして、モジュールを呼びます。
`const create_ = require('create_action_pinpoint');`
`const write_campaign_data = require('write_campaign_data');`

### セグメントとキャンペーンを削除するモジュール
次は、セグメントとキャンペーンを削除するモジュールを作成します。
ファイル名を`delete_action_pinpoint.js`とします
```js:delete_action_pinpoint.js
module.exports = async (action, application_id, id) => {
  const AWS = require('aws-sdk');
  const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });

  try {
    if (action == 'campaign') {
      const params_campaign = {
        ApplicationId: application_id,
        CampaignId: id,
      };
      const campaign_response = await pinpoint.deleteCampaign(params_campaign).promise();
      console.log('campaign_response:', JSON.stringify(campaign_response, null, 2));

      return campaign_response;
    }
    const params_segment = {
      ApplicationId: application_id,
      SegmentId: id,
    };
    const segment_response = await pinpoint.deleteSegment(params_segment).promise();
    console.log('segment_response:', JSON.stringify(segment_response, null, 2));

    return segment_response;
  } catch (err) {
    console.error(err);
  }
};
```
## 2. セグメントとキャンペーンを削除するLambdaを作成
ローカルの`node_modules`内に`delete_action_pinpoint.js`を加えて、再度nodejsをzip化して、LambdaLayerにアップしましょう。
```terminal
self_modules % tree
.
└── nodejs
   └── node_modules
       ├── create_action_pinpoint.js
       ├── delete_action_pinpoint.js
       └── write_campaign_data.js
```

```js
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;
const campaign_id = "0c75d2bcf6284280822eac0892537b73";

const delete_ = require('delete_action_pinpoint');

exports.handler = async (event) => {
  console.log('event:', JSON.stringify(event, null, 2));

  const pinpoint_response = await delete_('campaign', application_id, campaign_id);
  
  const segment_id = pinpoint_response['CampaignResponse']['SegmentId'];
  
  await delete_('segment', application_id, segment_id);
};
```

### セグメントとキャンペーンを更新するモジュール
次は、セグメントとキャンペーンを削除するモジュールを作成します。
ファイル名をupdate_action_pinpoint.jsとします
```js:update_action_pinpoint.js
module.exports = async (action, write_request, application_id, id) => {
  const AWS = require('aws-sdk');
  const pinpoint = new AWS.Pinpoint({ region: process.env.AWS_REGION });

  try {
    if (action == 'campaign') {
      const params_campaign = {
        ApplicationId: application_id,
        WriteCampaignRequest: write_request,
        CampaignId: id,
      };
      const campaign_response = await pinpoint.updateCampaign(params_campaign).promise();
      console.log('campaign_response:', JSON.stringify(campaign_response, null, 2));

      return campaign_response;
    }
    const params_segment = {
      ApplicationId: application_id,
      WriteSegmentRequest: write_request,
      SegmentId: id,
    };
    const segment_response = await pinpoint.updateSegment(params_segment).promise();
    console.log('segment_response:', JSON.stringify(segment_response, null, 2));

    return segment_response;
  } catch (err) {
    console.error(err);
  }
};
```

## 3. セグメントとキャンペーンを更新するLambdaを作成
ローカルの`node_modules`内に`update_action_pinpoint.js`を加えて、再度nodejsをzip化して、LambdaLayerにアップしましょう。
```terminal
self_modules % tree
.
└── nodejs
   └── node_modules
       ├── create_action_pinpoint.js
       ├── delete_action_pinpoint.js
       ├── update_action_pinpoint.js
       └── write_campaign_data.js
```

セグメントとキャンペーンを更新するLambdaのコードは以下の通りです。
```js
const application_id = process.env.AWS_PINPOINT_PROJECT_ID;

const campaign_id = '0c75d2bcf6284280822eac0892537b73';

const update_ = require('update_action_pinpoint');
const write_campaign_data = require('write_campaign_data');

exports.handler = async (event) => {
  console.log('event:', JSON.stringify(event, null, 2));

  const campaign_data = write_campaign_data(
    100, // int:MessagesPerSecond
    3600, // int:TimeToLive
    'test_title', // str:push_title
    'test_campaign', // str:campaign_title
    'test_body', // str:push_body
    event['startTime'] // str:schedule
  );
  const campaign_response = await update_('campaign', campaign_data, application_id, campaign_id);

  const segment_id = campaign_response['CampaignResponse']['SegmentId'];

  const segment_data = {
    Name: 'favorite_segment',
    Dimensions: {
      UserAttributes: {
        Item: {
          AttributeType: 'INCLUSIVE',
          Values: event['item'],
        },
        Gender: {
          AttributeType: 'INCLUSIVE',
          Values: event['gender'].split(','),
        },
      },
    },
  };
  await update_('segment', segment_data, application_id, segment_id);
};
```

# フィルタリング内容が同じ場合、セグメントを再利用する

今のままですと、毎回セグメントを作成します。
フィルタリング(条件)の内容が同じセグメントが、すでにpinpointで作成済みの場合、新たなセグメントを作成せず、作成済みのセグメントを利用するLambdaを作成します。

こちらは、今後更新していきます。

# 参考

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Pinpoint.html