tag:crieit.net,2005:https://crieit.net/tags/Firestore/feed 「Firestore」の記事 - Crieit Crieitでタグ「Firestore」に投稿された最近の記事 2020-06-05T22:31:45+09:00 https://crieit.net/tags/Firestore/feed tag:crieit.net,2005:PublicArticle/15923 2020-06-05T22:29:21+09:00 2020-06-05T22:31:45+09:00 https://crieit.net/posts/620b85ee075fd93545b20ccfb92d3491 自分の理想の教室を、友達とリアルタイムで一緒に作れるウェブサービス「みんなのきょうしつ」をリリースしました。 <p>さっき投稿した記事で、4日前に「musico」というウェブアプリをリリースしたことについて書きましたが、</p> <p>好きな楽曲について語り合うウェブサービス「musico」を作ってみました。<br /> <a href="https://crieit.net/posts/musico">https://crieit.net/posts/musico</a><br /> <a target="_blank" rel="nofollow noopener" href="https://musi-co.fun">musico | find track you like and talk about it</a></p> <p>実は昨日、もう一つウェブアプリをリリースしましたw<br /> その名も「みんなのきょうしつ」。</p> <p><a href="https://crieit.now.sh/upload_images/8ae825a52f93537d282260b5dff649f25eda3eaa27764.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8ae825a52f93537d282260b5dff649f25eda3eaa27764.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://class-room.fun">みんなのきょうしつ</a></p> <h2 id="開発の動機"><a href="#%E9%96%8B%E7%99%BA%E3%81%AE%E5%8B%95%E6%A9%9F">開発の動機</a></h2> <p>このアプリは、もともと3月のコロナ禍による一斉休校をきっかけに、いきなり会えなくなった友達とオンラインで再開して貰い、小中高生のみなさんに、社会の混乱からのストレスを一時的にでも忘れてもらえたら良いなという思いで、二日間ぐらいでガッと基本的な機能を作って公開しようとしていたものでした。ですが、その後なかなか着手する時間が取れず、限定的な公開にとどめて、ほぼ動いていない状態が続いていました。</p> <h2 id="開発頓挫、からの突貫リリースへ"><a href="#%E9%96%8B%E7%99%BA%E9%A0%93%E6%8C%AB%E3%80%81%E3%81%8B%E3%82%89%E3%81%AE%E7%AA%81%E8%B2%AB%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%B8">開発頓挫、からの突貫リリースへ</a></h2> <p>そんななか、先日の「musico」の開発と公開をきっかけに、テンションとモチベーションがひさびさに爆上がりしたので、この機を逃す手はないと思い、一念発起して昨日・一昨日をかけて最低限の機能を実装し、公開していみました。</p> <h2 id="どんなアプリ?"><a href="#%E3%81%A9%E3%82%93%E3%81%AA%E3%82%A2%E3%83%97%E3%83%AA%EF%BC%9F">どんなアプリ?</a></h2> <p>このアプリでは、教室を模した座席表的なものに、自分の好きな人やモノの名前と写真を登録していき、自分の理想の教室が作れます。</p> <p><a href="https://crieit.now.sh/upload_images/3cd6bc3848f85dbd1abedf7a1cfeb3cc5eda46c7d816b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3cd6bc3848f85dbd1abedf7a1cfeb3cc5eda46c7d816b.png?mw=700" alt="image" /></a></p> <p>さらに、URLを共有することで、別の場所にいる友だちとオンラインでリアルタイムで共同編集することが出来ます。(動画は開発中に撮影したものです)</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.youtube.com/watch?v=N7qo5popZ3Q">動作ムービー</a></p> <p>また、「記念撮影」機能で作った教室の画像を作成・ダウンロードしたり、各種SNSに手軽にシェアすることが出来ます。(写真は友人が作った「あつまれ どうぶつの森」のキャラクターが集まる教室です)</p> <p><a href="https://crieit.now.sh/upload_images/43103ea48631f57b3237c5df0b282b575eda46f1109e2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/43103ea48631f57b3237c5df0b282b575eda46f1109e2.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://class-room.fun/classrooms/8jVBr7suU0In1vbE3Ze8">https://class-room.fun/classrooms/8jVBr7suU0In1vbE3Ze8</a></p> <p>地味にインスタ映えしたりしますw</p> <p><a href="https://crieit.now.sh/upload_images/1780742f62e146dcfd91693883f4363d5eda47b04c5d4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1780742f62e146dcfd91693883f4363d5eda47b04c5d4.png?mw=700" alt="image" /></a></p> <h3 id="技術的なこと"><a href="#%E6%8A%80%E8%A1%93%E7%9A%84%E3%81%AA%E3%81%93%E3%81%A8">技術的なこと</a></h3> <p>技術スタック的には「musico」と同じくFirebase+Vuejs(Nuxtjs)という、構成です。「musico」はUniversalモード(SSR)で作りましたが、こっちはSPAとして開発し、OGP取得部分のみCloud Functionsで作りました。</p> <p>しかし、Firebaseめっちゃ便利ですね〜。</p> <p>こっちもあんまり凝ったことはしていないのですが、OGPの動的生成あたりとか、記念写真機能あたりについて、あとで技術的なことを書こうかなと思っています。</p> <p>それではまた。</p> ぷろみつ tag:crieit.net,2005:PublicArticle/15909 2020-05-24T17:18:35+09:00 2020-05-24T17:18:35+09:00 https://crieit.net/posts/Nuxt-Firebase-Cloud-Messaging-FCM-Web Nuxt+Firebase Cloud Messaging(FCM)でWebプッシュ通知を送る <p>WebでもFCMが使えるようになったので、試してみたときの備忘録。<br /> これでSafari以外には、通知が送れるようになる(<em>´ω`</em>)</p> <h3 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h3> <p>構成は、@nuxtjs/pwaでPWA化している感じ。</p> <h4 id="コンソール側"><a href="#%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E5%81%B4">コンソール側</a></h4> <p>Settingsでウェブプッシュ証明書を作成して、鍵ペアを取得する。</p> <p><img width="973" alt="スクリーンショット_2020-05-24_17_06_33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/d17f3a0e-e6df-73a7-d57b-a6ff36e7c79f.png"></p> <p>これを、PUBLIC_VAPID_KEYという環境変数に設定しておく。</p> <h4 id="クライアント側"><a href="#%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E5%81%B4">クライアント側</a></h4> <h5 id="firebaseの初期化"><a href="#firebase%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96">firebaseの初期化</a></h5> <p><code>~/plugins/firebase.ts</code>というファイルを用意し、firebaseを初期化</p> <pre><code class="typescript">// ~/plugins/firebase.ts import * as firebase from "firebase/app"; import "firebase/auth"; import "firebase/firestore"; import "firebase/messaging"; if (!firebase.apps.length) { // まずは、firebaseの初期化 firebase.initializeApp({ apiKey: process.env.API_KEY, authDomain: process.env.AUTH_DOMAIN, databaseURL: process.env.DATABASE_URL, projectId: process.env.PROJECT_ID, storageBucket: process.env.STORAGE_BUCKET, messagingSenderId: process.env.MESSAGING_SENDER_ID, appId: process.env.APP_ID, measurementId: process.env.MEASUREMENT_ID }); // Push通知をサポートしているかをチェック // サポートしていないと、firebase.messaging()を呼んだときに例外が発生 const isSupported = firebase.messaging.isSupported(); // コンソールで発行した、ウェブプッシュ証明書の鍵ペアを取得 const publicVapidKey = process.env.PUBLIC_VAPID_KEY; if (!!publicVapidKey && process.client && !!isSupported) { // FCMの初期化。鍵ペアを設定する const messaging = firebase.messaging(); messaging.usePublicVapidKey(publicVapidKey); // @nuxtjs/pwaが生成するsw.jsと、 // 後で作成するFCM受信処理用のsw-firebase-messaging.jsを統合するための設定 navigator.serviceWorker .register("/sw.js") .then(registration => messaging.useServiceWorker(registration)) .catch(err => console.error(err)); } } export default firebase; </code></pre> <h5 id="トークンの取得"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E5%8F%96%E5%BE%97">トークンの取得</a></h5> <p>Push通知を送る際の宛先として、トークンを指定しないといけないので取得。<br /> トークンは端末ごとに取得する必要があるので、注意が必要。<br /> ※PCとスマホだとそれぞれトークンが違う</p> <pre><code class="typescript">import firebase from "~/plugins/firebase"; // トークンの取得 public async getToken(user: User) { const isSupported = firebase.messaging.isSupported(); if (!isSupported) return; const token = await firebase.messaging().getToken(); // firestoreにトークンを保存しておく処理(中身は略) await saveToken(user, token); } </code></pre> <p>Firestoreなどへユーザごとにトークンを保存しておく。</p> <p><code>firebase.messaging().getToken();</code>を呼んだ際に、<br /> 通知の設定が「確認」だと、許可を求めるダイアログが表示される。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/e9976414-8418-09c1-8846-25211a949776.png" alt="スクリーンショット 2020-05-24 16.30.23.png" /></p> <p>これが許可されていないと、トークンも取得できない。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/c21078c8-38d0-04c7-133a-b5402611443d.png" alt="スクリーンショット 2020-05-24 16.30.35.png" /></p> <h5 id="メッセージを受け取ったときの処理"><a href="#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E5%87%A6%E7%90%86">メッセージを受け取ったときの処理</a></h5> <p>メッセージを受信したときに受け取る関数は2つあり、</p> <ul> <li>フォアグラウンド(画面を見ている時) ... onMessage</li> <li>バックグラウンド(画面を見ていない時) ... setBackgroundMessageHandler</li> </ul> <p>・【参考】<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/receive#handle_messages_when_your_web_app_is_in_the_background">JavaScript クライアントでメッセージを受信する  |  Firebase</a></p> <p>今回はバックグラウンドのときに通知を送りたいので、<br /> <code>setBackgroundMessageHandler</code>を設定していく。</p> <p>こんな感じ。</p> <p>ドキュメントを見ると、『<code>firebase-messaging-sw.js</code>というファイル名で作成』と書かれているけど、<br /> その名前にすると、自動で読み込まれてしまう。。</p> <p>開発用と本番用で切り替えたいときなどもあるので、@nuxtjs/pwaが生成するsw.jsと統合できるように、<br /> <code>sw-firebase-messaging.js</code>という名前でファイルを作成しておく。</p> <p>ファイル名を変更したので、上で書いている「firebaseの初期化」の部分で、<br /> <code>messaging.useServiceWorker(registration)</code>を呼んでいる形。</p> <pre><code class="javascript">// ~/static/sw-firebase-messaging.js importScripts("https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js"); importScripts("https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js"); // Firebaseの初期化 firebase.initializeApp({ apiKey: "...", authDomain: "...", databaseURL: "...", projectId: "...", storageBucket: "...", messagingSenderId: "...", appId: "...", measurementId: "...", }); // [START background_handler] const isSupported = firebase.messaging.isSupported(); if (!!isSupported) { const messaging = firebase.messaging(); // バックグラウンド時の処理 messaging.setBackgroundMessageHandler(function(payload) {  // 受け取ったFCMの内容を取得 const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, icon: payload.notification.icon, }; // 通知を作成する return self.registration.showNotification(notificationTitle, notificationOptions); }); } // [END background_handler] </code></pre> <h5 id="nuxt.config.tsでPWA関連の設定をする"><a href="#nuxt.config.ts%E3%81%A7PWA%E9%96%A2%E9%80%A3%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E3%81%99%E3%82%8B">nuxt.config.tsでPWA関連の設定をする</a></h5> <p>作成した<code>sw-firebase-messaging.js</code>を取り込む設定と、<br /> @nuxtjs/pwaが生成するmanifest.jsonに、gcm_sender_idを追加する設定を追加</p> <pre><code class="typescript">import { Configuration } from "@nuxt/types"; const config: Configuration = { // 略 modules: [ "@nuxtjs/pwa", ], workbox: { // sw-firebase-messaging.jsをimportするように追加 importScripts: [ "sw-firebase-messaging.js" ] }, pwa: { manifest: { // manifest.jsonにgcm_sender_idを追加 gcm_sender_id: process.env.MESSAGING_SENDER_ID || "" } }, }; export default config; </code></pre> <p>これでクライアント側はOK!</p> <h4 id="サーバ側: メッセージを送信する"><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E5%81%B4%3A+%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E9%80%81%E4%BF%A1%E3%81%99%E3%82%8B">サーバ側: メッセージを送信する</a></h4> <p>メッセージの送信は、firebase-adminでできる。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/send-message?hl=ja">アプリサーバーからの送信リクエストを作成する  |  Firebase</a></p> <p>firestoreを利用しているので、Cloud Functionsのfirestoreトリガーを使い、<br /> ドキュメントが追加されたら通知するようにしている例。</p> <pre><code class="typescript">import * as functions from "firebase-functions"; import admin from "../common/firebaseAdmin"; // 初期化済みのfirebase-admin export default functions .firestore.document("ドキュメントのパス") .onCreate(async (snap, context) => { // getTokenで保存しておいたトークンを取得(中身は略) const token = getToken(); // 通知の送信 const title = "通知するタイトル"; const body = "通知する本文"; const icon = "通知で表示するアイコン画像のURL" const link = "通知をタップしたときに開くURL" await admin.messaging().send({ // 送信先の端末のトークン token: token, // 通知する内容 notification: { title: title, body: body }, // Web Push向けの通知内容 webpush: { notification: { icon: icon }, fcmOptions: { link: link } } }); }); </code></pre> <p>送信はこれだけ!</p> <h3 id="ほかの小ネタ"><a href="#%E3%81%BB%E3%81%8B%E3%81%AE%E5%B0%8F%E3%83%8D%E3%82%BF">ほかの小ネタ</a></h3> <h4 id="トークンを削除する"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B">トークンを削除する</a></h4> <p><code>firebase.messaging().deleteToken();</code>でトークンを無効化できる。</p> <pre><code class="typescript">// トークンの削除 public async deleteToken(user: User) { const isSupported = firebase.messaging.isSupported(); if (isSupported) { const token = await firebase.messaging().getToken(); await firebase.messaging().deleteToken(token); } } </code></pre> <h4 id="フォアグラウンドで通知を受け取ったときになにかする"><a href="#%E3%83%95%E3%82%A9%E3%82%A2%E3%82%B0%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%81%A7%E9%80%9A%E7%9F%A5%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E3%81%AA%E3%81%AB%E3%81%8B%E3%81%99%E3%82%8B">フォアグラウンドで通知を受け取ったときになにかする</a></h4> <p><code>onMessage</code>を使うと、通知を受け取ったときに呼び出してくれる。</p> <pre><code class="typescript">firebase.messaging().onMessage(async (payload) => { // 受け取ったときの処理 }); </code></pre> <h4 id="トークンが変更されたときになにかする"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%8C%E5%A4%89%E6%9B%B4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E3%81%AA%E3%81%AB%E3%81%8B%E3%81%99%E3%82%8B">トークンが変更されたときになにかする</a></h4> <p><code>onTokenRefresh</code>を使うと、トークンが更新されたときに呼び出してくれる。<br /> 新しいトークンは再度<code>getToken()</code>を呼ばないといけない。</p> <pre><code class="typescript">firebase.messaging().onTokenRefresh(async () => { // トークンが更新されたときの処理 }); </code></pre> <h4 id="通知の許可状態を確認する"><a href="#%E9%80%9A%E7%9F%A5%E3%81%AE%E8%A8%B1%E5%8F%AF%E7%8A%B6%E6%85%8B%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">通知の許可状態を確認する</a></h4> <p>通知の状態は、<code>Notification.permission</code>で確認できるらしい。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/notification/permission">Notification.permission - Web API | MDN</a></p> <pre><code class="typescript">if (Notification.permission === "default") { // 確認(デフォルト) } else if (Notification.permission === "granted") { // 許可 } else if (Notification.permission === "denied") { // 拒否 } </code></pre> <p>以上!!</p> <h3 id="【PR】これをつかって、こんなのつくりました!"><a href="#%E3%80%90PR%E3%80%91%E3%81%93%E3%82%8C%E3%82%92%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%A6%E3%80%81%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F%EF%BC%81">【PR】これをつかって、こんなのつくりました!</a></h3> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/2f71c56a-0ab2-5910-f203-e1fca191ad69.png" alt="スクリーンショット 2020-05-24 12.44.28.png" /></p> <p>こんな通知を受け取れます!<br /> <img width="359" alt="スクリーンショット_2020-05-24_13_34_17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/1d4037bf-e662-a15a-4567-91f845ef5142.png"></p> <p>1週間でWebサービスを作るイベント <a href="https://crieit.net/boards/web1week-202005">web1week</a>への投稿作品です!<br /> よかったら、遊んでみてください(<em>´ω`</em>)</p> <p>■エアで投げ銭できるWebサービス「エア銭」<br /> URL: <a target="_blank" rel="nofollow noopener" href="https://air-money.netlify.app/">https://air-money.netlify.app/</a></p> <h1 id="参考にしたサイトさま"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%95%E3%81%BE">参考にしたサイトさま</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developers.cyberagent.co.jp/blog/archives/9662/">FRESH! における Web プッシュ通知機能 〜実装編〜 | CyberAgent Developers Blog</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/firebase/quickstart-js/blob/bcce38ebc1e5602560e2b76b20f19b7834b8279e/messaging/firebase-messaging-sw.js#L15-L37">quickstart-js/firebase-messaging-sw.js at bcce38ebc1e5602560e2b76b20f19b7834b8279e · firebase/quickstart-js</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/reference/js/firebase.messaging.Messaging#deletetoken">Messaging | JavaScript SDK  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/send-multiple#%E3%83%88%E3%83%94%E3%83%83%E3%82%AF-http-post-%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">複数のデバイスにメッセージを送信する  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#WebpushConfig">REST Resource: projects.messages  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/send-message">アプリサーバーからの送信リクエストを作成する  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/first-message">バックグラウンド アプリにテスト メッセージを送信する  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/client">JavaScript Firebase Cloud Messaging クライアント アプリを設定する</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ryo_hisano/items/1171beca22d5a04ed802">Firebase Cloud Messagingで始めるWebプッシュ通知 - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15518 2019-10-30T21:18:16+09:00 2019-10-30T21:18:53+09:00 https://crieit.net/posts/Firestore-Nuxt-js Firestoreの簡易管理ツールをNuxt.jsでつくってみた <p>Firestore、とっても便利ですが、Firebaseのコンソールがイケてないので、<br /> ローカルで動かせる簡易の簡易ツールを作ってみました。Nuxt.js製です。</p> <p>Firebase Admin SDKを使ってるので、<strong>秘密鍵を配置すればOK</strong>。<br /> <strong>セキュリティルールの変更も不要</strong>です。</p> <p>動いているところはこんな感じ。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/e4d2f323-d2d7-39fb-e178-dc135f2e21f2.gif" width="680"/></p> <p>GitHubで公開してます。ただ書きなぐりなので、ソースはイケてないです。。<br /> まだα版くらいなので、機能は限定的です。。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/memory-lovers/simple-firestore-admin">memory-lovers/simple-firestore-admin: Simple Firestore Admin</a></p> <h3 id="なんで作ったか"><a href="#%E3%81%AA%E3%82%93%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8B">なんで作ったか</a></h3> <p>Firestoreを使った<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">Webサービス</a>を作ってみたところ、<br /> ちょこっとしたDBの変更にもデータが多かったり、1ドキュメントのサイズが大きいと、<br /> <strong>Firebaseコンソールだと重い感じに</strong>。。</p> <p>いろいろ見ていると、Webサービスなどもあるのですが、<br /> セキュリティールールを変えないといけないなど、設定がめんどくさいなと。。</p> <p>なので、 <strong>設定が少なくて、軽い管理ツールがほしい</strong>、と思い、作ってみました。</p> <h3 id="できること"><a href="#%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%93%E3%81%A8">できること</a></h3> <p>とりあえず、いま自分が必要な機能だけなので、最小な感じ。</p> <ol> <li>コレクションの検索: <code>collection().where().orderBy()</code></li> <li>ドキュメントの更新: <code>doc().update()</code></li> <li>ドキュメントの削除: <code>doc().delete()</code></li> </ol> <h3 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h3> <h4 id="1. インストール"><a href="#1.+%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">1. インストール</a></h4> <p>インストールは、GitHubからクローンしてください。</p> <pre><code class="console">$ git clone [email protected]:memory-lovers/simple-firestore-admin.git $ cd simple-firestore-admin </code></pre> <h4 id="2. セットアップ"><a href="#2.+%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97">2. セットアップ</a></h4> <h5 id="2-a. 秘密鍵の取得"><a href="#2-a.+%E7%A7%98%E5%AF%86%E9%8D%B5%E3%81%AE%E5%8F%96%E5%BE%97">2-a. 秘密鍵の取得</a></h5> <p>Firebaseコンソールから秘密鍵を生成して配置してください。</p> <p>左上の歯車から「プロジェクトの設定」をクリックし、</p> <p><img width="306" alt="スクリーンショット_2019-10-30_19_50_30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/8d3f5f8e-a641-3f6e-95e7-0889f86b9262.png"></p> <p>「サービス アカウント」タブの下にある、「新しい秘密鍵を生成」をクリックすると、<br /> 秘密鍵のjsonファイルをダウンロードできます。</p> <p><img width="680" alt="スクリーンショット_2019-10-30_19_50_20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/8297e57c-12e6-b63e-c46b-3fbd0b8af8a9.png"></p> <p>この例では、<code>./credential.json</code>としてます。</p> <h5 id="2-b. 設定ファイル(.env)の作成と設定"><a href="#2-b.+%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%28.env%29%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8%E8%A8%AD%E5%AE%9A">2-b. 設定ファイル(.env)の作成と設定</a></h5> <p><code>env_sample</code>という設定ファイルの雛形があるので、<code>.env</code>にコピーしてください。</p> <pre><code class="console">$ cp env_sample .env </code></pre> <p>コピーしたら、<code>.env</code>を開いて、配置した秘密鍵の相対パスを設定してください。</p> <pre><code class="diff"> # Copy this file with file name '.env' ## ex. CREDENTIAL_PATH=./credential.json - CREDENTIAL_PATH=YOUR_CREDENTIAL_PATH + CREDENTIAL_PATH=./credential.json </code></pre> <p>これで設定はOK。</p> <h4 id="3. 起動"><a href="#3.+%E8%B5%B7%E5%8B%95">3. 起動</a></h4> <p>Nuxt.js製なので、こんな感じでビルドと起動をしてもらえればOK。</p> <pre><code class="console"># パッケージのインストール $ npm install # ビルド $ npm run build # 起動 $ npm run start </code></pre> <p><code>http://localhost:3000</code>で起動するので、<br /> ブラウザで操作できるようになります。</p> <h3 id="開発時のポイント: serverMiddlewareでfirebase-admin"><a href="#%E9%96%8B%E7%99%BA%E6%99%82%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%3A+serverMiddleware%E3%81%A7firebase-admin">開発時のポイント: serverMiddlewareでfirebase-admin</a></h3> <p>開発時にハマったポイントとしては、<br /> firebase-adminがNode.js上でしか使えなかったことです。。</p> <p>当初はNuxtアプリから直接firebase-adminを呼ぼうとしていましたが、<br /> <strong>Node.js上でしか扱うことができない</strong>ことをあとから知り。。</p> <p>Expressなどからだと呼ぶことができるので、<br /> Nuxtの<a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/api/configuration-servermiddleware/#servermiddleware-%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3">serverMiddleware</a>を使って、<br /> <strong>APIサーバ的なものを内部で持たせるようにしてみました</strong>。</p> <p>serverMiddlewareの使い方はこんな感じ。</p> <h4 id="Express関連のコード"><a href="#Express%E9%96%A2%E9%80%A3%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89">Express関連のコード</a></h4> <p>簡易化してますが、こんな感じのコードを、<code>server/index.ts</code>に配置</p> <pre><code class="typescript">import { Request, Response } from "express"; import * as firestore from "./modules/firestore"; import bodyParser from "body-parser"; import express from "express"; const app = express(); // setup body-parser app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); // select app.post("/api/select", async (req: Request, res: Response) => { // firestore.collection("users").get()的な処理をする const result = await firestore.fetchSelect(req.body); res.send(result); }); export default app; </code></pre> <h4 id="nuxt.config.tsで設定"><a href="#nuxt.config.ts%E3%81%A7%E8%A8%AD%E5%AE%9A">nuxt.config.tsで設定</a></h4> <p>先ほど作成した<code>server/index.ts</code>をserverMiddlewareとして利用するように設定</p> <pre><code class="typescript">import { Configuration } from "@nuxt/types"; const config: Configuration = { serverMiddleware: ["~/server"], }; export default config; </code></pre> <p>別にサーバを建てると、開発時にCORS対策でaxiosでproxyしないといけなかったりしますが、<br /> serverMiddlewareを使うとそういった設定も不要に(<em>´ω`</em>)</p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>とりあえず、必要な部分だけを作っただけなので、<br /> バグもイケてないところもまだまだありますが、よかったら遊んでみてもらえれば。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/memory-lovers/simple-firestore-admin">memory-lovers/simple-firestore-admin: Simple Firestore Admin</a></p> <p>Firestoreの管理画面作りたいな。。と思ったら、<br /> こんな感じでserverMiddlewareを使うのが良さそうです(<em>´ω`</em>)</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>冒頭で話していた開発しているWebサービスです!<br /> だいぶデータも増えてきたので、こういう管理画面が欲しくなってきました。。</p> <p>積読用の読書管理アプリ 『積読ハウマッチ』<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイトさま"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%95%E3%81%BE">参考にしたサイトさま</a></h1> <ul> <li><a href="https://crieit.net/posts/Nuxt-Express">Nuxt+Expressのプロジェクト作成で良さそうなのは? - Crieit</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sauzar18/items/6255877457b29c5f7421">Nuxt.js+Express.jsにTypeScriptを導入してみた - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sauzar18/items/d68c13f200b3bc671679">Nuxt.js v2.9にTypeScriptとExpress.jsを対応してみた - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15506 2019-10-26T09:06:51+09:00 2019-10-26T09:06:51+09:00 https://crieit.net/posts/Nuxt-js-SPA-Firebase-Web-5db38e1b32bf6 Nuxt.js(SPA)+FirebaseのWebアプリで初マイグレーションをしてけど、いろいろ失敗した話。。 <p>これはただの失敗談です。。役に立つかわからないけど、誰かの参考になるといいな。。</p> <p>Nuxt.js+Firebaseで開発した<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">Webサービス</a>をリリースして2ヶ月目くらい。<br /> サービス止めて、初マイグレーションしてときの失敗談です。</p> <h2 id="サービスを止めてやりたかったこと"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E6%AD%A2%E3%82%81%E3%81%A6%E3%82%84%E3%82%8A%E3%81%9F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">サービスを止めてやりたかったこと</a></h2> <p>いままではちょこっとした変更が多かったため、<br /> サービスを止める必要なく、Nuxtアプリの更新だけで十分でしたが、<br /> 新しい機能を追加する際に、Firestoreのスキーマを変える必要が出てきました。</p> <p>やりたいことは、<br /> 「 <strong>今までのデータを読み取って、別の形式に再構成する</strong> 」<br /> という感じのことで、止めないとダメかなと。</p> <h2 id="メンテンナンスのときの手順"><a href="#%E3%83%A1%E3%83%B3%E3%83%86%E3%83%B3%E3%83%8A%E3%83%B3%E3%82%B9%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AE%E6%89%8B%E9%A0%86">メンテンナンスのときの手順</a></h2> <p>手順はこんな感じ。</p> <ol> <li>メンテナンス画面に遷移するようにmiddlewareを変更</li> <li>firestoreのルールを全部ブロックするように変更</li> <li>スキーマ変更処理を実行</li> <li>メンテナンス画面を解除し、hostingに最新版をアップロード</li> <li>firestoreのルールをもとに戻す</li> </ol> <p>ポイントとしては、</p> <ul> <li>ページを移動したら、<strong>middlewareでメンテナンス画面に遷移</strong>する</li> <li>クライアントから変更されないように、<strong>全部ブロックするルールに変更</strong>する</li> </ul> <p>の2点くらい。</p> <p>結果、かなりの失敗をしました。。</p> <h1 id="やらかした失敗と反省点"><a href="#%E3%82%84%E3%82%89%E3%81%8B%E3%81%97%E3%81%9F%E5%A4%B1%E6%95%97%E3%81%A8%E5%8F%8D%E7%9C%81%E7%82%B9">やらかした失敗と反省点</a></h1> <p>ダメだったのは以下の2点。</p> <ol> <li><strong>時間がかかりすぎ</strong>(予想5h以内→実際11h)</li> <li><strong>メンテナンス画面がちゃんと出ていなかった</strong></li> </ol> <h2 id="1. 時間がかかりすぎ問題"><a href="#1.+%E6%99%82%E9%96%93%E3%81%8C%E3%81%8B%E3%81%8B%E3%82%8A%E3%81%99%E3%81%8E%E5%95%8F%E9%A1%8C">1. 時間がかかりすぎ問題</a></h2> <p>一番の失敗は時間かかりすぎです。。<br /> 予想以上に時間が。。予想5h以内→実際11h</p> <p>対象のスキーマが、</p> <pre><code>本 - 履歴 - ユーザ </code></pre> <p>多対多の関係を持つ構造を持っていて、<br /> 本を起点に履歴データの構造を変更するような処理だった。</p> <p>規模としては、本の件数が47670ドキュメントで、履歴が58144ドキュメント。</p> <p>実際に実行してみたところ、速度が上がったり下がったりで、<br /> 1時間に5000冊位な感じ。。これだけで10時間近く。。</p> <h3 id="振り返ってみて"><a href="#%E6%8C%AF%E3%82%8A%E8%BF%94%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6">振り返ってみて</a></h3> <p>やってみていくつか良くない感じだったなと。。<br /> 振り返ってみて、改善できそうなのは以下の2点な感じ。。</p> <h4 id="a. メンテナンスでは差分部分のみ反映する"><a href="#a.+%E3%83%A1%E3%83%B3%E3%83%86%E3%83%8A%E3%83%B3%E3%82%B9%E3%81%A7%E3%81%AF%E5%B7%AE%E5%88%86%E9%83%A8%E5%88%86%E3%81%AE%E3%81%BF%E5%8F%8D%E6%98%A0%E3%81%99%E3%82%8B">a. メンテナンスでは差分部分のみ反映する</a></h4> <p>全量を読み込んで、反映していたので、かなりの時間が。。</p> <p>とはいえ、1日の変更であれば、そこまで多くないので、<br /> <strong>事前に全量の変更を用意して</strong>おき、メンテナンスにしてから、<br /> <strong>差分があるところだけを更新</strong>をするなどのほうが良かった気がしてる。。</p> <p>次はそうしよう。。</p> <h4 id="b. 処理の並列実行"><a href="#b.+%E5%87%A6%E7%90%86%E3%81%AE%E4%B8%A6%E5%88%97%E5%AE%9F%E8%A1%8C">b. 処理の並列実行</a></h4> <p>内部の処理も<code>Promise.all</code>を使ってない部分がいくつか。。</p> <p><code>foreach</code>で実行している部分を置き換えて、<br /> <strong>並列実行にするだけでも改善されそうな感じ</strong>。。</p> <p>この記事がわかりやすい感じだった。<br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/euxn23/items/b9bd8bf28fe3ca707fe3#%E4%B8%A6%E5%88%97%E3%81%A8%E7%9B%B4%E5%88%97%E3%81%AE%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91%E3%81%A8%E6%B3%A8%E6%84%8F%E7%82%B9">Node.js で Promise の直列実行と並列実行、同時実行数の制御 - Qiita</a></p> <h3 id="2. メンテナンス画面が出てない"><a href="#2.+%E3%83%A1%E3%83%B3%E3%83%86%E3%83%8A%E3%83%B3%E3%82%B9%E7%94%BB%E9%9D%A2%E3%81%8C%E5%87%BA%E3%81%A6%E3%81%AA%E3%81%84">2. メンテナンス画面が出てない</a></h3> <p>メンテナンスをはじめてから、GoogleAnalyticsを見てると、リアルタイムユーザがちらほら。<br /> 終わってからも、メンテナンス画面じゃないとの報告もちらほら。。</p> <p>Firebase Hostingにアップロードしてもキャッシュが残っているとだめだったもよう。。</p> <h3 id="振り返ってみて"><a href="#%E6%8C%AF%E3%82%8A%E8%BF%94%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6">振り返ってみて</a></h3> <p>振り返ってみて、改善できそうなのは以下の2点な感じ。。</p> <h4 id="a. PWAモジュールを使ってnetworkファーストで取得する"><a href="#a.+PWA%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6network%E3%83%95%E3%82%A1%E3%83%BC%E3%82%B9%E3%83%88%E3%81%A7%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">a. PWAモジュールを使ってnetworkファーストで取得する</a></h4> <p>以下の記事の前半で書かれている通り、<strong>キャッシュ戦略を設定することで解決</strong>できそう。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://qiita.com/hecateball/items/29f726430d204c964f04">Nuxt.js(SPA)とFirebaseで強制リビジョン(バージョン)アップするならPWAモジュールを使おう - Qiita</a></p> <h4 id="b. Remote Configを使って切り替える"><a href="#b.+Remote+Config%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%88%87%E3%82%8A%E6%9B%BF%E3%81%88%E3%82%8B">b. Remote Configを使って切り替える</a></h4> <p>同じく上記の記事で書かれている通り、<strong>Remote Configを使う</strong>のが良さそう。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/remote-config">Firebase Remote Config  |  Firebase</a></p> <p><strong>Remote Configの値によって、メンテナンス画面の表示を切り替えれるようにしておけば</strong>、<br /> Firebaseのコンソール上での変更で、切り替えれるようにできる感じ。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>やってみてわかることも多いですが、なかなかつらい感じ。。</p> <p>Firebase/Firestore関連の運用系の話はあまり見ないので、<br /> とりあえず書いてみましたが、誰かの役に立てば。。</p> <p>こんな方法もあるよ!こっちのほうがいいよ!などあれば、<br /> コメントもらえるとうれしいです(<em>´ω`</em>)</p> <h2 id="ほかの失敗談"><a href="#%E3%81%BB%E3%81%8B%E3%81%AE%E5%A4%B1%E6%95%97%E8%AB%87">ほかの失敗談</a></h2> <p>以前にもこんなのを書きました。<br /> 初級向けの話が多いですが、実際にやってみての体験談的な話。<br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/8c1d1240c3aa200cbec0">Nuxt.js(SPA)+Firebaseで積読用の読書管理サービスを作ってみたときにハマったこと... - Qiita</a><br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/ef7dd47519403cd9bcf2">Firebaseで作ったWebサービスを3ヶ月運用してみて、ハマったこと・知っておきたかったこと - Qiita</a></p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>やらかしたWebサービスはこちら。。<br /> 積読用の読書管理アプリ 『積読ハウマッチ』<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてください〜</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15474 2019-10-11T10:59:34+09:00 2019-10-11T10:59:34+09:00 https://crieit.net/posts/Firebase-Web-3 Firebaseで作ったWebサービスを3ヶ月運用してみて、ハマったこと・知っておきたかったこと <p>Nuxt.jsとFirebaseで作っていた<a target="_blank" rel="nofollow noopener" href="https://tsudoku.site">Webサービス</a>を7月末にリリースして、はや3ヶ月。。<br /> RDB脳なのでFirebaseを使った開発でいろいろとハマった。。そのポイントを整理してみました。</p> <p>Firebaseをはじめようとしている人の一助になれば。</p> <h2 id="Nuxt.js(SPA)+Firebaseで作っています!"><a href="#Nuxt.js%28SPA%29%2BFirebase%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%EF%BC%81">Nuxt.js(SPA)+Firebaseで作っています!</a></h2> <p>以前、以下のような記事を書いたのですが、そのFirebase関連ぽいまとめです。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/8c1d1240c3aa200cbec0">Nuxt.js(SPA)+Firebaseで積読用の読書管理サービスを作ってみたときにハマったこと... - Qiita</a></p> <p>前回同様、内容的にはドキュメントをよく読めば書いてあることばかりですが、<br /> 実際に運用したり、機能追加したりする時に、気づくので、手戻りが多く...<br /> あらかじめ、知っていたら良かったなと思う点をまとめています。</p> <p>Firebaseはとてもよいですが、RDBに慣れ親しんでいると、<br /> 思わぬところでハマったり、運用してみて初めて分かることが多い感じに。。</p> <p>ハマったことが多いですが、<strong>かなりコストを抑えて作れていてすごい( ゚д゚)!</strong><br /> というのが、Firebaseを使ってみた感想です♪</p> <h2 id="全体の構成はこんな感じ"><a href="#%E5%85%A8%E4%BD%93%E3%81%AE%E6%A7%8B%E6%88%90%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98">全体の構成はこんな感じ</a></h2> <p><img width="845" alt="スクリーンショット 2019-08-13 23.06.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/3ab45cc9-0a03-5a5b-8297-a6f5af1e9c0d.png"></p> <ul> <li>SPAモードのNuxtアプリをFirebase Hositingにデプロイ</li> <li>データベースはFirestore</li> <li>OGP生成のためにCloud Function for Firebase+ImageMagic</li> <li>OGP用のHTML生成でCloud Function for Firebase</li> <li>外部APIを利用したいのでZEIT NOW経由でのAPIを用意</li> </ul> <h2 id="わたしはここでハマりました。。"><a href="#%E3%82%8F%E3%81%9F%E3%81%97%E3%81%AF%E3%81%93%E3%81%93%E3%81%A7%E3%83%8F%E3%83%9E%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82%E3%80%82">わたしはここでハマりました。。</a></h2> <h3 id="FirestoreでORがつかえない"><a href="#Firestore%E3%81%A7OR%E3%81%8C%E3%81%A4%E3%81%8B%E3%81%88%E3%81%AA%E3%81%84">FirestoreでORがつかえない</a></h3> <p>DBを扱っているとORが使いたくなりますが、Firestoreでは使えません。。<br /> 公式ドキュメントの<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/query-data/queries?hl=ja">ここ</a>に書いてある制約事項。。</p> <blockquote> <p>論理 OR クエリ。この場合は、OR 条件ごとに独立したクエリを作成し、アプリでクエリ結果を結合する必要があります。</p> </blockquote> <p><img width="845" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/bab57b35-7a52-0d38-56b1-ca69d726b440.png"/></p> <p>シンプルなクエリだったら複数回呼べばよいだけなので、あまり問題ないのですが、<br /> orderByやlimitと一緒に使うと困ります。。</p> <p>開発している<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>だとこの部分。</p> <p><img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/6f8885d4-6cab-1c29-aa66-d6bd0172ae8e.png" /></p> <ul> <li>読書中→積読の順序で表示して、</li> <li>各ステータスは更新日時の降順で表示し、</li> <li>30件ずつで表示する</li> </ul> <p>という感じの処理をしています。</p> <p>ORが使えないので、</p> <ul> <li>読書中を検索</li> <li>結果が30件未満だったら、残り分、積読を検索</li> </ul> <p>という感じでやっています。</p> <h3 id="Firestoreで別フィールドでの範囲指定+orderByが使えない"><a href="#Firestore%E3%81%A7%E5%88%A5%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E3%81%A7%E3%81%AE%E7%AF%84%E5%9B%B2%E6%8C%87%E5%AE%9A%2BorderBy%E3%81%8C%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84">Firestoreで別フィールドでの範囲指定+orderByが使えない</a></h3> <p>これもFirestoreの制約事項。。</p> <blockquote> <p>複数のフィールドに範囲フィルタを適用するクエリ(前のセクションの説明を参照)。</p> </blockquote> <p><img width="845" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/bab57b35-7a52-0d38-56b1-ca69d726b440.png"/></p> <p><img width="845" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/b9bd6977-5bf9-a4b6-547d-a88ebf2ebb1c.png" /></p> <p>公式ドキュメントだとこのあたり。<br /> 複数のorderByとかは使えるけど、where+orderByだとダメ。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/query-data/queries#compound_queries">Cloud Firestore で単純なクエリと複合クエリを実行する  |  Firebase</a><br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja">Cloud Firestore でのデータの並べ替えと制限  |  Firebase</a></p> <p>たとえば、<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>だと</p> <ul> <li>持っている人が10人以上の本を、</li> <li>更新日時の降順で並び替える</li> </ul> <p>とかができない。。</p> <p>実際にやる場合には、</p> <ul> <li>あらかじめ、持っている人が10人以上の本を別コレクションとして作成しておいて、</li> <li>そのコレクションに対して、更新日時の降順で並び替える</li> </ul> <p>という形で対応する必要がある。。</p> <p>開発当初・リリース前では、あまり気にならない制約事項だったけど、<br /> リリース後の機能追加などで制約に引っかかるように。。</p> <h3>ドキュメントフィールドの削除は<code>FieldValue.delete()</code></h3> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/delete-data?hl=ja">公式ドキュメント</a>にちゃんと書いてあるけど、<br /> ドキュメントフィールドを削除したいときは、<code>FieldValue.delete()</code>を使わないといけない。。</p> <p>雰囲気で使い始めたときは、<code>null</code>をセットすると削除できると思ってて、<br /> そのまま<code>null</code>がセットされてた(<em>´ω`</em>)</p> <h3>更新するときは、<code>update</code>と<code>set({merge: true})</code>がある</h3> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja">公式ドキュメント</a>をよく読むと書いてあるけど、更新する方法は2種類ある。</p> <ol> <li><code>update</code>は、ドキュメント全体を置き換える</li> <li><code>set({merge: true})</code>は、一部のドキュメントフィールドを置き換える</li> </ol> <p><code>set({merge: true})</code>については、すこし解釈が違うけどこんなイメージ。</p> <p>公式ドキュメントの説明では、こんな感じ。</p> <blockquote> <p>ドキュメントが存在するかどうかわからない場合は、新しいデータを既存のドキュメントに結合するオプションを渡し、ドキュメント全体が上書きされないようにします。</p> </blockquote> <p>たとえば、こんなドキュメントがあって、</p> <pre><code class="json">{ name: "Alice", age: 20 } </code></pre> <p>年齢を30歳に変えたいなと思った場合、こんな感じ。</p> <pre><code class="javascript">const docRef = db.collection("user").doc("Alice"); // updateの場合、すべてのドキュメントフィールドを渡さないといけない await docRef.update({ name: "Alice", age: 30 }) // set({merge: true})の場合、変更したいドキュメントフィールドだけ渡せばOK await docRef.set({ age: 30 }, { merge: true }) </code></pre> <p>間違えて、updateで変更したいドキュメントフィールドだけにすると。。</p> <pre><code class="javascript">const docRef = db.collection("user").doc("Alice"); // updateの場合、すべてのドキュメントフィールドを渡さないといけない await docRef.update({ age: 30 }) // => // { // age: 30 // } </code></pre> <p>こんな感じで、<code>name: "Alice"</code>が消えてしまいます。。</p> <p>その名の通り、マージなので、新しいドキュメントフィールドを追加したいときにも便利(<em>´ω`</em>)</p> <pre><code class="javascript">const docRef = db.collection("user").doc("Alice"); await docRef.set({ bookNum: 0 }, { merge: true }) // => // { // name: "Alice", // age: 30, // bookNum: 0 // } </code></pre> <p>マイグレーションするときは、これを使って、フィールドを追加したりしている(<em>´ω`</em>)</p> <h3 id="トランザクション内では最大500件まで"><a href="#%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%86%85%E3%81%A7%E3%81%AF%E6%9C%80%E5%A4%A7500%E4%BB%B6%E3%81%BE%E3%81%A7">トランザクション内では最大500件まで</a></h3> <p>公式ドキュメントだとこのあたり。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/transactions?hl=ja">トランザクションと一括書き込み  |  Firebase</a></p> <p>制約がいくつか書かれているけど、以下のな感じ。</p> <ol> <li>トランザクション内では、<strong>書き込みよりも読み込みを先にしないといけない</strong></li> <li>トランザクションが<strong>最大リクエストサイズの10MiBまで</strong></li> <li>1度でできる書き込みは<strong>最大500のドキュメントまで</strong></li> </ol> <p>特に気になるのが、<strong>書き込みは最大500のドキュメント</strong>の部分。</p> <p>何千件も更新しないといけない場合に、問題が発生してきます。。</p> <p>5000件あると、10個のトランザクションに分ける必要がありますが、<br /> たとえば、1000件目で失敗した場合、最初の500件のみ実行される状態でエラーに。。</p> <p>そうなった場合、<strong>どこでエラーが起きたかを記録する仕組みがないと大変なことに</strong>。。</p> <p><a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>では、ログなど既存の情報から復元することで対応可能でしたが、<br /> <strong>エラーが発生した場合、そのログを残しておくなどの仕組み</strong>があるとよいなと。。</p> <p>バグがないように作るのは大事なのですが、バグが起こったときに、<br /> リカバリできる情報や手段を用意しておくのも同じく大事。。</p> <h3 id="Firestoreトリガーは不整合を起こしやすい"><a href="#Firestore%E3%83%88%E3%83%AA%E3%82%AC%E3%83%BC%E3%81%AF%E4%B8%8D%E6%95%B4%E5%90%88%E3%82%92%E8%B5%B7%E3%81%93%E3%81%97%E3%82%84%E3%81%99%E3%81%84">Firestoreトリガーは不整合を起こしやすい</a></h3> <p>Firestoreで変更があったら、Cloud Functionsを呼ぶCloud Firestoreトリガー。<br /> 特定パスのCRUDに応じて、処理を組み込めるので便利。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/functions/firestore-events?hl=ja">Cloud Firestore トリガー  |  Firebase</a></p> <p>ただ、トランザクションと同じ感じで、<strong>トランザクションが分断されるので要注意</strong>。。</p> <p>クライアント側でトランザクションを使っていたとしても、<br /> トリガーで起動するCloud Firestoreはトランザクションに含まれません。。</p> <p>そのため、クライアント側で正常終了したと思っても、<br /> Cloud Firestore側でエラーが起こると、全体として不整合が起こった状態になります。。</p> <p>Firestoreトリガーを使うときは、<br /> <strong>トランザクションを分けてもよい処理なのか</strong>をよく検討する必要があります。。</p> <h3 id="Firestoreのスキーマ変更が大変"><a href="#Firestore%E3%81%AE%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E%E5%A4%89%E6%9B%B4%E3%81%8C%E5%A4%A7%E5%A4%89">Firestoreのスキーマ変更が大変</a></h3> <p>Firestoreはドキュメント型のNoSQLで、NoSQLはクエリ志向のDBです。<br /> そのため、クエリが増える・変わるとスキーマを変更する必要が出てきます。</p> <p>たとえば、「〇〇の総数」みたいなのはドキュメントフィールドとして値を持たせる必要があり、<br /> あとでほしいと思った時に、マイグレーションが必要になってきます。。</p> <p>Firestoreには<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/solutions/counters?hl=ja">分散カウンタ</a>という便利なものがありますが、</p> <blockquote> <p>次のコードは、分散カウンタを初期化します。<br /> と記載されているとおり、値が0のドキュメントフィールドが必要だったり。。</p> </blockquote> <p>なので、ローカルPCでfirebase-adminを実行して、<br /> Firestoreのデータを変更する仕組みを用意しておく必要があります。</p> <p>ローカルPCでfirebase-adminを実行するときの話はこちらの記事に。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/07/25/073000">ローカルPCからfirebase-adminを使ってFirestoreを操作する(管理ツール)</a><br /> ・<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/e4d4846f394d36e29776">ローカルPCでfirebase-adminを使って一括削除/一括更新する - Qiita</a></p> <p>ただ、ドキュメント数が1万とかだと、<br /> <strong>あっという間に無料枠を使い切るので、頻繁な変更は要注意</strong>。。</p> <p>(とはいえ、1日に2万件の読み込みが無料だったり、)<br /> (10万件ごとに$0.18/20円くらいなので100万件でも200円くらい。)<br /> (RDBで100万レコードを扱うよりも、安い気がしてきている)</p> <h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2> <p>わりとネガティブな内容になっている気もしますが、<br /> 制約を制約として認識していれば、問題ない話ばかりだと思ってます。</p> <p>大変だったのは手探りでやっていたため、後で知ることになったため。。<br /> 設計とかする前に知っておきたかったまとめです。。</p> <p>個人的にはFirebaseはとてもよく、<br /> <strong>かなりコストを抑えてサービスを公開できてすごい( ゚д゚)!</strong><br /> というのが、Firebaseを使ってみた感想です♪</p> <p>気をつけることも多いですが、慣れれば大丈夫(<em>´ω`</em>)<br /> とはいえ、プロダクトによっては合わないこともあるので、なにかの参考になれば!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ<br /> 機能追加が増えると知見も貯まるので、貯まったらまた記事を書こうと思います♪</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15451 2019-10-04T17:10:28+09:00 2019-10-04T17:13:27+09:00 https://crieit.net/posts/Nuxt-js-Firebase-GAE-2-PV Nuxt.js+Firebase+GAEで作った個人サービスが半月で2万PV超えたので実績値を全て公開する <blockquote> <p>Qiitaへ<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kinmi/items/ce39b52ff712098431c4">投稿した記事</a>のクロス投稿になります。</p> </blockquote> <h1 id="こんなサービス作りました"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E4%BD%9C%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F">こんなサービス作りました</a></h1> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【<a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/%E6%8B%A1%E6%95%A3%E5%B8%8C%E6%9C%9B?src=hash&ref_src=twsrc%5Etfw">#拡散希望</a>】🙌🎉🎊サービス開始🎊🎉🙌ボケをツイートして「いいね❤️」「リツイート🔁」の数でランキング!Twitter連動型 大喜利サイト「ついぎり」サービス開始しました‼️<a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/%E3%81%A4%E3%81%84%E3%81%8E%E3%82%8A?src=hash&ref_src=twsrc%5Etfw">#ついぎり</a><a target="_blank" rel="nofollow noopener" href="https://t.co/bkXfzHyVSs">https://t.co/bkXfzHyVSs</a></p>— ついぎり@公式アカウント (@twigiri_app) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/twigiri_app/status/1161589000223641600?ref_src=twsrc%5Etfw">August 14, 2019</a></blockquote> <p>Twitterで大喜利するサービスです。<br /> 8月中頃にローンチしたのですが、有難いことに半月で約2.5万PVいきました。</p> <p>開発に至ったポエム記事はCrieitに投稿しています。<br /> <a href="https://crieit.net/posts/19b75796b5c032c728ebab48ae064af4">なぜ大喜利サービスを作ったのか</a></p> <h1 id="この記事について"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">この記事について</a></h1> <p>やっぱり公開直後は怖かったです。</p> <p>そう、<strong>クラウド破産</strong>。<br /> <a target="_blank" rel="nofollow noopener" href="https://gigazine.net/news/20180803-spent-much-money-in-firebase/">Firebaseの設定を間違えて72時間で300万円以上請求されてしまったウェブサービス</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/itkr/items/745d54c781badc148bb9">BigQueryで150万円溶かした人の顔</a></p> <p>上記以外にもネット上にはインパクトのある高額請求の記事がたくさんあり、<br /> 内容を確認すれば「なんだそんな事か」となるのですが、<br /> 実績値というのはセキュリティやNDAの関係からか、なかなか出回ってません。</p> <p><strong>個人開発ならそんなの関係ねぇ</strong></p> <p>って事で全て公開します。<br /> 使い方によって大きく変わる所かと思いますが、何かの参考(個人開発の後押し)になれば幸いです。</p> <h1 id="環境構成"><a href="#%E7%92%B0%E5%A2%83%E6%A7%8B%E6%88%90">環境構成</a></h1> <ul> <li>Nuxt v2.8.1 <ul> <li>SSRモード</li> </ul></li> <li>GAE <ul> <li>standard</li> <li>runtime: nodejs10</li> <li>instance_class: F1</li> </ul></li> <li>Firestore <ul> <li>改めて別記事書こうと思いますが一般的な構成だと思います</li> </ul></li> <li>Cloud Functions for Firebase <ul> <li>engine: node8</li> <li>ユーザー登録/脱会/ツイート/いいねRT集計/バックアップ で使用</li> </ul></li> <li>Cloud Storage for Firebase <ul> <li>OGP画像の保存に使用</li> </ul></li> <li>Regionは全てasia-northeast1</li> </ul> <h1 id="使用量"><a href="#%E4%BD%BF%E7%94%A8%E9%87%8F">使用量</a></h1> <h2 id="PV数"><a href="#PV%E6%95%B0">PV数</a></h2> <p><img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F127547%2F24881933-651c-4c96-824c-39736e729605.png?ixlib=rb-1.2.2&auto=compress%2Cformat&gif-q=60&s=6fb0cd97a92b355d9c652e4ab302b671" alt="PV数" /><br /> トータルで約2.5万PV。<br /> デイリーだとローンチ直後の約5,500PVがMAXです。</p> <h2 id="割り当て"><a href="#%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6">割り当て</a></h2> <p><img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F127547%2Fb76116ed-9fff-e787-4c78-bee5045b0550.png?ixlib=rb-1.2.2&auto=compress%2Cformat&gif-q=60&s=aa7c74d1f29cd701cbaaf4fd11b3492f" alt="割り当て" /><br /> アクセス数に比例した増加は見られませんでした。</p> <p><code>GCP Storage egress between NA and APAC</code><br /> 使用量: 3.59GB<br /> 一番費用が発生してるこれ、よく分かってません・・・<br /> GAEのデプロイにかかる項目なのかな?と思ってます。</p> <p><code>Cloud Firestore Entity Writes Japan</code><br /> <code>Cloud Firestore Read Ops Japan</code><br /> Write: 480,077<br /> Read: 606,253<br /> Firestoreは<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/quotas?hl=ja#free-quota">1日単位で無料枠がリセットされる</a>のですが、リリース直後はちょっと無料枠から足出た程度ですね。<br /> 21日辺りは障害等でデータいじってた・・・気がします。</p> <h1 id="諸経費"><a href="#%E8%AB%B8%E7%B5%8C%E8%B2%BB">諸経費</a></h1> <h2 id="サーバー代"><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E4%BB%A3">サーバー代</a></h2> <p><img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F127547%2F79b2afae-5344-fea9-34f5-2f1b7e7e7b7f.png?ixlib=rb-1.2.2&auto=compress%2Cformat&gif-q=60&s=5a86fdff390d11dcb2a1529bde970ce9" alt="サーバー代" /><br /> 0円でした。<br /> 使用量として57円かかってるのですが、<br /> GCPには1年間有効な$300のトライアルクレジットがあるので、そこに収まってます。<br /> クレジットを$0.5消費しました。使い切れる自信がありません。</p> <h2 id="ドメイン代"><a href="#%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E4%BB%A3">ドメイン代</a></h2> <p><img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F127547%2F47080e38-4045-f28d-c4d8-e55661e5af2d.png?ixlib=rb-1.2.2&auto=compress%2Cformat&gif-q=60&s=4106c6c479ee293fc185f0b086063bc9" alt="ドメイン代" /><br /> 1,600円です。<br /> <a target="_blank" rel="nofollow noopener" href="https://domains.google.com/">Google Domains</a>でappドメインを取りました。<br /> appドメインはSSL証明書無しでは使えないという安全なドメインです。<br /> GAEの場合、SSL証明は<a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/appengine/docs/standard/nodejs/securing-custom-domains-with-ssl?hl=ja">デフォルトで有効&無料</a>です。</p> <h1 id="収益"><a href="#%E5%8F%8E%E7%9B%8A">収益</a></h1> <p>AdSense広告を導入しています。<br /> キャプチャは載せて良いのか分からなかったので・・・<br /> 半月で約1,300円でした。</p> <h1 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h1> <h2 id="環境構成について"><a href="#%E7%92%B0%E5%A2%83%E6%A7%8B%E6%88%90%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">環境構成について</a></h2> <p>一番びっくりしたのはNuxt.jsがF1インスタンスで落ちずに稼働していることです。<br /> 公式ドキュメントでも<a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/faq/appengine-deployment/">F2が推奨されてる</a>のですが、F2では無料枠に収めることが難しいです。<br /> バックエンド処理をFirebaseに任せてるおかげで負荷が少ない構成になってるのかなーと思います。</p> <h2 id="費用について"><a href="#%E8%B2%BB%E7%94%A8%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">費用について</a></h2> <p>正直「バズった」と言える数値ではないと思ってるので「バズっても大丈夫」とは言えないです・・・<br /> 座布団数(ボケツイートのいいね/RT総数)も、現在のランキング1位は3桁です。<br /> 「バズっても大丈夫」と分かったら改めて記事書くので、誰かバズってください。</p> <p>しかし、まぁバズが発生してない個人開発としてはそれなりのアクセス数だと思ってます。<br /> それで数十円なので「そんなに怖くないよ!」とは言えるかと。</p> <h2 id="安心して使うために"><a href="#%E5%AE%89%E5%BF%83%E3%81%97%E3%81%A6%E4%BD%BF%E3%81%86%E3%81%9F%E3%82%81%E3%81%AB">安心して使うために</a></h2> <ul> <li>GCPの課金アラートを設定する</li> <li>Rulesはちゃんと書く</li> </ul> <p>は最低限やっとくべきです。<br /> ただ、Rulesはちゃんと書くと一般的なバックエンド処理と同レベルの規模になると思います。<br /> 最低限の尺度が難しいですが、認証/取得件数/タイムスタンプ比較辺りは最低でもやっておくべきです。</p> <h2 id="マネタイズについて"><a href="#%E3%83%9E%E3%83%8D%E3%82%BF%E3%82%A4%E3%82%BA%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">マネタイズについて</a></h2> <p>AdSenseに頼ったサービスの運営は凄く難しいという体験をしました。<br /> PV落ちたら施策を打たないといけないので放置出来ないし、<br /> 収益率はAdSenseの匙加減1つで大きくブレそうです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kinmi/items/c66aa98718acad84621b">前作</a>ではマネタイズしてなく、正直「もしバズったらお金かかるよな・・・」とアクセス数に対してネガティブになってました。<br /> その経験を踏まえ、広告を貼りました。<br /> 結果、素直に「バズれ!」と思えるようになったので、それだけでも導入した価値はあったかと思います。</p> <h1 id="おわり"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A">おわり</a></h1> <p>今後、この個人開発で得た知見で記事書こうと思ってます。</p> <ul> <li>TwitterAPIがBANされた話</li> <li>Firestoreの構成について</li> <li>文字入れ画像を作るソースコード</li> <li>オレオレアトミックデザイン</li> </ul> <p>他に「ここ知りたい」等あれば公開しますし、ボリュームある内容であれば記事書きます!</p> <p>ただ、一番書きたいのは「バズっても大丈夫だった件」です。</p> <p><a target="_blank" rel="nofollow noopener" href="https://twigiri.app/">Twitter連動型 大喜利サイト「ついぎり」</a><br /> <img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F127547%2F61391e21-cd2f-0ca3-734e-f96beee4a3b5.png?ixlib=rb-1.2.2&auto=compress%2Cformat&gif-q=60&s=1d4d37fe422a9c228abb233de8b07382" alt="ついぎり" /><br /> 面白い投稿お待ちしてます😋</p> きんみ tag:crieit.net,2005:PublicArticle/15426 2019-09-26T19:11:16+09:00 2019-09-26T20:05:46+09:00 https://crieit.net/posts/firebase-admin 複数のプロジェクトのfirebase-adminを起動して、開発用に本番データの一部を移行する <p>開発している<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">Webサービス</a>で、本番データの一部を開発用に使いたいと思ったときの備忘録。<br /> firebase-adminは複数のプロジェクトで使えるらしい。</p> <p>開発用だけじゃなく、UIDを変更しながらのデータ移行などにも使えそう。</p> <h2 id="ローカル環境でfirebase-adminを使うための準備"><a href="#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E7%92%B0%E5%A2%83%E3%81%A7firebase-admin%E3%82%92%E4%BD%BF%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE%E6%BA%96%E5%82%99">ローカル環境でfirebase-adminを使うための準備</a></h2> <p>準備の部分は、以前まとめたこちらを参照ください〜<br /> - <a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/07/25/073000">ローカルPCからfirebase-adminを使ってFirestoreを操作する(管理ツール) - くらげになりたい。</a></p> <h3 id="ソースはこんな感じ。"><a href="#%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98%E3%80%82">ソースはこんな感じ。</a></h3> <pre><code class="javascript">const admin = require("firebase-admin"); // *** 移行元のプロジェクトの設定 const srcSA = require("./key/XXXXX.json"); // サービスアカウントの秘密鍵を取得 // firebase-adminの初期化 admin.initializeApp({ credential: admin.credential.cert(srcSA) }); const srcDB = admin.firestore(); // firestoreのインスタンスを取得 // ** 移行先のプロジェクトの設定 const destSA = require("./key/XXXXX.json"); // サービスアカウントの秘密鍵を取得 // ※同じadminを使って、別のAppを作成しないといけない※ // ※2つ目を初期化する場合は、"dest"など名前をつけないといけない※ const destAdmin = admin.initializeApp( { credential: admin.credential.cert(destSA) }, "dest"); const destDB = destAdmin.firestore(); // firestoreのインスタンスを取得 // **************************** // * MAIN // **************************** async function main() { console.log(`***** START MAIN`); // 移行元からデータを取得 const snaps = await srcDB.collection("data").get() const srcData = snaps.docs; for (let v of data) { try { // 移行先からデータを保存 const destDataRef = destDB.collection("data").doc(v.id); await destDataRef.set(v.data()); } catch (error) { console.errror(`Error: ${error}`, error); } } console.log(`***** END MAIN`); process.exit(0); } main().then(); </code></pre> <p>雛形はこんな感じ。</p> <p>以前書いた以下の記事だと丸々移行するので、UIDが変わるとめんどくさいことになる。。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/ef80b07347cfdd37f116">Firestoreのデータをgcloudを使ってバックアップ&別のプロジェクトへインポートしてみる - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/98283e837f30209f23c8">Firestoreのデータを分析するための3つの方法とその注意点 - Qiita</a></li> </ul> <p>この方法であれば、</p> <ul> <li>whereで一部のだけに絞り込んだり、</li> <li><code>set()</code>するときに、UIDを差し替えたり</li> </ul> <p>できるので、開発用に使うにはよいかんじ。</p> <h5 id="注意1: 2つ目を初期化する場合は別の名前に"><a href="#%E6%B3%A8%E6%84%8F1%3A+2%E3%81%A4%E7%9B%AE%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E5%88%A5%E3%81%AE%E5%90%8D%E5%89%8D%E3%81%AB">注意1: 2つ目を初期化する場合は別の名前に</a></h5> <p>そのまんまコピペで2つのfirebase-adminを作るとエラーに。。<br /> 2つ目を初期化する場合は、"dest"など名前をつけないといけないいけないらしい。。</p> <p>公式ドキュメントの「<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/admin/setup#initialize_multiple_apps">複数のアプリを初期化する</a>」に書いてあった。</p> <p>これで、2つのfirebase-adminが起動できるので、データ移行などもできそう(<em>´ω`</em>)</p> <h5 id="注意2: メモリを大量に使いそうな場合はオプションを指定"><a href="#%E6%B3%A8%E6%84%8F2%3A+%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%92%E5%A4%A7%E9%87%8F%E3%81%AB%E4%BD%BF%E3%81%84%E3%81%9D%E3%81%86%E3%81%AA%E5%A0%B4%E5%90%88%E3%81%AF%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%8C%87%E5%AE%9A">注意2: メモリを大量に使いそうな場合はオプションを指定</a></h5> <p>データが多く、メモリを大量に使いそうな場合は、<br /> <code>--max_old_space_size</code>オプションを指定しないといけない。。</p> <p>途中で失敗すると(読み込み/書き込み件数的に)悲しいことになるので、<br /> 大きめのサイズを指定しておくと良さそう。</p> <pre><code class="console">node --max_old_space_size=8192 index.js </code></pre> <h5 id="注意3: 件数が多い場合は、limitを指定して再帰処理"><a href="#%E6%B3%A8%E6%84%8F3%3A+%E4%BB%B6%E6%95%B0%E3%81%8C%E5%A4%9A%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81limit%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%A6%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86">注意3: 件数が多い場合は、limitを指定して再帰処理</a></h5> <p>大量のデータを一度に取得しようとすると、</p> <blockquote> <p>Deadline Exceeded error</p> </blockquote> <p>が出て怒られるので、少しずつ実行するのがよいかんじ。</p> <pre><code class="javascript">const BATCH_SIZE = 300 async function moveData(lastItem) { // 移行元からデータを取得 const colRef = srcDB.collection("data"); let query = colRef.orderBy("createAt", "asc"); if (lastItem != null) query = query.startAfter(lastItem.createAt); const snap = await query.limit(BATCH_SIZE).get(); if (snap.docs.length === 0) return; // 0件なら終了する const srcData = snaps.docs; for (let v of data) { try { // 移行先からデータを保存 const destDataRef = destDB.collection("data").doc(v.id); await destDataRef.set(v.data()); } catch (error) { console.errror(`Error: ${error}`, error); } } // 最後の要素を渡して、そこから継続する await moveData(data[data.length - 1]); } // **************************** // * MAIN // **************************** async function main() { console.log(`***** START MAIN`); await moveData(null); console.log(`***** END MAIN`); process.exit(0); } </code></pre> <h5 id="注意4: 複数の場所に反映する場合は、batchやtransactionを使う"><a href="#%E6%B3%A8%E6%84%8F4%3A+%E8%A4%87%E6%95%B0%E3%81%AE%E5%A0%B4%E6%89%80%E3%81%AB%E5%8F%8D%E6%98%A0%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81batch%E3%82%84transaction%E3%82%92%E4%BD%BF%E3%81%86">注意4: 複数の場所に反映する場合は、batchやtransactionを使う</a></h5> <p>途中でエラーになる場合もあるので、batchやtransactionを使うとよさそう。</p> <p>公式ドキュメントだと、このあたり<br /> - <a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/transactions">トランザクションと一括書き込み  |  Firebase</a></p> <pre><code class="javascript"> for (let v of data) { try { const batch = destDB.batch(); // 移行先からデータを保存 const destDataRef = destDB.collection("data").doc(v.id); batch.set(destDataRef, v.data()); // 移行先からデータを保存: その2 const destData2Ref = destDB.collection("data2").doc(v.id); batch.set(destData2Ref, v.data()); // コミット await batch.commit(); } catch (error) { console.errror(`Error: ${error}`, error); } } </code></pre> <p>ループ外で使うほうのもいい感じ。</p> <p>ただ、一括で書き込めるドキュメント数は500までと制限があるので、<br /> 一度に書き込む範囲は適宜調整が必要。</p> <blockquote> <p>1 回のトランザクションまたは一括書き込みでは、<br /> 最大500のドキュメントに書き込みを行うことができます。<br /> <a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/transactions?hl=ja#security_rules_limits">トランザクションと一括書き込み  |  Firebase</a></p> </blockquote> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>firebase-adminは複数のプロジェクトでも扱える。<br /> 部分的なデータ移行や一部を書き換えながらの移行もできる(<em>´ω`</em>)</p> <p>ただ、件数が多かったり、複数の書き込みがある場合は、いろいろ必要。。</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15422 2019-09-24T20:45:37+09:00 2019-09-24T20:45:37+09:00 https://crieit.net/posts/Firestore-3 Firestoreのデータを分析するための3つの方法とその注意点 <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/ef80b07347cfdd37f116">別の記事</a>を書く前に、いろいろ試したことの備忘録。<br /> Firestoreのデータをいろいろ見たかったけど、<br /> 別プロジェクトの移動してみることにした経緯のまとめ。</p> <h2 id="試したのは以下の3つ"><a href="#%E8%A9%A6%E3%81%97%E3%81%9F%E3%81%AE%E3%81%AF%E4%BB%A5%E4%B8%8B%E3%81%AE3%E3%81%A4">試したのは以下の3つ</a></h2> <ol> <li>エクスポートデータをBigQueryにインポートしてBigQuery上で確認</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/jloosli/node-firestore-import-export">node-firestore-import-export</a>を使ってローカルのJSONファイルとしてエクスポートして確認</li> <li>別プロジェクトにデータを移行して、別プロジェクト上で確認(<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/ef80b07347cfdd37f116">この記事</a>)</li> </ol> <p>それぞれの方法と注意点は以下の通り。</p> <h3 id="1. BigQueryにインポートして確認"><a href="#1.+BigQuery%E3%81%AB%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88%E3%81%97%E3%81%A6%E7%A2%BA%E8%AA%8D">1. BigQueryにインポートして確認</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/bigquery/docs/loading-data-cloud-firestore?hl=ja">公式ドキュメント</a>などで紹介されている通り、<br /> FirestoreのデータをBigQueryにインポートできるらしい。</p> <p>これが一番いい方法だが、後述するカラム10000制約に引っかかったので、諦める形に。。</p> <h4 id="流れとしては、こんな感じ。"><a href="#%E6%B5%81%E3%82%8C%E3%81%A8%E3%81%97%E3%81%A6%E3%81%AF%E3%80%81%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98%E3%80%82">流れとしては、こんな感じ。</a></h4> <ol> <li>バックファイルファイルを配置できるようCloud StorageにBucketを作成</li> <li>gcloudを使って、Firestoreのバックアップファイルを作成したBucketにエクスポート</li> <li>BigQueryでデータセットを作成</li> <li>テーブルを作成する際に、テーブル作成元に「Google Cloud Storage」を選択</li> </ol> <h4 id="注意: ハマった点"><a href="#%E6%B3%A8%E6%84%8F%3A+%E3%83%8F%E3%83%9E%E3%81%A3%E3%81%9F%E7%82%B9">注意: ハマった点</a></h4> <h5 id="a. バックアップデータを作成する際は、collection-idsを指定しないといけない"><a href="#a.+%E3%83%90%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AF%E3%80%81collection-ids%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3%81%84%E3%81%91%E3%81%AA%E3%81%84">a. バックアップデータを作成する際は、collection-idsを指定しないといけない</a></h5> <p><a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/bigquery/docs/loading-data-cloud-firestore?hl=ja">公式ドキュメント</a>にも書いてあるが以下の通りらしい。。</p> <blockquote> <p>エクスポート コマンドには、<strong>collection-idsフィルタを指定する必要があります</strong>。<br /> コレクションIDフィルタを指定せずにエクスポートされたデータをBigQueryに読み込むことはできません。</p> </blockquote> <p>なので、バックアップする際は、こんな感じで、コレクションを指定しなければならない。</p> <pre><code class="console">$ gcloud beta firestore export gs://[BUCKET_NAME] --collection-ids=[COLLECTION_ID_1],[COLLECTION_ID_2] </code></pre> <h5 id="b. ファイル形式も「Cloud DataStoreバックアップ」に変更しないといけない"><a href="#b.+%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%BD%A2%E5%BC%8F%E3%82%82%E3%80%8CCloud+DataStore%E3%83%90%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%80%8D%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3%81%84%E3%81%91%E3%81%AA%E3%81%84">b. ファイル形式も「Cloud DataStoreバックアップ」に変更しないといけない</a></h5> <p><a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/bigquery/docs/loading-data-cloud-firestore?hl=ja">公式ドキュメント</a>のスクリーンショットになかったのでハマった。。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/a9eac817-3145-5784-865e-756c50ec5109.png" alt="スクリーンショット_2019-09-24_19_50_01.png" /></p> <p>インポートする際には、ファイル形式も「Cloud DataStoreバックアップ」に変更しないといけないらしい。。<br /> なにもしていないと、ファイル形式「Avro」になっているので、ファイル形式が合わずエラーになってしまう。。</p> <h5 id="c. ドキュメントフィールドがobjectの場合、ドット区切りで展開され、カラムの上限は10000まで"><a href="#c.+%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E3%81%8Cobject%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81%E3%83%89%E3%83%83%E3%83%88%E5%8C%BA%E5%88%87%E3%82%8A%E3%81%A7%E5%B1%95%E9%96%8B%E3%81%95%E3%82%8C%E3%80%81%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AE%E4%B8%8A%E9%99%90%E3%81%AF10000%E3%81%BE%E3%81%A7">c. ドキュメントフィールドがobjectの場合、ドット区切りで展開され、カラムの上限は10000まで</a></h5> <p>これが原因で諦めました。。</p> <p>Firestore上で、以下のようなobjectやマップなどが存在すると、</p> <pre><code class="json">{ "uid": "XXXXX", "group": { "groupA": true, "groupB": false } } </code></pre> <p>BigQueryにインポートする際、ドットで繋げる形で展開されます。</p> <pre><code class="csv">uid, group.groupA, group.groupB </code></pre> <p>また、<a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/bigquery/docs/loading-data-cloud-firestore?hl=ja">公式ドキュメント</a>の制約事項に、</p> <blockquote> <p>Cloud Firestore のエクスポートを正しく読み込むには、<br /> エクスポートデータ内のドキュメントが一貫したスキーマを共有し、<br /> 個別のフィールド名が<strong>10,000個未満</strong>でなければなりません。</p> </blockquote> <p>と、あるため、大量のプロパティを持つobjectをセットしていると、インポート時にエラーになります。。</p> <p><strong>対応策</strong></p> <p>フィールド数の問題を回避する方法として、特定のフィールドだけ読み込むこともできます。</p> <blockquote> <p>CLI で --projection_fieldsフラグを使用するか、<br /> load ジョブの構成でprojectionFieldsプロパティを設定することで<br /> 特定のフィールドを読み込むことができます。</p> </blockquote> <p>とあるため、読み込むフィールドを指定することで、回避することができそうです。</p> <h2>2. <a target="_blank" rel="nofollow noopener" href="https://github.com/jloosli/node-firestore-import-export">node-firestore-import-export</a>でローカルにエクスポート</h2> <p>次にやったのがこれ。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/jloosli/node-firestore-import-export">node-firestore-import-export</a>を使うと、CUIでJSON形式でエクスポートできるらしい。<br /> JSON形式でエクスポートできれば、ツールなど作っていろいろできそうかなと。</p> <p>実行するには、Google Cloudアカウントのcredentialsファイルが必要。</p> <pre><code class="console"># インストール $ npm install -g node-firestore-import-export # 全体のバックアップ firestore-export --accountCredentials path/to/credentials/file.json --backupFile backups_myDatabase.json # コレクションを指定して、一部だけをエクスポート firestore-export --accountCredentials path/to/credentials/file.json --backupFile backups_myDatabase.json --nodePath collectionA </code></pre> <p>データ量が多い場合は、<code>--max_old_space_size</code>を指定して実行する。</p> <pre><code>node --max_old_space_size=8192 /usr/local/bin/firestore-export --accountCredentials path/to/credentials/file.json --backupFile backups_myDatabase.json </code></pre> <h4 id="注意: というか見送った点。。"><a href="#%E6%B3%A8%E6%84%8F%3A+%E3%81%A8%E3%81%84%E3%81%86%E3%81%8B%E8%A6%8B%E9%80%81%E3%81%A3%E3%81%9F%E7%82%B9%E3%80%82%E3%80%82">注意: というか見送った点。。</a></h4> <p>サクッと使えてよかったけど、データ量が多いと、やはりつらい。。</p> <ol> <li>データ量が多いと、メモリ不足やFirestoreとのコネクション切れで失敗する</li> <li>無料枠でも使えるが、firebase-adminを使って取ってきているだけなので読み込みが大量に発生する</li> <li>エクスポートしたファイルも大きくなるため、ツールでもメモリを大量に使う</li> </ol> <h3 id="別プロジェクトにデータを移行して、別プロジェクト上で確認"><a href="#%E5%88%A5%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AB%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E7%A7%BB%E8%A1%8C%E3%81%97%E3%81%A6%E3%80%81%E5%88%A5%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E4%B8%8A%E3%81%A7%E7%A2%BA%E8%AA%8D">別プロジェクトにデータを移行して、別プロジェクト上で確認</a></h3> <p>上の2つの方法を試してなんとかしたかったのは、このの3つ。</p> <ol> <li>エクスポート時に大量の読み込みが発生し、<strong>本番環境の無料枠を消費する</strong></li> <li><strong>ネストが深いobjectがあると</strong>と簡単にBigQueryにインポートできない</li> <li>ローカルファイルでも全データをメモリ上に展開するので、つらい</li> </ol> <p>そのため、別プロジェクトに移行して、本番環境と関係ない部分で、いろいろ触れるようにしてみた。<br /> これであれば、本番環境を気にせず、firebase-adminとかで、いろいろ調べたりできそう。</p> <p>詳細なやり方は、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/ef80b07347cfdd37f116">別の記事</a>にまとめました。</p> <p>他と同様、リアルタイムの情報ではないので、注意が必要。</p> <h2 id="簡単なまとめ"><a href="#%E7%B0%A1%E5%8D%98%E3%81%AA%E3%81%BE%E3%81%A8%E3%82%81">簡単なまとめ</a></h2> <h5 id="1. BigQueryにインポートして確認"><a href="#1.+BigQuery%E3%81%AB%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88%E3%81%97%E3%81%A6%E7%A2%BA%E8%AA%8D">1. BigQueryにインポートして確認</a></h5> <ul> <li>BigQuery上なので、SQLで分析ができる</li> <li>有料プランのみ</li> <li><strong>注意</strong>: バックアップデータを作成する際は、collection-idsを指定しないといけない</li> <li><strong>注意</strong>: ファイル形式も「Cloud DataStoreバックアップ」に変更しないといけない</li> <li><strong>注意</strong>: ドキュメントフィールドがobjectの場合、ドット区切りで展開され、カラムの上限は10000</li> </ul> <h5>2. <a target="_blank" rel="nofollow noopener" href="https://github.com/jloosli/node-firestore-import-export">node-firestore-import-export</a>でローカルにエクスポート</h5> <ul> <li>無料枠でも使える</li> <li>JSONでエクスポートできる</li> <li><strong>注意</strong>: エクスポート時に大量の読み込みが発生し、本番環境の無料枠を消費する</li> <li><strong>注意</strong>: ドキュメントのネストが深いと簡単にBigQueryにインポートできない</li> <li><strong>注意</strong>: ローカルファイルでも全データをメモリ上に展開するのでつらい</li> </ul> <h5>3. 別プロジェクトにデータを移行して、別プロジェクト上で確認(<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/ef80b07347cfdd37f116">この記事</a>)</h5> <ul> <li>有料プランのみ</li> <li>別プロジェクトなので、本番環境を気にせず使える</li> </ul> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで</p> <h1 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/export-import?hl=ja">データのエクスポートとインポート  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://medium.com/google-cloud-jp/firestore-backup-67327a74cd54">Cloud Firestoreのバックアップ・リストア - google-cloud-jp - Medium</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://note.mu/yo16/n/ne25615bab8f5">gcloudのアカウント変更とプロジェクト変更|yo16|note</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ShuA/items/1ff83b8d804168b087ba">自分用GCPコマンドリファレンス - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/Takakuwa-Shun/items/ac455c96a81a189eacce">gcloudの自分用メモ - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://blog.engineer.adways.net/entry/2018/06/08/150000">gcloudコマンド(GCP)で複数のプロジェクトとアカウントの使い分けを便利に - Adwaysエンジニアブログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sakamossan/items/8ff74ed377dc77325b80">macosにgcloudとkubectlのインストール - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/sdk/docs/quickstart-macos?hl=ja">macOS 用のクイックスタート  |  Cloud SDK のドキュメント  |  Google Cloud</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/bigquery/docs/loading-data-cloud-firestore?hl=ja">Cloud Firestore のエクスポートからのデータの読み込み  |  BigQuery  |  Google Cloud</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://medium.com/google-cloud-jp/firestore-bigquery-3b887a5bc27e">エクスポートされたFirestoreドキュメントをBigQueryに自動ロード - google-cloud-jp - Medium</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kurikei/items/ece7f6a21115494386c9">Cloud Firestore のコレクションを日次で Bigquery にロードする - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15421 2019-09-24T19:17:59+09:00 2019-09-24T19:17:59+09:00 https://crieit.net/posts/Firestore-gcloud Firestoreのデータをgcloudを使ってバックアップ&別のプロジェクトへインポートしてみる <p>Firestoreのデータをガリガリ見ていきたいなと思っていたら、<br /> 別のプロジェクトに移行できそうだったので、いろいろ調べたときの備忘録。</p> <p>分析目的だけど、BigQueryは1000カラム制約で使えなかったので代替案...<br /> 別プロジェクトなら無料枠でいろいろできそう。</p> <h3 id="注意"><a href="#%E6%B3%A8%E6%84%8F">注意</a></h3> <p>Firestoreのエクスポート/インポートについて、いくつか注意。</p> <ol> <li>Firestoreのエクスポート/インポートは<strong>Blazeプラン</strong>が必要(無料枠では不可)</li> <li>ドキュメントの読み取りと書き込み時にCloud Firestoreの料金が課金される</li> </ol> <p>さらに、</p> <blockquote> <p>エクスポート / インポート オペレーションのコストは、<strong>費用制限の対象にはなりません</strong>。<br /> オペレーションが完了するまで、エクスポート/インポートでGCPの<strong>予算アラートはトリガーされません</strong>。</p> </blockquote> <p>とのことなので、ご利用は計画的に..</p> <hr /> <h3 id="1. gcloudのインストール"><a href="#1.+gcloud%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">1. gcloudのインストール</a></h3> <h4 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h4> <pre><code class="console">$ brew cask install google-cloud-sdk </code></pre> <h4 id=".bashrcなどに以下を追記"><a href="#.bashrc%E3%81%AA%E3%81%A9%E3%81%AB%E4%BB%A5%E4%B8%8B%E3%82%92%E8%BF%BD%E8%A8%98">.bashrcなどに以下を追記</a></h4> <pre><code class="bash">source '/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.bash.inc' source '/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/completion.bash.inc' </code></pre> <h4 id="gcloudの初期化"><a href="#gcloud%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96">gcloudの初期化</a></h4> <pre><code class="console"># 初期設定の実行 $ gcloud init # このあと、ログイン画面が出てきて認証する # コンポーネントのアップデート $ gcloud components update # 現在のプロジェクトの確認 $ gcloud info | grep project </code></pre> <h3 id="2. FirestoreのデータをExportする"><a href="#2.+Firestore%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92Export%E3%81%99%E3%82%8B">2. FirestoreのデータをExportする</a></h3> <p><code>firestore export</code>を使うと、データのバックアップが作成できるらしい。<br /> バックアップデータはGCPのStorage上に作成されるので、<br /> あらかじめ、Storageを作成しておく必要がある。</p> <pre><code class="console"># 現在のプロジェクトの確認 $ gcloud info | grep project # 全体のエクスポート $ gcloud beta firestore export gs://[BUCKET_NAME] # コレクションを指定して、一部だけをエクスポート $ gcloud beta firestore export gs://[BUCKET_NAME] --collection-ids=[COLLECTION_ID_1],[COLLECTION_ID_2] # 実行状況の確認 $ gcloud beta firestore operations list </code></pre> <p>データ量が多いと、コマンドが途中で終了するので、<br /> 完了したかどうかは<code>operations list</code>を実行して確認する必要がある。</p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/export-import?hl=ja">データのエクスポートとインポート  |  Firebase</a></p> <h3 id="3. プロジェクトを変更する"><a href="#3.+%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B">3. プロジェクトを変更する</a></h3> <pre><code class="bash"># プロジェクトの設定 $ gcloud config set project [PROJECT] # 現在の設定情報の確認 $ gcloud info </code></pre> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/sdk/docs/configurations">SDK 構成の管理  |  Cloud SDK のドキュメント  |  Google Cloud</a></p> <h3 id="4. アカウントを変更する"><a href="#4.+%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B">4. アカウントを変更する</a></h3> <pre><code class="console"># アカウントの設定 $ gcloud config set account [ACCOUNT] # 設定したアカウントの認証 $ gcloud auth login # 登録しているアカウントの一覧を確認 $ gcloud auth list # プロジェクトの設定 $ gcloud config set project [PROJECT] # 現在の設定情報の確認 $ gcloud info </code></pre> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/sdk/docs/authorizing">Cloud SDK ツールの承認  |  Cloud SDK のドキュメント  |  Google Cloud</a></p> <h3 id="5. バックアップを別のプロジェクトにインポートする"><a href="#5.+%E3%83%90%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%82%92%E5%88%A5%E3%81%AE%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AB%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B">5. バックアップを別のプロジェクトにインポートする</a></h3> <p>全体の流れはこんな感じ。</p> <pre><code class="console"># バックアップしたいプロジェクトに設定 $ gcloud config set project [PROJECT] # 全体のエクスポート $ gcloud beta firestore export gs://[BUCKET_NAME] # 別のプロジェクトにプロジェクトに変更 $ gcloud config set project [PROJECT] # バックアップファイルのインポート $ gcloud beta firestore import gs://[BUCKET_NAME]/[EXPORT_PREFIX]/ </code></pre> <p><strong>注意点</strong></p> <ol> <li>インポートしたいプロジェクトのFirestoreは初期設定までする必要がある</li> <li>実行はプロジェクトのサービスアカウントで実行されるので、<br /> バックアップファイルのバケットに権限を付与する必要がある</li> </ol> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/export-import?hl=ja">データのエクスポートとインポート  |  Firebase</a></p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>有料のみではあるけど、</p> <ol> <li>本番データの分析したり</li> <li>本番データを開発用に利用したり</li> <li>プロジェクトの移行</li> </ol> <p>なんかにも使えそう。<br /> 分析はBigQueryを使えるならそのほうがいいかも?</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで</p> <h1 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/export-import?hl=ja">データのエクスポートとインポート  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://medium.com/google-cloud-jp/firestore-backup-67327a74cd54">Cloud Firestoreのバックアップ・リストア - google-cloud-jp - Medium</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://note.mu/yo16/n/ne25615bab8f5">gcloudのアカウント変更とプロジェクト変更|yo16|note</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ShuA/items/1ff83b8d804168b087ba">自分用GCPコマンドリファレンス - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/Takakuwa-Shun/items/ac455c96a81a189eacce">gcloudの自分用メモ - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://blog.engineer.adways.net/entry/2018/06/08/150000">gcloudコマンド(GCP)で複数のプロジェクトとアカウントの使い分けを便利に - Adwaysエンジニアブログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sakamossan/items/8ff74ed377dc77325b80">macosにgcloudとkubectlのインストール - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/sdk/docs/quickstart-macos?hl=ja">macOS 用のクイックスタート  |  Cloud SDK のドキュメント  |  Google Cloud</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15326 2019-08-16T12:23:37+09:00 2019-08-16T12:23:37+09:00 https://crieit.net/posts/PC-firebase-admin ローカルPCでfirebase-adminを使って一括削除/一括更新する <p>最近、Nuxt.js+Firebaseで作った積読を解消するWebアプリ「<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>」をリリースしました!!</p> <p>そのときに、Firestoreのスキーマを一括変更したり、<br /> 不要なデータを一括削除したいと思ったときの備忘録。</p> <p>ローカルPCでfirebase-adminを使うときの初期設定とかはこちらを参照<br /> - <a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/07/25/073000">ローカルPCからfirebase-adminを使ってFirestoreを操作する(管理ツール) - くらげになりたい。</a></p> <h4 id="注意点"><a href="#%E6%B3%A8%E6%84%8F%E7%82%B9">注意点</a></h4> <p>Firestoreでは、1度に実行できる更新処理の数に制限がある。</p> <blockquote> <p>一括書き込みでは最大 500 件のオペレーションを実行できます</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/transactions">トランザクションと一括書き込み  |  Firebase</a></p> <p>そのため、以下にも記載されている通り、<br /> 100件ごとなど、小さなバッチに分けて実行する必要がある。</p> <blockquote> <p>メモリ不足エラーを避けるため、小さなバッチに分けてドキュメントを削除することをおすすめします。コレクション全体またはサブコレクションが削除されるまで、このプロセスを繰り返します。</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/delete-data#collections">Cloud Firestore からデータを削除する  |  Firebase</a></p> <h3 id="小さなバッチに分けて実行するソース"><a href="#%E5%B0%8F%E3%81%95%E3%81%AA%E3%83%90%E3%83%83%E3%83%81%E3%81%AB%E5%88%86%E3%81%91%E3%81%A6%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%82%BD%E3%83%BC%E3%82%B9">小さなバッチに分けて実行するソース</a></h3> <p>全体としてはこんな感じ。</p> <p>登場する3つ</p> <ol> <li><code>main()</code> ... メイン関数。実行したときに呼ばれる関数</li> <li><code>deleteAllUsers()</code> ... ユーザを一括削除する関数。mainから呼ばれる</li> <li><code>executeBatch()</code> ... 指定したバッチサイズで再帰的に実行する関数。deleteAllUsers()から呼ばれる</li> </ol> <p>なお、コレクション<code>users</code>のなかにあるドキュメントには、<br /> 作成日時のタイムスタンプ<code>createAt</code>のドキュメントフィールドがある想定です。</p> <pre><code class="javascript">const admin = require("firebase-admin"); // 配置したサービスアカウントの秘密鍵を取得 const serviceAccount = require("./key/XXXXX.json"); // firebase-adminを初期化 admin.initializeApp({ credential: admin.credential.cert(serviceAccount) }); // firestoreのインスタンスを取得 const db = admin.firestore(); /** * 一括実行処理の共通関数 * @param {FirebaseFirestore.Firestore} db firestoreのインスタンス * @param {Number} limit 1回に実行するサイズ * @param {Function} queryFunc ドキュメントを検索するクエリを作成する関数。firestore.Queryを返す * @param {Function} executeFunc 削除や更新などを実行する関数。 * @param {Object|undefined} last 検索でヒットした最後の要素。バッチサイズで繰り返す際に利用s */ async function executeBatch(db, limit, queryFunc, executeFunc, last) { // queryFuncを使って対象のドキュメントを取得 const query = queryFunc(db, last); const items = await query.limit(limit).get(); // ドキュメントが1つも見つからなければ、終了 if (items.size === 0) return; // executeFuncを使ってbatchに削除/更新処理を追加&コミット const batch = db.batch(); for (let i = 0; i < items.size; i++) { executeFunc(db, batch, items.docs[i]); } await batch.commit(); // リストの最後の要素を取得して、再帰実行 const lastItem = items.docs[items.size - 1].data(); return await executeBatch(db, limit, queryFunc, executeFunc, lastItem); } /** * すべてのユーザを削除 */ async function deleteAllUsers(db, limit) { // 検索部分の関数: 作成日時で昇順ソートして取得 const queryFunc = (db, last) => { let query = db.collection("users").orderBy("createAt", "asc"); if (!!last) query = query.startAfter(last.createAt); return query; }; // 実行部分の関数: itemで該当のドキュメントを受け取るので削除 const executeFunc = (db, batch, item) => { const docRef = db.collection("users").doc(item.id); batch.delete(docRef); }; return await executeBatch(db, limit, queryFunc, executeFunc, undefined); } // **************************** // * MAIN // **************************** async function main() { console.log(`***** START MAIN *****`); // バッチサイズを100件で実行 const limit = 100; await deleteAllUsers(db, limit) console.log(`***** END MAIN *****`); } main().then(); </code></pre> <p>他の処理でも共通的に使えるように<code>executeBatch()</code>では、<br /> <code>queryFunc</code>と<code>executeFunc</code>の2つの関数を引数で受け取れるようにしている。</p> <p>なので他の処理などを追加するときは、<code>deleteAllUsers()</code>みたいなのを増やしていけばOK!!</p> <p>これで一括削除やスキーマの更新もだいぶ楽に...(<em>´ω`</em>)</p> <h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2> <p>Webサービスは運用しはじめると色々大変なので、管理ツールなども大事...<br /> 最近リリースしたこちらのアプリでも使ってます♪</p> <p>■積んでる本の総額がわかる読書管理サービス<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a><br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/2c9872d0-101b-88aa-af2a-5bb907d09e28.png" width="20%"/></p> <p>総額がわかると少しは積ん読してる本を読もうと思ったり♪<br /> 積読総額ランキングや人気の本などもあるので、よかったらあそんでもらえれば!!</p> <p>積読ハウマッチは、Nuxt.js+Firebase+ZEIT Nowで作ってます♪</p> <h1 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/delete-data#collections">Cloud Firestore からデータを削除する  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://note.mu/shogoyamada/n/n04d80a8d284f">Firestoreにテストデータを流す|shogo yamada|note</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://gist.github.com/mono0926/78c3d3e078323d566b46929c7540b15b">Firestoreの特定のコレクションのドキュメントを全削除するスクリプト(TypeScript版)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://medium.com/google-cloud-jp/firestore-backup-67327a74cd54">Cloud Firestoreのバックアップ・リストア - google-cloud-jp - Medium</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/07/25/073000">ローカルPCからfirebase-adminを使ってFirestoreを操作する(管理ツール) - くらげになりたい。</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15310 2019-08-09T08:49:16+09:00 2019-08-09T08:49:16+09:00 https://crieit.net/posts/Web-5d4cb4fcdd570 積読を解消をうながすWebサービス『積読ハウマッチ』をリリースしました!!! <p>4月くらいから作っていたWebサービスをついにリリースしました♪<br /> 積んでる本の総額がわかる読書管理サービスです!</p> <p>積読が多い自分を戒めるためのWebサービス( ゚д゚)!</p> <p>■積んでる本の総額がわかる書籍サービス|積読ハウマッチ<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">https://tsundoku.site</a></p> <p><a href="https://crieit.now.sh/upload_images/f86129c7aa3dee1104223eaa7d236fb55d4cb2a04b063.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f86129c7aa3dee1104223eaa7d236fb55d4cb2a04b063.png?mw=700" width="25%"/></a></p> <h3 id="思いついたときのツイート"><a href="#%E6%80%9D%E3%81%84%E3%81%A4%E3%81%84%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88">思いついたときのツイート</a></h3> <p>思い返すと、完全にネタ的なおもいつきではじまった感(<em>´ω`</em>)</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">おもしろそうなアプリを思いついてしまった。。自分がダメージを受けるが作りたい。。</p>— きらぷか📚積読ハウマッチ開発中【事前登録はじめました】 (@kira_puka) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka/status/1116553164541399040?ref_src=twsrc%5Etfw">April 12, 2019</a></blockquote> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ちなみに、おもしろそうっていうのは、「くだらない!!」的なおもしろさで、すごい!とかイケてる!とかはまったくない</p>— きらぷか📚積読ハウマッチ開発中【事前登録はじめました】 (@kira_puka) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka/status/1116563595632893954?ref_src=twsrc%5Etfw">April 12, 2019</a></blockquote> <h3 id="こんなサービスです"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A7%E3%81%99">こんなサービスです</a></h3> <p>メインの画面はこんな感じ。<br /> 本を登録して、ステータスを変更していく、積読を管理するサービスです。</p> <p><a href="https://crieit.now.sh/upload_images/6f44c811b94a08fffcc0368201d8e27c5d4cb15aeb1a7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6f44c811b94a08fffcc0368201d8e27c5d4cb15aeb1a7.png?mw=700" alt="スクリーンショット 2019-07-26 9.31.32.png" /></a></p> <p>ほかとちがうのは、<strong>本の総額がわかる</strong>ところ<br /> <strong>いま、いくら積んでいるかがわかってしまうおそろしさ((((;゚Д゚))))ガクガク</strong></p> <h3 id="コンセプト"><a href="#%E3%82%B3%E3%83%B3%E3%82%BB%E3%83%97%E3%83%88">コンセプト</a></h3> <p><a href="https://crieit.now.sh/upload_images/d08ca6a468920cdc81745d2f2204e6e75d4cb188bc65c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d08ca6a468920cdc81745d2f2204e6e75d4cb188bc65c.png?mw=700" alt="スクリーンショット 2019-07-26 9.29.58.png" /></a></p> <p>おもいつきではあるんですが、もともと積読が多いなと。。</p> <p>本で買うのですが、積み上がっている本を見て、ふと、<br /> **「これっていくらなんだろ?」 **<br /> とおもったのが発端。読まないとお金を積んでるだけだなと。。</p> <h3 id="使い方のかんたんな説明"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9%E3%81%AE%E3%81%8B%E3%82%93%E3%81%9F%E3%82%93%E3%81%AA%E8%AA%AC%E6%98%8E">使い方のかんたんな説明</a></h3> <p>使い方はシンプル</p> <p>検索して、</p> <p><a href="https://crieit.now.sh/upload_images/9908182b569e1b60b5c10c20a5db01955d4cb1a589772.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9908182b569e1b60b5c10c20a5db01955d4cb1a589772.png?mw=700" alt="スクリーンショット 2019-07-26 9.30.25.png" /></a></p> <p>持ってる本を積んで、</p> <p><a href="https://crieit.now.sh/upload_images/c7c59729dab973076b8d04714dca49845d4cb1aeab425.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c7c59729dab973076b8d04714dca49845d4cb1aeab425.png?mw=700" alt="スクリーンショット 2019-07-26 9.30.34.png" /></a></p> <p>総額を見て、現実を受け止め、</p> <p><a href="https://crieit.now.sh/upload_images/86b1b913fe42a2fe8c240f23889fb5a35d4cb1bd36d82.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/86b1b913fe42a2fe8c240f23889fb5a35d4cb1bd36d82.png?mw=700" alt="スクリーンショット 2019-07-26 9.30.42.png" /></a></p> <p>積読を減らすことをがんばる!</p> <p><a href="https://crieit.now.sh/upload_images/6325d3a9f673b1a0bf303c5f03c332af5d4cb1c68507f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6325d3a9f673b1a0bf303c5f03c332af5d4cb1c68507f.png?mw=700" alt="スクリーンショット 2019-07-26 9.30.49.png" /></a></p> <p>持ってる本を登録して、読みはじめたり、<br /> 読み終わったりしたらステータスを変える感じです!</p> <h3 id="積んでる金額を見るだけだとつらいので。。"><a href="#%E7%A9%8D%E3%82%93%E3%81%A7%E3%82%8B%E9%87%91%E9%A1%8D%E3%82%92%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E3%81%A0%E3%81%A8%E3%81%A4%E3%82%89%E3%81%84%E3%81%AE%E3%81%A7%E3%80%82%E3%80%82">積んでる金額を見るだけだとつらいので。。</a></h3> <p>ランキング機能もついています(<em>´ω`</em>)</p> <p><strong>「こんなに積んでるけど、ほかのひとはどれくらいなんだろ?」</strong></p> <p>っていうのが発端。</p> <p>他の人のをみて「まだまだ積んでも大丈夫(<em>´ω`</em>)」と思いたいなと。。</p> <p><a href="https://crieit.now.sh/upload_images/3bc3f7432fd83b0febe269579571f63d5d4cb1d68abd3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3bc3f7432fd83b0febe269579571f63d5d4cb1d68abd3.png?mw=700" alt="スクリーンショット 2019-07-26 9.50.00.png" /></a></p> <p><strong>読まれてる本、買われてる本のランキング</strong>もあるので、<br /> どんな本が人気かもわかるように♪</p> <p><a href="https://crieit.now.sh/upload_images/1f090737658ae37b78842e05183582f85d4cb1f61cd9f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1f090737658ae37b78842e05183582f85d4cb1f61cd9f.png?mw=700" alt="スクリーンショット 2019-08-07 12.45.00.png" /></a></p> <p><strong>新着もあるので、新しく積まれたり読まれたりした本を、<br /> ぼーっとながめるのも楽しい感じに(<em>´ω`</em>)</strong></p> <h3 id="Twitterでシェアして積読金額を晒せる..."><a href="#Twitter%E3%81%A7%E3%82%B7%E3%82%A7%E3%82%A2%E3%81%97%E3%81%A6%E7%A9%8D%E8%AA%AD%E9%87%91%E9%A1%8D%E3%82%92%E6%99%92%E3%81%9B%E3%82%8B...">Twitterでシェアして積読金額を晒せる...</a></h3> <p>SNSでシェアした時の画像も力をいれてみました(<em>´ω`</em>)<br /> <strong>積んでる金額を晒すと、さらに読まないといけない感が出るかも(・・?</strong><br /> (積んでるのに、ドヤ顔のおじさんが...)</p> <p><a href="https://crieit.now.sh/upload_images/40e3db845e5af712e49912ca6a9b867b5d4cb20c64d89.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/40e3db845e5af712e49912ca6a9b867b5d4cb20c64d89.png?mw=700" alt="スクリーンショット 2019-07-26 10.01.46.png" /></a></p> <p>もちろん、読んだ本も共有できるので、<br /> 「こんなに読んだんだ!がんばった!」<br /> もシェアできます♪ <strong>独学や自己研鑽をがんばってるひとにも!</strong></p> <p><a href="https://crieit.now.sh/upload_images/eefdda10ec1cab2aaaa17a5e3766bd3c5d4cb21ab8da4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/eefdda10ec1cab2aaaa17a5e3766bd3c5d4cb21ab8da4.png?mw=700" alt="スクリーンショット 2019-07-26 10.01.53.png" /></a></p> <p>(こっちはすっきりしたおじさん...)</p> <h3 id="まだまだ作れてないところはたくさん。。"><a href="#%E3%81%BE%E3%81%A0%E3%81%BE%E3%81%A0%E4%BD%9C%E3%82%8C%E3%81%A6%E3%81%AA%E3%81%84%E3%81%A8%E3%81%93%E3%82%8D%E3%81%AF%E3%81%9F%E3%81%8F%E3%81%95%E3%82%93%E3%80%82%E3%80%82">まだまだ作れてないところはたくさん。。</a></h3> <p>とりあえず、ファーストリリースができたんですが、<br /> <strong>まだまだ作りたい部分はたくさん。。!!</strong></p> <ul> <li>読んだ感想やメモを記録できるようにしたい</li> <li>どれくらい読んだかを日々の進捗を記録できるようにしたい</li> <li>読み終える目標の日を決めて、アラートしてもらいたい</li> </ul> <p>積読の消化を促すような機能をどんどんいれれたらとおもっています!</p> <p>見てるだけでも楽しいので、まずはちらっとみてもらえたら♪<br /> おもしろそうだなと思ったら、ぜひ一緒に積み比べしましょう!</p> <p>良い積読ライフを(<em>´ω`</em>)</p> <p>■積んでる本の総額がわかる書籍サービス|積読ハウマッチ<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">https://tsundoku.site</a></p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/14665 2018-12-16T23:27:12+09:00 2019-05-12T08:36:22+09:00 https://crieit.net/posts/Nuxt-js-Firestore-SSR Nuxt.js+Firestoreの場合に安全にSSRする方法 <p>Nuxt.jsをサーバーで動かしてFirestoreを使い、且つサーバーサイドレンダリングしたい時には色々考えることが出てきます。懸念点と対応した方がよさそうな点をまとめてみます。</p> <h2 id="SSRしない場合との違い"><a href="#SSR%E3%81%97%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E3%81%AE%E9%81%95%E3%81%84">SSRしない場合との違い</a></h2> <p>SSRせずクライアント上でFirestoreからデータを取る場合ですが、匿名認証にしろ、SNSアカウントの認証にしろ、認証しているかどうかでセキュリティルールによりデータを守りやすく、他者によるFirebaseプロジェクトの設定を盗用しての勝手な操作を防いだりすることができます。</p> <p>しかし、asyncData内によるSSRとなると、そのクライアント側の認証情報を利用することはできません。そうなるとデータを守れないどころか、そもそもセキュリティルールが設定されているとデータにアクセスすることすらできません。こういう時にどうすれば良いかを考えます。</p> <h2 id="どのように解決するか"><a href="#%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E8%A7%A3%E6%B1%BA%E3%81%99%E3%82%8B%E3%81%8B">どのように解決するか</a></h2> <h3 id="Cloud Functionsの利用"><a href="#Cloud+Functions%E3%81%AE%E5%88%A9%E7%94%A8">Cloud Functionsの利用</a></h3> <p>今回はCloud Functionsを利用する方法を考えました。具体的にどうするかというと、asyncData内ではfunctionsからデータを取るだけにします。例えば下記のような感じです。</p> <pre><code class="javascript"> async asyncData({ params }) { const response = await axios.get(functionUrl) return response.data } </code></pre> <p>呼び出し方はどうであれ、通信が発生するので上記のようにリクエストは1回にした方が良いでしょう。functions側でそのページ専用のアクションを作ってデータを返します。</p> <pre><code class="javascript">export const showUser = functions.https.onRequest((req, res) => { return cors(req, res, async () => { const user = await User.getUserByUsername(req.query.id) res.json({ user, posts: await Post.getPosts(user.uid, 5) }) }) }) </code></pre> <h3 id="データの取得方法"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%8F%96%E5%BE%97%E6%96%B9%E6%B3%95">データの取得方法</a></h3> <p>しかし、これでは先程のasyncDataと同様、セキュリティルールの関係でデータが取れないのでは? と思われるかもしれません。ただ、Firebaseにはサーバー用のFirebase Admin SDKというものがあります。これはセキュリティルールに関係なくデータを扱うことができるため、これを利用します。具体的には下記のような形です。</p> <pre><code class="typescript">import * as admin from 'firebase-admin' const firestore = admin.firestore() const usersCollection = firestore.collection('users') export async function getUser(uid: string) { const querySnapshot = await usersCollection .where('uid', '==', uid) .get() .catch(error => { console.error(error) return null }) if (!querySnapshot || querySnapshot.size == 0) { return null } const user = querySnapshot.docs[0].data() user.id = querySnapshot.docs[0].id return user } </code></pre> <p>クライアント側とは微妙にcollectionの取り方が違うだけで、基本的にはほぼ同じです。このようにしてサーバーサイドレンダリングのためのデータ取得処理を行うことができます。</p> <h2 id="セキュリティルールを無視で良いのか?"><a href="#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%83%AB%E3%83%BC%E3%83%AB%E3%82%92%E7%84%A1%E8%A6%96%E3%81%A7%E8%89%AF%E3%81%84%E3%81%AE%E3%81%8B%EF%BC%9F">セキュリティルールを無視で良いのか?</a></h2> <p>今回の例のfunctions側でのデータ取得処理に関しては、ルールを無視で問題ありません。</p> <p>というのも、そもそもSSRの目的としては、クローラに認識してもらうためのSEO対策になるかと思います。つまり、ユーザー認証が必要なデータを取る必要は一切無いということになります。あくまでも誰もが閲覧できるパブリックなデータを取るだけです。そのため基本的には認証に関するセキュリティルールをこの場で考慮する必要はありません。ただ、corsはちゃんと挟んでおきましょう。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>やってみて思いましたが、これって普通のサーバーを使った開発と同じですよね!? なんとなく手軽さが失われた感じはありますが、それでもまあサーバーを管理する必要が無いのは大きなメリットではあることに変わりはないでしょう。</p> <p>他にも色々と方法はあるかもしれませんが、とにかくどの様な方法にしろ、どのようにデータを守るかはしっかり考えて構築が必要となります。</p> <p>以前認証とセキュリティルールについて書いた考察もありますので、もし気が向いたらそちらもぜひ御覧ください。</p> <p><a href="https://crieit.net/posts/Firebase">Firebaseの匿名認証はなんのためにあるのか - セキュリティ編</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14613 2018-11-27T21:41:07+09:00 2018-11-29T13:31:02+09:00 https://crieit.net/posts/RDB-Firebase-DB RDBとFirebaseのDB両方使ったっていいじゃない <p>今年Firebaseを使い始めてからちょこちょこ自分の中で議題に上がるのが、RDBとFirebaseのデータベース(Firestore、Realtime Database)のどちらが使い勝手良いのか、ということ。ただ、そもそもどちらかを選ばなきゃいけないというわけではなく、適宜両方使えばいいということが分かったのでそれについての雑記。</p> <p>そもそもまず、なぜ悩むところがあったのか。</p> <h2 id="Firebaseのデータベースのデメリット"><a href="#Firebase%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88">Firebaseのデータベースのデメリット</a></h2> <p>一番大きいのはやはりリレーショナルでないことと、クエリが弱いということ。集計ができなかったり、欲しいデータがすぐ取れなかったりする。</p> <h2 id="RDBのデメリット"><a href="#RDB%E3%81%AE%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88">RDBのデメリット</a></h2> <p>とりあえずRDBにしておけば何でもできるというのはあるが、デメリットとしてはやはりサーバーもしくはお金が必要になってくるということ。サーバー内に入れると管理やスペックの調整などが気になるし、別途RDSやCloud SQLを使うと当然お金がかかる。</p> <p>Heroku等のPostgresやJawsを使う手もあるが、あまりにも容量が少なすぎる。</p> <h2 id="Firebaseへの期待が高まりすぎる"><a href="#Firebase%E3%81%B8%E3%81%AE%E6%9C%9F%E5%BE%85%E3%81%8C%E9%AB%98%E3%81%BE%E3%82%8A%E3%81%99%E3%81%8E%E3%82%8B">Firebaseへの期待が高まりすぎる</a></h2> <p>RDBはメリットが高いが、やはりサーバーが不要というFirebaseのデータベースのメリットも同様に非常に高い。そのため、いつかバージョンアップした際にクエリが強くなるのではないか、もっとデータが取りやすくなるのではないか、みたいな期待が高まりすぎてしまった。</p> <p>多分Datastoreを流用していると思うので、あまり期待しすぎても仕方なく、ひとまず現在は現状のままどうやって使っていくかを考える必要がある。Firebaseのデータベースを使うとなると、それでうまく作り方を考える必要が出てくる。(これはこれで面白いのだが)</p> <h2 id="どちらかに限定する必要はない"><a href="#%E3%81%A9%E3%81%A1%E3%82%89%E3%81%8B%E3%81%AB%E9%99%90%E5%AE%9A%E3%81%99%E3%82%8B%E5%BF%85%E8%A6%81%E3%81%AF%E3%81%AA%E3%81%84">どちらかに限定する必要はない</a></h2> <p>そこで気づいたのが、そもそも元々RDBを使っているアプリケーションであっても、Firebaseのデータベースを併用して使えばもっと便利になるのでは、ということ。</p> <p>例えばこのCrieitの例だが、先日リアルタイムで下書きをライブ公開できる機能を作成した。</p> <p><a href="https://crieit.net/posts/634be945aec88661d0edd1656e25f588">編集中の内容をライブ公開できるリアルタイム投稿機能を実装しました</a></p> <p>このサイトはGoogle Compute Engineの永久無料枠で使えるf1-microというインスタンスで動いているのだが、とにかくメモリの使用量も小さく貧弱。Laravelは一応Pusher等と連携してWebSocketを使える機能があるのでLaravel自体でもライブ公開機能は実現可能なのだが、結局状態変化があった時にデータを取得などしようとするとLaravel自体にアクセスが行ってしまう。あまりに頻度が多いと、現在のサーバーのスペックでは厳しくなる可能性がある。</p> <p>その時に思い出したのがFirebaseのRealtime Databse。これは同時接続数などの制限があるものの、書き込み、読み込み等の回数には特に制限がない。つまり、制限内でライブ公開する機能を付けるのであれば、サーバーの負荷ゼロでライブ公開機能が実現できてしまう。</p> <p>実際に下書き入力中の内容は特にサーバーに保存する必要がないので、入力した内容はそのままRealtime Databaseに保存し、閲覧者側はそれを受け取るだけになっており、完全にサーバー負荷も費用的な負荷もなくライブ公開の機能が作れてしまった。無料で運営している個人開発サービスとしてはこのやり方はとても相性が良い。流行りすぎて人が増えるとそうはいかなくなるが、とりあえず今の段階では全く心配はない。むしろ遊び道具が一つ増えて楽しみが増えた。</p> <p>逆で、Firebaseだけで運用しているサービスにRDBの利用を追加する、ということも考えられる。RDSやCloud SQLでは費用が増えてしまうが、とりあえずHerokuのPostgres等でも良いかもしれない。Firestore側が苦手とする集計や柔軟なページングなどを行いたいデータだけ、併用して保存することで、Cloud Functions経由で簡単に集計データなどが取れたりするのではないかと思う。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>とにかく柔軟に考えて、併用することでパフォーマンス的にも費用的にもベストな形になるような状態を目指していくと面白い。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14572 2018-10-19T20:52:47+09:00 2019-12-18T16:11:20+09:00 https://crieit.net/posts/Firebase Firebaseの匿名認証はなんのためにあるのか - セキュリティ編 <p>FirebaseのAuthenticationはGoogleログインやTwitterログインが簡単にできる便利なものですが、その中には匿名認証というものもあります。これが一体何のためにあるのか、セキュリティの観点から見てみます。今回はブラウザ上で動くWebアプリケーションの話です。</p> <h2 id="そもそも匿名認証とは?"><a href="#%E3%81%9D%E3%82%82%E3%81%9D%E3%82%82%E5%8C%BF%E5%90%8D%E8%AA%8D%E8%A8%BC%E3%81%A8%E3%81%AF%EF%BC%9F">そもそも匿名認証とは?</a></h2> <p>匿名認証というのは、ユーザーが何も操作などをしなくても、内部で勝手にログインする機能です。</p> <h2 id="何のためにあるのか?"><a href="#%E4%BD%95%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AB%E3%81%82%E3%82%8B%E3%81%AE%E3%81%8B%EF%BC%9F">何のためにあるのか?</a></h2> <p>基本的には公式の説明にいきなり書かれています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/anonymous-auth?hl=ja">JavaScript を使用して Firebase 匿名認証を行う  |  Firebase</a></p> <blockquote> <p>アプリに登録していないユーザーが、セキュリティ ルールで保護されているデータを使用できるようになります。</p> </blockquote> <h2 id="セキュリティとは?"><a href="#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%81%A8%E3%81%AF%EF%BC%9F">セキュリティとは?</a></h2> <p>Firebaseは便利ですが、怖いところがあります。Webアプリケーションの場合、JavaScriptを使うためFirebaseアプリケーションを初期化するための設定情報が丸見えになってしまいます。そのため、何のセキュリティも導入していないとその設定情報を使って誰でも勝手にそのアプリケーションの情報を読み書きできてしまいます。</p> <p>それを制限する方法として、ForestoreやStorageにはセキュリティルールがあります。例えば以下のようなことが設定できます。</p> <ul> <li>あるデータは誰でも閲覧できる</li> <li>あるデータはログインしたユーザーしか書き込みできない</li> </ul> <p>等。</p> <h3 id="セキュリティルールを設定しないとどうなるのか?"><a href="#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%83%AB%E3%83%BC%E3%83%AB%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3%81%A9%E3%81%86%E3%81%AA%E3%82%8B%E3%81%AE%E3%81%8B%EF%BC%9F">セキュリティルールを設定しないとどうなるのか?</a></h3> <p>前述の通り設定情報が丸見えですので、それをコピーして誰かが自分のプログラムに組み込めば、他人のFirebaseのアカウントで自由にデータなどを扱えるようになってしまいます。</p> <p>見られてはいけないデータが見られてしまいますし、そもそも利用量によって金額も増えてしまう恐れがありとても危険です。</p> <h2 id="匿名認証を使ったセキュリティ設定の一例"><a href="#%E5%8C%BF%E5%90%8D%E8%AA%8D%E8%A8%BC%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E8%A8%AD%E5%AE%9A%E3%81%AE%E4%B8%80%E4%BE%8B">匿名認証を使ったセキュリティ設定の一例</a></h2> <p>セキュリティ上問題ないと思われる、匿名認証を使ったシンプルな例をあげてみます。(一部誤りと追記修正を含んでいます)</p> <h3 id="全てのデータの読み書きを認証済みの場合のみ許可する"><a href="#%E5%85%A8%E3%81%A6%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E8%AA%AD%E3%81%BF%E6%9B%B8%E3%81%8D%E3%82%92%E8%AA%8D%E8%A8%BC%E6%B8%88%E3%81%BF%E3%81%AE%E5%A0%B4%E5%90%88%E3%81%AE%E3%81%BF%E8%A8%B1%E5%8F%AF%E3%81%99%E3%82%8B">全てのデータの読み書きを認証済みの場合のみ許可する</a></h3> <p>認証していない場合はあらゆるデータの読み書きを不可能にします。</p> <h3 id="匿名認証で誰もが必ず認証している状態にする"><a href="#%E5%8C%BF%E5%90%8D%E8%AA%8D%E8%A8%BC%E3%81%A7%E8%AA%B0%E3%82%82%E3%81%8C%E5%BF%85%E3%81%9A%E8%AA%8D%E8%A8%BC%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E7%8A%B6%E6%85%8B%E3%81%AB%E3%81%99%E3%82%8B">匿名認証で誰もが必ず認証している状態にする</a></h3> <p>これで、ログイン機能がなかったり、手動でログインしたりしなくても全てのユーザーが読み書きできるようになります。</p> <h3 id="ログイン可能なホスト名を本番環境のみにする"><a href="#%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E5%8F%AF%E8%83%BD%E3%81%AA%E3%83%9B%E3%82%B9%E3%83%88%E5%90%8D%E3%82%92%E6%9C%AC%E7%95%AA%E7%92%B0%E5%A2%83%E3%81%AE%E3%81%BF%E3%81%AB%E3%81%99%E3%82%8B">ログイン可能なホスト名を本番環境のみにする</a></h3> <p>Firebase ConsoleのAuthenticationの設定でログインできるホスト名を設定することができます。ここで本番のホスト名だけを認証可能にします。こうすることで、Firebaseの設定情報を使われてもデータにアクセスすることなどは不可能となります。</p> <p>localhost等も当然消しておきましょう。開発時には別途開発用のFirebaseアプリケーションを用意し、そちらの設定を使ってください。ビルド時にはcross-envを使うと良いと思います。(誤りがあるので追記をご覧ください)</p> <p>追記)<br /> このホスト設定はリダイレクト用のため、匿名認証には利用できないようです。今のところ簡単に制限する方法はなさそうです。適切なセキュリティルールを設定してください。ルールの制限がないデータは今のところ他者に自由遊ばれる事ができてしまうのかもしれません…。良い情報があればぜひ提供をお願いします。</p> <h2 id="APIキーを制限する"><a href="#API%E3%82%AD%E3%83%BC%E3%82%92%E5%88%B6%E9%99%90%E3%81%99%E3%82%8B">APIキーを制限する</a></h2> <p>Firebaseのコンソールでなく、GCPコンソールの方で制限が必要そうです。詳しくは下記に書きました。</p> <p><a href="https://crieit.net/posts/Google-API">GoogleからAPIキーが公開されているよとアラートが来た</a></p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>このように、匿名認証を入れることで本番でしかデータを扱うことができなくなり、セキュリティのベース部分の強化を行うことができます。(追記:前述の理由から、リダイレクトがないので本番以外でも扱うことはできるため、正しいセキュリティルール設定が必要です)</p> <p>とにかく、設定情報が丸見えというのは非常に恐ろしい話です。当記事の話も鵜呑みにせず、しっかりと色々なパターンを考えセキュリティを強化しておきましょう。(むしろ間違っている話があればご指摘をお願いします)</p> <p>Firebaseと連携するあらゆる処理をfunctionsに実装して設定情報は公開しないようにする、くらいの強固さで考えるくらいでも良いかもしれません。(認証の連携ができないので厳しいかもしれませんが)</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14500 2018-08-06T21:59:54+09:00 2018-10-31T16:37:36+09:00 https://crieit.net/posts/Firestore Firestoreの参照型によるリレーションを試す <p>FirebaseのFirestoreはNoSQLのため、コレクションの下にサブコレクションを登録するような形でデータをネストして登録していくことができます。ただ、これではRDBでやっているようなリレーションが実装できず、データが色々なところに分散してしまうため、各データを連携させる必要がある場合には非常に使いづらいです。一応リレーションもできるっぽいので、ちょっと試してみました。</p> <p>※2018/8月現在、Firestoreがβバージョンのタイミングの記事となります。環境はJavaScriptです。</p> <p>ちなみに、もうちょっと詳しく考察されている記事があります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/1amageek/items/d2ef7a49bccf5b4ea78e">Cloud FirestoreのSubCollectionとQueryっていつ使うの問題</a></p> <p>多分これのリレーションシップ参照型というところの話です。上記の記事ではManyToMany, hasManyあたりの話をしていると思いますが、今回試したのはbelongsToの話になります。</p> <h2 id="参照型"><a href="#%E5%8F%82%E7%85%A7%E5%9E%8B">参照型</a></h2> <p>Firestoreに格納するデータには色々型があります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/manage-data/data-types?hl=ja">https://firebase.google.com/docs/firestore/manage-data/data-types?hl=ja</a></p> <p>この中を見ると「参照」という型があります。が、概念的なものが書かれているだけで、他のページを探してみても説明が見つかりません。(ただ見つけられていないだけか、βだからかは分かりません)</p> <p>そのためとりあえずこれを使って1:1のリレーションができるか試してみました。</p> <h3 id="そもそも参照型と何か"><a href="#%E3%81%9D%E3%82%82%E3%81%9D%E3%82%82%E5%8F%82%E7%85%A7%E5%9E%8B%E3%81%A8%E4%BD%95%E3%81%8B">そもそも参照型と何か</a></h3> <p>データを追加するところのマニュアルを見てみると、addした後に<code>docRef</code>という値が取得できています。もしかするとこれかな? と思い、別のデータを保存する際にこのdocRefをそのまま代入して保存してみました。</p> <pre><code class="javascript"> // userのdocRefをそのまま入れる this.post.user = this.$store.state.user; db .collection("posts") .add(this.post) .then(docRef => { this.posts = getPosts(); }) .catch(error => console.log(error)); </code></pre> <p>Firebaseのconsoleで見てみるとちゃんと入っていました。</p> <pre><code>body: "aaaa" user: /users/scQa34jZbdF2mpuVcMMV </code></pre> <p>という感じで、参照型の保存自体はこんな感じでできるようです。</p> <h3 id="リレーションデータの取得"><a href="#%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%8F%96%E5%BE%97">リレーションデータの取得</a></h3> <p><del>取得までは試していません。</del></p> <p>ざっと1ファイルで適当に試してみました。適当なので実際に組み込む時は並列処理したりもっと良い書き方ができると思います。</p> <pre><code class="javascript">const firebase = require("firebase"); const config = { }; firebase.initializeApp(config); const db = firebase.firestore(); async function execute() { // ユーザーデータを登録 const userData = { name: "name" + (new Date()).getTime(), }; const userRef = await db.collection("users").add(userData); // ユーザーデータを紐づけて投稿を登録 const postData = { name: "post" + (new Date()).getTime(), user: userRef }; const postRef = await db.collection("posts").add(postData); // 登録したデータを全部取得 const querySnapshot = await db.collection("posts").get(); const posts = []; querySnapshot.forEach(doc => { posts.push(doc.data()); }); for (let i = 0; i < posts.length; i++) { // リレーションデータを取得 const post = posts[i]; const userQuerySnapshot = await post.user.get(); post.userData = userQuerySnapshot.data(); } return posts; } execute().then(posts => { console.log(posts); process.exit(0); }); </code></pre> <p>とりあえず試しに動かしたサンプルも置いておきます。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/dala00/firestore-reference-sample">Firestore Reference data type sample</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14498 2018-08-03T20:50:24+09:00 2018-11-25T22:03:58+09:00 https://crieit.net/posts/GAE-Nuxt-SSR-Firestore GAE+NuxtのSSRでFirestoreを使う <p><a href="https://crieit.net/posts/App-Engine-Nuxt-SSR">App Engineの標準環境でNuxtを使って無料SSR</a> を先日やってみたので、今度はFirebaseのCloud Firestoreでデータの保存を試してみました。</p> <p>ちなみにGAEのスタンダード環境ではNode8が動くため、ローカルでも全てNode8で動かす必要があります。</p> <p>※また後日記事を書きますが、通常セキュリティルールが入っているため、実際にはcloud functions経由でfirebase-adminを利用してデータを取ってくる必要があります。</p> <h2 id="Firebaseの初期設定"><a href="#Firebase%E3%81%AE%E5%88%9D%E6%9C%9F%E8%A8%AD%E5%AE%9A">Firebaseの初期設定</a></h2> <p>plugins/firebase.jsに下記のように設定を保存します。</p> <pre><code class="javascript">import * as firebase from "firebase"; require("firebase/firestore"); if (!firebase.apps.length) { const config = { apiKey: "your firebase api key", authDomain: "your firebase auth domain", databaseURL: "your firebase database url", projectId: "your google cloud platform project id", storageBucket: "your google cloud platform storage bucket", messagingSenderId: "your firebase messaging sender id" }; firebase.initializeApp(config); } firebase.firestore(); // もしかしたら要らないかも </code></pre> <p>そしてnuxt.config.jsで上記を読み込むようにしておきます。</p> <pre><code class="javascript"> plugins: ["~/plugins/firebase.js"], </code></pre> <p>これでFirebaseのライブラリがNuxt上で利用できるようになります。</p> <h2 id="Firestoreへのデータ追加&取得"><a href="#Firestore%E3%81%B8%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E8%BF%BD%E5%8A%A0%EF%BC%86%E5%8F%96%E5%BE%97">Firestoreへのデータ追加&取得</a></h2> <p>元々あるindex.vueを下記のように修正し、データの一覧を追加してボタンを押すとデータを登録できるようにした最小のサンプルを作ってみました。(関係ないところは削っています)</p> <h3 id="テンプレート"><a href="#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88">テンプレート</a></h3> <p>テンプレートは単にデータを表示しているだけで特筆するところは無いです。</p> <pre><code class="html"><template> <section class="container"> <div> <div v-for="text in texts" :key="text.id"> <span>{</span><span>{</span> text.body <span>}</span><span>}</span> </div> <input type="text" v-model="textInput"> <button type="button" @click="add">add</button> </div> </section> </template> </code></pre> <h3 id="スクリプト"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88">スクリプト</a></h3> <h4 id="初期化"><a href="#%E5%88%9D%E6%9C%9F%E5%8C%96">初期化</a></h4> <pre><code class="javascript">import db from "../lib/db"; </code></pre> <p>DBを簡単に使える様にインポートしてすぐ使えるようにしています。これは別に必ず作らなければならないわけではありません。中身は単にdbを初期化して取ってきているだけです。</p> <pre><code class="javascript">import * as firebase from "firebase"; export default firebase.firestore(); </code></pre> <h4 id="データをFirestoreから取得してくる"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92Firestore%E3%81%8B%E3%82%89%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6%E3%81%8F%E3%82%8B">データをFirestoreから取得してくる</a></h4> <p>ページを読み込むと、まずFirestoreからデータを取ってきて一覧します。</p> <pre><code class="javascript">async function getTexts() { return new Promise(callback => { let texts = []; db .collection("texts") .get() .then(query => { query.forEach(doc => { texts.push(doc.data()); }); callback(texts); }); }); } export default { data() { return { texts: [], textInput: "" }; }, async asyncData({ params }) { return { texts: await getTexts() }; } } </code></pre> <p>データの初期化にmountedを使うことは多いと思いますが、これはサーバーサイドレンダリングでは動作しないため、代わりに<code>asyncData</code>や<code>fetch</code>を使う必要があります。<code>asyncData</code>は下記URLに使い方が載っていますが、async/awaitを使ったり、Promiseやcallback形式など自由に選べるようです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/guide/async-data/">非同期なデータ - Nuxt.js</a></p> <p>あとこの中では<code>this</code>が使えないため、仕方なく適当にクラスの外にfunctionを定義してそれを呼び出しています。</p> <p>あ、あとasyncDataはコンポーネントでは使えないため、今回はページ上で実行しています。実際の開発の際もコンポーネントで値を使いたい場合はプロパティで渡すか、storeを使うかになると思います。</p> <h4 id="データをFirestoreに追加する"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92Firestore%E3%81%AB%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B">データをFirestoreに追加する</a></h4> <p>あとは追加ボタンをクリックした時の処理を追加します。</p> <pre><code class="javascript"> methods: { add() { db .collection("texts") .add({ body: this.textInput }) .then(docRef => { this.textInput = ""; return getTexts(); }) .then(texts => (this.texts = texts)); } } </code></pre> <p>追加してとりあえず先程の一覧取得処理を実行しているだけです。</p> <p>これでFirestoreにデータが保存され、画面上にも表示されます。Firebase Consoleのデータ一覧でも確認できます。</p> <h2 id="サーバーサイドレンダリングされているか確認"><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%82%B5%E3%82%A4%E3%83%89%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%81%8B%E7%A2%BA%E8%AA%8D">サーバーサイドレンダリングされているか確認</a></h2> <p>Google App Engineで動作させればサーバーサイドレンダリングされますので、一度ページをリロードして確認すればソース上に登録した文字列が存在するのが確認できると思います。(間違って<code>mounted</code>で処理しているとSSRされません)</p> <p>FIrebaseだとデプロイもあっというまでサーバー側の処理とか何も気にしないでいいので楽ですね!</p> <p>あとはFirestoreには<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/firestore/query-data/listen?hl=ja">リアルタイム アップデート</a>の機能もありますので、一覧取得とかは頻繁に更新されるのであればそっちでやってみるのも良いかもしれません。</p> だら@Crieit開発者