AWS Amplifyで既存のCognito User Pool / DynamoDBをimportする方法

# はじめに
今回、開発未経験の私がAWSのAmplifyを使いユーザーの検索を行うアプリを作りました。
そこで、既に作成されているCognitoからUserPoolをimportしてユーザー情報を取得する方法と、
既に作成されているDynamoDBから取得したいテーブルをimportして情報を取得する方法を、今後も扱うことがあると思いますので、メモとして残しておこうと思います。
また、右も左も分からない手探りな状態で進めているので、もしやっている事とその意味が違った場合は、教えていただけたら幸いです。

## 作成されているCognitoのUserPoolをimportする方法
私は、CognitoのUserPoolからユーザー情報を取得して、名前が存在した場合は表示させる機能を実践したのでそれを軸に書いていきます。

### 1: 自分以外のユーザー情報を取得するには`aws-sdk`をまずimportして使う必要がある。
### 2: aws-sdkのドキュメントで取得する方法を探す。それらしい処理を書いてみる。（この際はテスト的に書くため別ファイルを作り検証する）

```javascript
const { CognitoIdentityServiceProvider } = require("aws-sdk");
const cognito = new CognitoIdentityServiceProvider({
    region: "us-east-2",
  });

cognito.listUsers({
    UserPoolId: 'us-east-2_xxxxxxxxxx',
}).promise()
.then(user => {
    console.log(user)
}).catch(e => {
    console.log(e)
})
```
(ここで自分はAWS IAMがdefaultになっていたので使っているProfileを`awsp`で確認しておく必要がある。
...なぜか勝手に切り替わってることがあるので作業に入る前に都度確認する方が良い。)
これを`node (ファイル名)`で実行し、取得できていることが確認できる。

※このときにエラーコード`AccessDeniedException`が返ってきた場合は、AWS LambdaのIAMロールにポリシーの追加が必要なので、[`function/src/(function名)cloudformation-template.json`]の中を変更する必要があります。

### 3、次に検索の絞り込むを追加します。
公式ドキュメントを参考に絞り込む方法を探し、「Filter」で`username= \ "Reddy \"`すればいいみたいです。
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListUsers.html

今回は、前方一致でユーザー名を検索したいので、`=`を`^=`と書き変えます。

```javascript
const { CognitoIdentityServiceProvider } = require("aws-sdk");
const cognito = new CognitoIdentityServiceProvider({
    region: "us-east-2",
  });

cognito.listUsers({
    UserPoolId: 'us-east-2_xxxxxxxxxx',
    Filter: "username^='dev_abc'"
}).promise()
.then(user => {
    console.log(user)
}).catch(e => {
    console.log(e)
})
```
これにより`dev_abc`のユーザー名から始まるデータをまとめて取得できます。

### ４、次び`amplify add api`コマンドでAPIを追加します
ここでは[GraphQL API]と[REST API]を選べますが、今回は、[REST API]を選びました。（また、amplifyの一番最初の設定で間違えていたため、`? Restrict API access`を`Yes`にするとうまくいかないので、`No`を選択肢し、認証機能をfunctionで設定します）
また、`? Choose a Lambda source Create a new Lambda function`を選択するとAPIと一緒にFunctionも作られます。

### 5、[function/(function名)/src/app.js]の中を書き換えます。
今回はデータの取得だけなので、`Example get method`の中身のみ変更を加えます。

