2020-11-08 :

名刺代わりの10冊メーカーを作ろう part2


今日やったこと

・Firestore のルール設定

firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /cards/{card} {
      allow read: if true;
      allow create: if true;
        match /datas/{data} {
          allow read: if true;
          allow create: if true;
        }
    }
  }
}

認証を入れるのはやめたのでかなりシンプルになった。保存されているデータはいつでも読み取り可能で、書き込みも可能。ただし、書き換えは不可能という仕様にした。

ログイン機能を実装しないのは、分かりやすさ、手軽さを最優先にしようと思ったというのが理由です。


・Firestore のキャッシュを利用する

src/views/Show.vue
created(){
    var db = firebase.firestore();
    var docRef = db.collection("cards").doc(this.$route.params.id)

    docRef.get({ source: "cache" }).then((doc) => {
      this.setData(doc, docRef)
    }).catch(error => {
      console.log("Error getting document:", error);
      docRef.get().then((doc) => {
        this.setData(doc, docRef)
      })
    })
  },

firestoreからデータを読み取るとき、まず初めにキャッシュが存在するか調べて、存在しなかったらサーバーから読み取るようにしました。従量課金制なので、なるべく節約したいですね。


・OGP生成機構を作る

ここが一番苦労しました。このサイトのように適当にmetaタグを入れて終わりという訳にもいかず。ページへのリクエストが飛んできたときにfirestoreとstorageからOGP用のデータを引っ張ってくる必要があり、ここのデータを持ってくるのに若干のタイムラグがあるためそれを制御するための機構が必要になります。

という訳で、firebase Functions を用意します。

functions/index.js
const functions = require('firebase-functions');
const express = require('express')
const app = express()
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore()

const url = 'https://books-card-maker.web.app/'
const site_name = '10冊メーカー'
const meta_description = '小説10選ページが生成できます。ログイン不要。'
const meta_keywords = ['名刺代わりの小説10選', '小説']
const og_description = '小説10選ページが生成できます。ログイン不要。'
const og_image_width = 1200
const og_image_height = 630
const fb_appid = ''
const tw_description = '小説10選ページが生成できます。ログイン不要。'
const tw_site = ''
const tw_creator = ''

const genHtml = (param, title) => `
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>${title}</title>
    <meta name="description" content=${meta_description}>
    <meta name="keywords" content=${meta_keywords.join(',')}>
    <meta property="og:locale" content="ja_JP">
    <meta property="og:type" content="website">
    <meta property="og:url" content=${url}>
    <meta property="og:title" content=${title}>
    <meta property="og:site_name" content=${site_name}>
    <meta property="og:description" content=${og_description}>
    <meta property="og:image:width" content=${og_image_width}>
    <meta property="og:image:height" content=${og_image_height}>
    <meta property="fb:app_id" content=${fb_appid}>
    <meta name="twitter:title" content=${title}>
    <meta name="twitter:description" content=${tw_description}>
    <meta name="twitter:site" content=${tw_site}>
    <meta name="twitter:creator" content=${tw_creator}>
    <meta property="og:image" content="https://firebasestorage.googleapis.com/v0/b/books-card-maker.appspot.com/o/ogp%2F${param}.png?alt=media&token=9476a1a2-ce07-4fcb-bd99-634b1a4a13a5">
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:image" content="https://firebasestorage.googleapis.com/v0/b/books-card-maker.appspot.com/o/ogp%2F${param}.png?alt=media&token=9476a1a2-ce07-4fcb-bd99-634b1a4a13a5">
  </head>
  <body>
    <script>
      // クローラーにはメタタグを解釈させて、人間は任意のページに飛ばす
      location.href = 'https://books-card-maker.web.app/card/${param}';
    </script>
  </body>
</html>
`
app.get('/id/:id', async (req, res) => {
  const docRef = db.collection("cards").doc(req.params.id)
  try {
    const docID = await docRef.get()
    if (docID.data() == undefined) {
      return res.status(404).json({
        error: {
          message: "docment が参照できませんでした"
        }
      })
    }
    const title = docID.data().name + "さんの名刺代わりの小説10選"
    const html = genHtml(req.params.id, title)
    res.set('cache-control', 'public, max-age=3600');
    return res.send(html)

  } catch (err) {
    return res.status(400).json({
      error: {
          message: err.message
      }
    })
  }
})

exports.generate = functions.https.onRequest(app)

firebase.jsonに追記して、https://<domain>/id/:id を踏むと上記のfunctionが発動するようにしています。

firebase.json
  "hosting": {
    "public": "dist",
    "rewrites": [
      {
        "source": "/id/**", "function": "generate"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  },

無事生成できました

画像はこんな感じのを生成する予定。

明日やること

  • OGP画像のデザイン
  • 入力のバリデーション作成
  • ホーム画面の作成
  • ツイート機能実装
  • UIを整える