AWSサーバーレスのウェブアプリケーションを構築をやってみた

#はじめに
AWSのハンズオンシリーズにある下記を実施した。
細かい画面操作と、CodeCommitではなくgithubを使いたかったため、この辺りを変更したバージョンを記述する。
ハンズオンに記載されている箇所は転記しない。

[サーバーレスのウェブアプリケーションを構築](https://aws.amazon.com/jp/getting-started/hands-on/build-serverless-web-app-lambda-apigateway-s3-dynamodb-cognito/)

#githubへソースをpush
CodeCommitではなく、Githubを使うためその辺り設定する。
そのため、ハンズオンにある下記はこちらで代用する
**モジュール１　ステップ２〜３**

###Macとgithubの連携
SSHキーの設定はこちらの記事を参考にした。
[Mac GitHub SSH接続設定](https://qiita.com/ucan-lab/items/e02f2d3a35f266631f24)

###リポジトリの作成
githubにて「wildrydes-site」というリポジトリをパブリックで作成した。

###作成したリポジトリのクローン
下記をローカル環境で実施
このとき、リポジトリを作成したいディレクトリに移動しておく。

```bash
git clone <作成したリポジトリのSSH URL>
```

###リソースのクローン
今回ハンズオンで使うAWSが用意しているソースを先ほどクローンしたリポジトリにクローンする。
（ややこしいですが。。。）
AWSのCLIを使うが、これも設定が必要となる。
[AWS コマンドラインインターフェイスの公式](https://aws.amazon.com/jp/cli/)
[ドキュメント（設定）](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html)

上記が準備できたら以下を実施

```bash
cd wildrydes-site/
aws s3 cp s3://wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website ./ --recursive --profile <AWS CLIで設定したIAMユーザ名>
```

###githubにpushする
```bash
git add .
git commit -m "handson commit"
git push <作成したリポジトリのSSH URL> master
```

下記のように無事、githubにAWSが準備したソースがpushされた。
![スクリーンショット 2021-02-21 16.07.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/473672bc-c039-df53-c2ca-261a366c9416.png)

#Amplifyコンソールでウェブホスティングを有効にする
ハンズオンにある下記をgithubで代用する
**モジュール１　ステップ4**

また、画面仕様が変更されているのでこのあたりも記載する。

###Amplifyを始める
下記の「Host your web app」の[Get started]をクリック
![スクリーンショット 2021-02-21 16.13.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/d96d6155-1b6d-250b-1046-b7cd4b60c4f4.png)

Githubをクリックして、続ける
![スクリーンショット 2021-02-21 16.16.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/4c75c3d0-fbfd-a8cb-924a-a1805fed2128.png)

Amplifyとgithubを連携する
![スクリーンショット 2021-02-21 16.18.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/af8d1c0d-31b7-a56d-651a-ff63eac46361.png)

先ほど作成したリポジトリを選択して続ける
![スクリーンショット 2021-02-21 16.20.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/29865de6-bcb6-fa17-ca5f-8d7efba12f5d.png)

ブランチや[ビルド設定の構成]ページは全てデフォルトのままにして、次へ進む。
最後の確認ページで[保存とデプロイ]を選択する。

完了したら、WildRydesサイトをローンチするためにサイトのイメージをクリックして画面を確認できます。
また、[master]のリンクをクリックすると、ブランチに関連するビルドとデプロイの詳細、そして、さまざまなデバイスのアプリのスクリーンショットが表示されます。

![スクリーンショット 2021-02-21 16.26.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/ee56d47a-be5a-192e-31d2-f77277e83696.png)

#ユーザーのアカウントを管理
対象サービス：Amazon Cognito
ハンズオンにある下記を実施する
**モジュール２**

#サーバーレスサービスバックエンド
対象サービス：AWS Lambda と Amazon DynamoDB
ハンズオンにある下記を実施する
**モジュール3**

lambdaの作成の箇所でハンズオンと異なる点を記載する。
###ランタイムの選択
**Node.js 14.x**を選択した

###コード
requestUnicorn.jsが見つからなかったため、下記の内容に書き換えた

```js
const randomBytes = require('crypto').randomBytes;

const AWS = require('aws-sdk');

const ddb = new AWS.DynamoDB.DocumentClient();

const fleet = [
    {
        Name: 'Bucephalus',
        Color: 'Golden',
        Gender: 'Male',
    },
    {
        Name: 'Shadowfax',
        Color: 'White',
        Gender: 'Male',
    },
    {
        Name: 'Rocinante',
        Color: 'Yellow',
        Gender: 'Female',
    },
];

exports.handler = (event, context, callback) => {
    if (!event.requestContext.authorizer) {
      errorResponse('Authorization not configured', context.awsRequestId, callback);
      return;
    }

    const rideId = toUrlString(randomBytes(16));
    console.log('Received event (', rideId, '): ', event);

    // Because we're using a Cognito User Pools authorizer, all of the claims
    // included in the authentication token are provided in the request context.
    // This includes the username as well as other attributes.
    const username = event.requestContext.authorizer.claims['cognito:username'];

    // The body field of the event in a proxy integration is a raw string.
    // In order to extract meaningful values, we need to first parse this string
    // into an object. A more robust implementation might inspect the Content-Type
    // header first and use a different parsing strategy based on that value.
    const requestBody = JSON.parse(event.body);

    const pickupLocation = requestBody.PickupLocation;

    const unicorn = findUnicorn(pickupLocation);

    recordRide(rideId, username, unicorn).then(() => {
        // You can use the callback function to provide a return value from your Node.js
        // Lambda functions. The first parameter is used for failed invocations. The
        // second parameter specifies the result data of the invocation.

        // Because this Lambda function is called by an API Gateway proxy integration
        // the result object must use the following structure.
        callback(null, {
            statusCode: 201,
            body: JSON.stringify({
                RideId: rideId,
                Unicorn: unicorn,
                UnicornName: unicorn.Name,
                Eta: '30 seconds',
                Rider: username,
            }),
            headers: {
                'Access-Control-Allow-Origin': '*',
            },
        });
    }).catch((err) => {
        console.error(err);

        // If there is an error during processing, catch it and return
        // from the Lambda function successfully. Specify a 500 HTTP status
        // code and provide an error message in the body. This will provide a
        // more meaningful error response to the end client.
        errorResponse(err.message, context.awsRequestId, callback)
    });
};

// This is where you would implement logic to find the optimal unicorn for
// this ride (possibly invoking another Lambda function as a microservice.)
// For simplicity, we'll just pick a unicorn at random.
function findUnicorn(pickupLocation) {
    console.log('Finding unicorn for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude);
    return fleet[Math.floor(Math.random() * fleet.length)];
}

function recordRide(rideId, username, unicorn) {
    return ddb.put({
        TableName: 'Rides',
        Item: {
            RideId: rideId,
            User: username,
            Unicorn: unicorn,
            UnicornName: unicorn.Name,
            RequestTime: new Date().toISOString(),
        },
    }).promise();
}

function toUrlString(buffer) {
    return buffer.toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

function errorResponse(errorMessage, awsRequestId, callback) {
  callback(null, {
    statusCode: 500,
    body: JSON.stringify({
      Error: errorMessage,
      Reference: awsRequestId,
    }),
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  });
}
```

###保存について
関数にある[Deploy]をクリックし、OKが出ると完了
![スクリーンショット 2021-02-22 0.39.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/9a1fe092-dab4-7848-b33d-e456455f277c.png)

#RESTful API
対象サービス：Amazon API Gateway
ハンズオンにある下記を実施する
**モジュール4**

###作成
ステップ１の作成がわかりにくいので、簡単に記載する。
下記から新たに作成する。
![スクリーンショット 2021-02-22 0.54.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/716c0bb7-5f0a-205c-13ce-4477a9bb6669.png)

![スクリーンショット 2021-02-22 0.57.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/5c5bb8f8-861a-0361-135d-8ab12f4eb841.png)

あとはハンズオンの手順通りに実施すると、うまくできた。

#さいごに
作成したリソースは削除し忘れないようにする！！