Cloud Datastoreに保存したSendGridのWebhookを参照するGoogle Cloud Functions API

やりたいこと

前回、SendGridのWebhookをDatastoreに貯めるところまで進めた。 tminami.hatenablog.com
今回は保存したデータを取得するAPIを、同じくGoogle Cloud Functionsでつくる。
Cloud Functionsの設定等は前記事参照。

ソース

index.js

const Buffer = require('safe-buffer').Buffer;
const Datastore = require('@google-cloud/datastore');

const BASIC_USERNAME = process.env.BASIC_USERNAME;
const BASIC_PASSWORD = process.env.BASIC_PASSWORD;
const PROJECT_ID = process.env.PROJECT_ID;
const datastore = Datastore({projectId: PROJECT_ID});

/**
 * Verify that the webhook request came from sendgrid.
 *
 * @param {string} authorization The authorization header of the request, e.g. "Basic ZmdvOhJhcg=="
 */
function verifyWebhook (authorization) {
  const basicAuth = Buffer.from(authorization.replace('Basic ', ''), 'base64').toString();
  const parts = basicAuth.split(':');
  if (parts[0] !== BASIC_USERNAME || parts[1] !== BASIC_PASSWORD) {
    const error = new Error('Invalid credentials');
    error.code = 401;
    throw error;
  }
}

/**
 * Receive a webhook from SendGrid.
 *
 * See https://sendgrid.com/docs/API_Reference/Webhooks/event.html
 *
 * @param {object} req Cloud Function request context.
 * @param {object} res Cloud Function response context.
 */
exports.getSendgridWebhook = async (req, res) => {
  try {
    if (req.method !== 'POST') {
      const error = new Error('Only POST requests are accepted');
      error.code = 405;
      throw error;
    }
    const sgMessageId = req.body['sg_message_id'] || null;
    if (!sgMessageId) {
      const error = new Error('No data error. require sg_message_id.');
      error.code = 500;
      throw error;
    }
    const isDesc = req.body['isDesc'] || false;
    const query = datastore
      .createQuery('sg_event')
      .filter('sg_message_id_prefix', '=', sgMessageId)
      .order('timestamp', {
        descending: isDesc,
      });
    const results = await datastore.runQuery(query);
    const json = JSON.stringify(results);
    
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify(results));
  } catch (err) {
    console.error(err);
    res.status(err.code || 500).send(err);
  };
};

index.js

{
  "name": "get-sendgrid-webhook",
  "version": "0.0.1",
  "main": "index.js",
  "dependencies": {
    "safe-buffer": "^5.1.2",
    "@google-cloud/datastore": "1.3.4"
  }
}

検索用index作成

上記のindex.jsでは『sg_message_id_prefix』をキーにして、『timestamp』昇順降順を選んでデータを取得している。
keyValue型のデータベースだと複数のプロパティを検索条件にするときはindexを作る必要があるみたい。
今回は以下手順で作成した。

  1. Cloud Shellを開く
  2. index.yamlを作成
    $ vim index.yaml
  3. 作成したindex.yamlをもとにインデックス作成
    $ gcloud datastore create-indexes index.yaml

    index.yaml

indexes:

- kind: sg_event
  properties:
  - name: sg_message_id_prefix
  - name: timestamp

- kind: sg_event
  properties:
  - name: sg_message_id_prefix
  - name: timestamp
    direction: desc

APIを叩いてみる

curlだとこんな感じ
curl -H 'BASIC_USERNAME:****' -H 'BASIC_PASSWORD:****' -H 'Content-Type:application/json' -X POST -d '{"sg_message_id":"****","isDesc": false}' 'エンドポイントURL'

所感

実際に使うとなったらインデックスを追加して、『event』のステータスも指定する感じにするか、
APIは汎用的にしておいて呼び出し側でjsonを分解して判定するかになる。(当たり前)