【クソアプリ】Amplifyで作る CGI風アクセスカウンター

この記事は「[クソアプリ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/kuso-app) ２」の１０日目の記事です。

https://qiita.com/advent-calendar/2021/kuso-app


去年はこちらで参加しました。

https://qiita.com/goosys/items/8baa6eb0a4c2f97d8691

# 概要

今年下半期は[Amplify](https://aws.amazon.com/jp/amplify/)に大変悩まされたので、復習も兼ねてシンプルなアプリを作ってみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/8baab837-16a3-8cc7-63f0-6e2983fb3ff2.png)

動作サンプル
https://dev.d1nlszba0lmhed.amplifyapp.com/

# ポイント

## CGI風の呼び出し

- `ContentType: image/***`のレスポンスを返すCGI風のエンドポイント
- 最上位パスは無駄に`/cgi-bin/`
- IMGタグで描画する懐かしのパターン

```html
<div id="counterBox">
  <span>ようこそ。あなたは</span>
  <img src="https://ENDPOINT/cgi-bin/counter">
  <span>人目のお客様です</span>
</div>
```

## キリ番機能

- **下一桁が0の時**と**ゾロ目の時**はキリ番
- キリ番の時は**ダサめ**の装飾付き画像（クリスマス風）に変わります
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/7b4310d7-b210-c7df-7055-a2a7b03b5948.png)
- それ以外は通常デザイン
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/1500401a-7268-dae8-a44d-53f7b2818140.png)

## CGIカウンター風のデータ構造

- 理想的には、１アクセス毎に「1」という1バイト文字列をテキストファイルに追記していき、テキストファイルのバイト数を調べることでカウントを得られるという**例のアレ**がやりたい
- S3とか使えばできるような気もしたが、今回はAmplifyということで無駄にDynamoDBを使っていく
- アクセス毎に１レコードを無駄に追加していき、DynamoDBのAPIでテーブル全件カウントするという無茶な感じにしてみる

# Amplifyで実装

`amplify`コマンドで以下の構成を作っていきます。

| resource | type | memo |
| -- | -- | -- |
| backend/api/cgibin | REST | CGI風のエンドポイントを作るAPI Gateway |
| backend/api/kiricounter | GraphQL | カウントをするDBとDB操作用のAppSync |
| backend/function/counter | Lambda | CGI部分の実装 |
| backend/function/appsync | Lambda Layer | `aws-appsync`等諸々のパッケージをLayer化 |
| backend/hosting/amplify-hosting | | 動作サンプルサイト |


## ポイント

### AppSync用のLambda Layer

- 今回特に分ける必要もないが、今後実際に使うときには分けておいた方がよさそうなので練習がてら作ってみる
- （`aws-appsync`か`graphql`かどちらか忘れたが）最新版を入れすぎると動かなかったので現状動いたものをメモ

```json:package.json
{
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "aws-appsync": "^4.1.4",
    "aws-sdk": "^2.1030.0",
    "graphql": "^15.7.0",
    "graphql-tag": "^2.12.6",
    "isomorphic-fetch": "^3.0.0"
  },
  "devDependencies": {}
}
```

### Lambda Layerを使う

- `amplify function update` からそれっぽいものを選んでいくだけ。便利
- メインのLambda自体の容量も小さくなるため、AWSのコンソールで直接編集もできるようになって便利

### LambdaからAppSyncとDynamoDBを使う

- `amplify function update` の`permission`関係のところから同様に選んでいく
- AppSyncはGraphQLのAPIを指定するとパーミッションが貰える
- DynamoDBは`storage`カテゴリに属している（わかりにくい）ため、そこから選んでいく
  - 使いたいDynamoDBテーブルをスペースで複数選択していく
  - 欲しいものだけ貰えて便利
- パーミッションが貰えると`src/index.js`に環境変数が書き込まれる
- 便利

```js:backend/function/counter/src/index.js
/* Amplify Params - DO NOT EDIT
	API_KIRICOUNTER_COUNTERTABLE_ARN
	API_KIRICOUNTER_COUNTERTABLE_NAME
	API_KIRICOUNTER_GRAPHQLAPIENDPOINTOUTPUT
	API_KIRICOUNTER_GRAPHQLAPIIDOUTPUT
	ENV
	REGION
Amplify Params - DO NOT EDIT */

require('isomorphic-fetch')
const aws = require('aws-sdk')
const gql = require('graphql-tag')
const AWSAppSyncClient = require('aws-appsync').default
const { AUTH_TYPE } = require('aws-appsync')

const client = new AWSAppSyncClient({
  url: process.env['API_KIRICOUNTER_GRAPHQLAPIENDPOINTOUTPUT'],
  region: process.env['REGION'],
  auth: {
    type: AUTH_TYPE.AWS_IAM,
    credentials: ()=> aws.config.credentials
  },
  disableOffline: true
})

const incrementMutation = gql
  `mutation MyMutation {
    createCounter(input: {count: "1"}) {
      createdAt
    }
  }
  `

const increment = () => {
  return client.mutate(
    {
      mutation: incrementMutation
    }
  ).then((result) => {
    if (result) {
      // console.log('result', result)
      return result
    } else {
      Promise.reject('error. increment result is null')
    }
  }).catch(console.error)
}

const docmentClient = new aws.DynamoDB.DocumentClient()

const count = () => {
  const params = {
    TableName: process.env['API_KIRICOUNTER_COUNTERTABLE_NAME'],
    Select:'COUNT'
  };
  return docmentClient.scan(params).promise()
  .then((data) => {
    if (data) {
      console.log('count result', data)
      return data
    } else {
      Promise.reject('error. count result is null')
    }
  }).catch(console.error)
}
```

## 気を付けるポイント

- `amplify add`で出来る設定は`update`でも大体やり直せる
    - 名前とかは無理そうだが、それ以外は何度でもやり直せるので気にせずガシガシやって大丈夫
- `amplify remove`する時は依存関係から先に消す
    - 依存しているAuthやLayerやPermissionが残っている状態で消そうとすると泣きそうになるくらいのエラーになるので気を付ける
- `amplify push`するとデプロイした情報がローカルに保存される
    - `team-provider-info.json`などに差分が出るため、忘れずにコミットする
    - 見やすさのため、pushで出た差分は作業のコミットとは分けてコミットしておく

# 終わり

いやー 便利ですね