LoginSignup
1
0

More than 3 years have passed since last update.

AddSearchでFirestoreのCloud Functionsで同期しよう

Last updated at Posted at 2020-07-24

まえがき

FirebaseのFirestoreは便利だが、クエリーの書き方に制限が多くて使い物にならない。
制限を解消するため他のサービスと連携する必要がある。
Algolia(https://www.algolia.com/)
というサービスもあるが、
他にもAddSearch(https://www.addsearch.com/)
というAlgoliaよりも微妙に安いサービスが有る。
今回はAddSearchの記事です。
僕が人柱になって記事書けば日本で発展してくれて、賢い方たちが情報発信してくれたら嬉しいなーっていう魂胆で書きましたよ。

プラン(お金関連)はココにあります。
https://www.addsearch.com/pricing/

Linux開発者のリーナス同じフィンランドのヘルシンキにある会社のサービス。
使い放題らしい。
サンプルや世間の情報があまりないのが残念。
情報が英語ですらあんまりない。
「AddSearch」でググっても「Add」と「Search」での結果が出る始末。

Firestoreの中身の値をAddSearchへ中身をコピーするSync(同期)する仕組みつくって、
コピーされたAddSearchからクエリーを書く方法で対処したいです。

FirebaseのデータベースサービスのFirestore上でドキュメントが生成・削除・更新があれば、
それを検知して、AddSearchに更新をかけるということ。

SyncするためにFirebaseのCloud Functionsで書きます。

「Cloud Functions for Firebase」内のデータベースをsyncさせるイメージです。

users/配下にある前提で話を勧めていきますね。

Cloud Functiondsの準備

キーの設定

firebase functions:config:set addsearch.sitekey="{サイトキー}" addsearch.secretkey="{シークレットキー}"

{サイトキー}これはあなたの管理画面の「Your Site Key」にしてください。
{シークレットキー}これはあなたの「Your Secret API Key」にしてください。

{}はいらないですよ。

・スクショ貼っておきます。

Screen Shot 2020-07-24 at 19.14.02.png

コード

TypeScriptで書きます。
一応、JSとAddSearchで使う公式っぽいnpmはあります(https://www.npmjs.com/package/addsearch-js-client)が、Issueできいたら検索専用なので今回使いません。
ちなみにそのnpmはTypeScriptは対応してない。
TypeScriptの型定義ファイルがなかったです。

自分でHTTTPリクエストする必要がある。
無難にaxiosを使います。使わないやり方は自分で読み替えて下さい。

Syncするための仕組み作成

準備

以下で色々npm入れて下さい。

npm install -g npm
npm i -g firebase-tools

firebase init functions して下さい。
中に生成されるので最初にフォルダ切ったほうがいいです。
? What language would you like to use to write Cloud Functions?ではTypeScriptを忘れずに!

> firebase init functions                                                                                                               Mon Jul 13 10:53:23 2020

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/shinriyo/development/functions_apps


=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Please select an option: Use an existing project
? Select a default Firebase project for this directory: hoge-project (hoge)
i  Using project hoge-project (hoge)

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? TypeScript
? Do you want to use TSLint to catch probable bugs and enforce style? Yes
✔  Wrote functions/package.json
✔  Wrote functions/tslint.json
✔  Wrote functions/tsconfig.json
✔  Wrote functions/src/index.ts
✔  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes

> protobufjs@6.9.0 postinstall /Users/shinriyo/development/functions_apps/functions/node_modules/protobufjs
> node scripts/postinstall

npm notice created a lockfile as package-lock.json. You should commit this file.
added 287 packages from 221 contributors and audited 287 packages in 12.772s

気をつけてほしいのが「"node": "10"」にしておかないとnodeが古いとCloud Functionsがサポートしなくなるのでおそらく8となってるのを10に変えて下さい。
しかし、11はサポートしてないみたいですw

{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase emulators:start --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "10"
  },
  "main": "lib/index.js",
  "dependencies": {
    "algoliasearch": "^4.3.0",
    "axios": "^0.19.2",
    "firebase-admin": "^8.10.0",
    "firebase-functions": "^3.6.1"
  },
  "devDependencies": {
    "tslint": "^5.12.0",
    "typescript": "^3.8.0",
    "firebase-functions-test": "^0.2.0"
  },
  "private": true
}

設定ファイル

こんな感じに作ってきました。
export してる箇所ありますが後で作るのでこの瞬間はエラーになると思います。

typescript
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
const env = functions.config();

// AddSearch
export const baseURL = 'https://api.addsearch.com';
// 設定のYour Site Key (Index public keyのこと)
export const siteKey = env.addsearch.sitekey;
// 設定のYour Secret API (Secret key is used in the authentication header)
export const secretKey = env.addsearch.secretkey;

// 各Functionのため
export * from './user.onDelete';
export * from './user.onUpdate';
export * from './user.onCreate';

生成

今回生成ですが、UpdateやDeleteなども作ります。(後述)

本当はCREATEするときにPOST使いたいと思うが、
https://www.addsearch.com/docs/api/indexing-create/
idはこちらで定義されたやつは使えない!!
仕方ないので
POSTはAddSearchの自動生成のテキトーなIDを使いたいときではいいが、
今回はFiresoreのdocumentIdを使いたいので、

なので以下これはだめですね。

        axios.post(`${baseURL}/v2/indices/${siteKey}/documents/`, args, {

https://www.addsearch.com/docs/api/indexing-update/
新規作成での同期はUPDATEでも使えるうPUTで書きます。新規も可能。

コード全体。
ファイル名にわざと.つけてますが動きます。
嫌なら名前を好きに付けて下さい。

src/user.onCreate.ts
import { index, baseURL, siteKey, secretKey } from './index';
import * as functions from 'firebase-functions';

// AddSearchのDocument生成
export const userOnCreateForAddSearch = functions.firestore
    .document('users/{userId}')
    .onCreate(async (snap, context) => {
        const axios = require('axios');
        const data = snap.data();
        const objectID = snap.id;

        const args = {
            withCredentials: true,
            custom_fields: {
                ...data
            },
            headers: {
                "Content-Type": "application/json",
            }
        }

        axios.put(`${baseURL}/v2/indices/${siteKey}/documents/${objectID}`, args, {
            // HTTP Basic Auth
            auth: {
                username: siteKey,
                password: secretKey,
            }
        })
            .then((response: any) => {
                console.log(response.data)
            })
            .catch((error: any) => {
                console.log(error)
            })
            .then(function () {
                console.log("*** finish ***")
            })
    });

注意点

axios.postの第三引数に「HTTP Basic Auth」にいるものを書く。

// HTTP Basic Auth
auth: {
username: siteKey,
password: secretKey,
}

の箇所。ハマった。
argsに含めてもだめだった。

内容は、custom_fieldsを使って書くこと。dataと書いてハマった。

{
  "id": objectID,
  "custom_fields": {
    ...data
  }
}

みたいにやること。
custom_fieldsの中に欲しいデータ書くらしい。
idはAlgoliaみたくobjectIDと書くんじゃなく、単にid

withCredentialsはおそらく必須。

削除処理

PUTSの処理をそもままやればいけると思ったらできない。

args は2番目だったと思ったら違う。

このサイト、よくみたら
PUTとDELETEで引数の順番が違う。

axios.delete(url[, config])
axios.put(url[, data[, config]])
src/user.onDelete.ts
import { index, baseURL, siteKey, secretKey } from './index';
import * as functions from 'firebase-functions';

// AddSearchのDocument生成
export const userOnCreateForAddSearch = functions.firestore
    .document('users/{userId}')
    .onCreate(async (snap, context) => {
        const axios = require('axios');
        const data = snap.data();
        const objectID = snap.id;

        const args = {
            withCredentials: true,
            custom_fields: {
                ...data
            },
            headers: {
                "Content-Type": "application/json",
            }
        }

        axios.put(`${baseURL}/v2/indices/${siteKey}/documents/${objectID}`, args, {
            // HTTP Basic Auth
            auth: {
                username: siteKey,
                password: secretKey,
            }
        })
            .then((response: any) => {
                console.log(response.data)
            })
            .catch((error: any) => {
                console.log(error)
            })
            .then(function () {
                console.log("*** finish ***")
            })
    });

更新

src/user.onUpdate.ts
import { index, baseURL, siteKey, secretKey } from './index';
import * as functions from 'firebase-functions'

export const userOnUpdateForAddSearch = functions.firestore
    .document('users/{userId}')
    .onUpdate(async (event, context) => {
        const axios = require('axios');
        // const oldData = event.before.data();
        const newData = event.after.data();
        const documentId = event.after.id;
        const args = {
            withCredentials: true,
            custom_fields: { ...newData },
            headers: {
                "Content-Type": "application/json",
            }
        }

        // PUT /v2/indices/{index public key}/documents/{document id}
        axios.put(`${baseURL}/v2/indices/${siteKey}/documents/${documentId}`, args, {
            // HTTP Basic Auth
            auth: {
                username: siteKey,
                password: secretKey,
            }
        })
            .then((response: any) => {
                console.log(response.data)
            })
            .catch((error: any) => {
                console.log(error)
            })
            .then(function () {
                console.log("*** finish ***")
            })
    });

Cloud Functionsにデプロイします

firebase deploy --only functions

あとがき

全部削除はわからない

消し方わかりません。中の人に消してもらいましたw

非対応の型?

タイムスタンプや、マップやマップの配列はうまく同期されてませんでした。
対応してないのか?教えてほしい。

ログ

これ叩いてちまちま見ている。
もっといいやり方あったら教えてほしい。

firebase functions:log

AddSearchに今はいってるのか確かめるのはPostmanとかで見るしか無いです。
Webで見る仕組みを来月2020/08くらいに出すっぽいです。

Screen Shot 2020-07-24 at 19.27.23.png

おまけ

  • 現状確認しているバグ

自動生成したやつではなく、マニュアルで(自分で)作ったIDでは検索できない。
サーバーエラーになる。(ほうこくすると今対処してるらしい)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0