```javascript
const express = require('express')
const bodyParser = require('body-parser')
const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')
const { CognitoIdentityServiceProvider } = require("aws-sdk");

const cognito = new CognitoIdentityServiceProvider({
    region: "us-east-2",
  });

/**
 * [4]で話していた認証設定をここで行う。
 **/
const getCognitoUserByToken = async (req) => {
  const authorization = req.headers.authorization;
  if (!authorization) {
    return;
  }
  const data = await cognito
    .getUser({
      AccessToken: authorization || "",
    })
    .promise();
  if (!data) return;
  return data;
};

/**
 * Cognitoユーザーをユーザー名（前方一致）で検索する機能を定義する
 *
 * @TODO 
 * @param {string} username 
 * @returns 
 */
const searchUserByName = async (username) => {
    const { Users } = await cognito.listUsers({
        UserPoolId: getUserPooldId(),
        Filter: `username^='${username}'`
    }).promise()
    return Users
}

/**
 * 実際はLambdaの環境変数などを使う。（今回は2択そのまま書きます）
 */
const getUserPooldId = () => {
  const env = process.env.ENV || ''
  switch (env) {
    case 'prod':
    case 'production':
      return 'us-east-2_xxxxxxxxxx'
    default:
      return 'us-east-2_zzzzzzzzzz'
  }
}

// declare a new express app
var app = express()
app.use(bodyParser.json())
app.use(awsServerlessExpressMiddleware.eventContext())

// Enable CORS for all methods
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*")
  res.header("Access-Control-Allow-Headers", "*")
  next()
});

app.get('/users/:username', async function(req, res) {
  try {

    const user = await getCognitoUserByToken(req);
    if (!user || !req.headers.authorization) {
      res.status(401).json({
        message: "UnAuthorized",
      });
      return;
    }
    const users = await searchUserByName(req.params.username)

    // Add your code here
    res.json({users, env: process.env});
  } catch (e) {
    console.log(e)
    res.status(500).json({message: e.message, code: e.code})
  }
});


app.listen(3000, function() {
    console.log("App started")
});

// Export the app object. When executing the application local this does nothing. However,
// to port it to AWS Lambda we will create a wrapper around that will load the app from
// this file
module.exports = app

```

### 6、最後に`amplify push`します。

## 作成されているDynamoDBをimportする方法

1、REST APIやGraphQLで既に作成されているDynamoDBのテーブルにアクセスしたい場合は、Amplify CLIでデータをインポートします。

2、`amplify import storage`を実行しStorageとしてDynamoDBのテーブルをimportします。

3、選択に`S3 bucket`か`DynamoDB table`の選択がでますが、`DynamoDB table`を選びます。

4、importするテーブルの候補は`? Select the DynamoDB Table you want to import:`のようにCLI側が出してくれます。（AmplifyのProjectでのリージョンと同じ必要があるみたいです。）
また、一回で複数の選択はできないので、importしたいテーブルが複数ある場合は、何回か同じように実行する必要があります。

5、Cognitoの時と同じように`amplify add api`でREST APIを追加します。
`? Choose a Lambda source Create a new Lambda function`を選択するとAPIと一緒にFunctionも作られます。

6、[function]を書き換えます。

```javascript
const queryAllSiteItem = async (username) => {
  const params =  {
    TableName: 'xxxxxxxxxxx',
    IndexName: 'xxxxxxxxxxxxxxxxxxx',
    KeyConditionExpression: 'site_owner = :site_owner AND begins_with(stock_state, :stock_state)',
    ExpressionAttributeValues: {
      ':site_owner': username,
      ':stock_state': 'in'
    }, 
  }
  const result = await ddb.query(params).promise()
  console.log(result)
  return result
}
```

7、変更を加える箇所はDynamoDBからデータを取得する上記の定義ぐらいで、他はCognitoのUserPoolからユーザー情報を取得する流れとあまり変わらないと思います。（変更があった場合は、最後に`amplify push`をするのを忘れないようにする。）


#　さいごに
ここまで、あたかも全て理解している様に書いてましたが、今回行ったことのほとんどが自分一人では分からず、会社の先輩にアドバイスをいただきながら実装しました。終わった今では多少理解できるようになりましたが、今回ハマった箇所がかなり多かったため、正直今だに頭の中が整理できていない状態です。
その為、次は「Amplifyのバックエンド開発でハマったことと、その対処方法」について、できるだけ思い出しながらまとめようと思います。