algolia と firebase と Vue を使ってハッシュタグ検索を実装する


[algolia](https://www.algolia.com/) という強力な検索サービスがあり、firebase と組み合わせることで簡単に全文検索やタグ検索などを実装することができます。今回は Instagram や Twitter にあるようなハッシュタグ検索を実装してみます。すぐできます。

## aloglia のインデックス作成

aloglia にログイン後、インデックスを作成します。今回は適当に books にします。インデックス作成自体はこれだけです。データを登録すると勝手にフィールドなどを作ってくれます。

作成後、API Key を取得します。フロントページで利用する検索用 API Key とインデックス登録などをする管理用の Admin API Key があります。検索用は Vue.js 側で、管理用は Firebase functions で使用します。

## algolia のインデックス登録

firestore にデータが登録更新されたら functions を起動して algolia へインデックス登録するようにします。

### init.ts

```typescript
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import * as algoliasearch from 'algoliasearch'
admin.initializeApp()
export const db = admin.firestore()

const ALGOLIA_ID = functions.config().algolia.app_id
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key

export const ALGOLIA_BOOK_INDEX_NAME = 'books'
export const algolia = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY)
```

### bookCreated.ts

```typescript
import * as functions from "firebase-functions"
import DocumentSnapshot = FirebaseFirestore.DocumentSnapshot
import DocumentData = FirebaseFirestore.DocumentData
import {algolia, ALGOLIA_BOOK_INDEX_NAME, db} from './init'

export const onBookCreated = functions.firestore.document('book/{bookId}').onWrite((change: functions.Change<DocumentSnapshot>) => {
  const book:DocumentData = change.after.data()
  book.objectID = change.after.id
  const index = algolia.initIndex(ALGOLIA_BOOK_INDEX_NAME)
  index.saveObject(Object.assign({}, book, { canonical: canonical }))
  })
})
```

firestore のトリガー `onWrite` を使うと特定ドキュメントが作成・更新されたときに関数を実行できます。

- [firestore トリガー](https://firebase.google.com/docs/functions/firestore-events)

book のスキーマはこのようになっています。 `hashtags` というフィールドに配列でハッシュタグを格納しています。

```json
{
  id: 'example_id',
  title: 'book title',
  hashtags: ['foo', 'bar', 'baz']
}
```

## algolia のハッシュタグ検索

無事にインデックスが登録されると algolia のコンソールにデータが表示されます。ここで2箇所設定を変更します。

### Dispay Settings

Dispay -> Faceting の項目に `hashtags` を追加します。

![スクリーンショット 2018-06-30 17.12.00.png](https://qiita-image-store.s3.amazonaws.com/0/2152/3bff8987-82ba-034a-0f7a-e93a98fdfefc.png "スクリーンショット 2018-06-30 17.12.00.png")

### Searchable Attributes

次に Ranking -> Searchable Attributes の項目にも `hashtags` を追加します。

![スクリーンショット 2018-06-30 17.13.11.png](https://qiita-image-store.s3.amazonaws.com/0/2152/a87791c3-8739-c6ba-8941-f7a2d65b94fa.png "スクリーンショット 2018-06-30 17.13.11.png")

こうすると Browse のページにハッシュタグの一覧が出てきます。

![スクリーンショット 2018-06-30 17.13.56.png](https://qiita-image-store.s3.amazonaws.com/0/2152/3b03b2bc-037b-2051-19a4-8ead8219bef6.png "スクリーンショット 2018-06-30 17.13.56.png")

これで準備は完了です。

## Vue.js

次は Vue.js でハッシュタグの候補サジェストを実装します。

必要なパッケージをインストールします。contenteditable の場合は `textcomplete.contenteditable` を追加でインストールしてください。

```
npm install --save algoliasearch textcomplete
```

### algolia.js

```javascript
import Algoliasearch from 'algoliasearch'

const config = {
  appId: process.env.ALGOLIA.APP_ID,
  searchKey: process.env.ALGOLIA.SEARCH_KEY
}
const algoliaClient = new Algoliasearch(config.appId, config.searchKey)

export const AlgoliaBooks = algoliaClient.initIndex(process.env.ALGOLIA.INDEXES.BOOKS)

export const AlgoliaBooksRegister = {
  id: 'markdown-hashtags',
  match: /(^|\s)#([a-z0-9+\-_\u30a0-\u30ff\u3040-\u309f\u3005-\u3006\u30e0-\u9fcf]*)$/,
  search: (query, callback) => {
    AlgoliaBooks.searchForFacetValues({
      facetName: 'hashtags',
      facetQuery: query
    }).then(content => {
      callback(content.facetHits)
    })
  },
  template: (hit) => {
    return '#' + hit.value
  },
  replace: (hit) => {
    return '$1#' + hit.value + ' '
  },
  header: '',
  footer: ''
}

export default ({ Vue }) => {
  Vue.prototype.$algolia = algoliaClient
}
```

### Vue component

```javascript
import { Textcomplete, Textarea } from 'textcomplete'
import { AlgoliaBooksRegister } from '../plugins/algolia'

export default {
  data () {
    return {
      book: {
        title: ''
      }
    }
  },
  mounted () {
    const editor = new Textarea(document.getElementById('title-example'))
    const textcomplete = new Textcomplete(editor)
    textcomplete.register([AlgoliaBooksRegister])
  }
}
```

これで `#` を入力すると algolia からリストを取得してサジェスト表示をしてくれます。

![スクリーンショット 2018-06-30 17.21.12.png](https://qiita-image-store.s3.amazonaws.com/0/2152/9b5b0135-f1fb-98f3-1a30-eb0a35d18550.png "スクリーンショット 2018-06-30 17.21.12.png")

非常に簡単ですね。