【個人開発】「お前のサービス使ってレビュー書くから、俺のサービスも使ってみてくれよ！」というサービスを公開しました


初めまして。Kigiと申します。

この度、個人開発者向け相互レビューサービス【sougo-review】を公開しました。

https://sougo-review.com/

# sougo-reviewとは？
タイトルの通り、
**「お前のサービス使ってレビュー書くから、俺のサービスも使ってみてくれよ！」**
というサービスです。

個人開発者が自分のサービスを登録します。
その後、他の人のサービスに対してリクエストを送ります。
リクエストが承認されると、レビューを送ることができます。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1976373/ddc1d8e9-f8f8-4f18-f5a6-970844a0b35e.png)

これだけです。
[実際のサービスページ](https://sougo-review.com/detail/kNiJxTVNRzfk88up1DLw)

しかし、これだけで個人開発者にとって嬉しいことが二つ起きます。
#### ①自分のサービスに【ユーザー】が増えます。
#### ②自分のサービスに【レビュー】がもらえます。

他にも
* 他の人のサービスを使ってレビューを送ることで、自分の個人開発に活かせることを学ぶことができる
* 個人開発者同士の繋がりが生まれる
といった副次的な効果もあります

# 技術スタック
sougo-reviewを実現するにあたって、技術要素は以下を採用しました。

### サービス
##### フロントエンド
* nuxt.js
    * Vuetify
 
##### バックエンド
* Firebase
    * Authentication(認証部分)
    * Functions(条件によるステータス更新などの複雑な処理)
    * Storage(画像保存先)
    * Hosting(サービスの公開)

##### データベース
* Firebase
    * Firestore
##### 解析ツール
* Google Analytics
* Clarity

##### サービスページ
作成したサービスページです。サービスの更新情報などを掲載する目的で準備しました。

https://sougo-review-article.vercel.app/
* フロントエンド
    * nuxt.js(下記Newtが提供するテンプレートを使用)
* ヘッドレスCMSサービス
    * [Newt](https://www.newt.so/)
* ホスティング(公開方法)
    * Vercel

# 個人開発のノウハウ
私自身が始めて導入したまたは導入に手間取った内容を共有します。
紹介するのは
* nuxt.jsへのClarity導入方法
* Firebaseの料金情報をリアルタイムで取得する→指定額を超えたらFirestore停止

### nuxt.jsへのClarity導入方法
[Clarity](https://clarity.microsoft.com/)とは無料で使うことができるweb分析ツールです。Google Analyticsとの使い分けとしては[ヒートマップツール]があります。公式ページにある通り訪問したユーザーがどこをタップしたり、どこまでスクロールしたか可視化されます。Firebaseを使っているとGoogle Analyticsを有効にすることも可能だと思いますが、Clarityも合わせて導入することでより高度な分析が可能になります。

導入方法として、公式サイトからサインアップをすると[Add new Project]が表示されますので
Name : プロジェクトの名前(自由でOK)
Website URL : 公開する予定のURL
を設定します。登録する前にドメインをとっておくと後で変更せずに済むのでスムーズでしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1976373/dcff00ed-0a6f-ba74-c103-03332643dbf3.png)

[Add new project]をクリックすると、プロジェクト画面に遷移します。遷移したら画面左にある[Install manually]の[Get traking code]を選択します。
![スクリーンショット 2022-08-21 0.26.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1976373/13bd7a49-fa57-493a-3de8-55be48bcdfda.png)

選択すると画面上にscriptタグが表示されます。このタグ内にあるfunctionを全てコピーします。先頭の()文字もコピーしてください。
![スクリーンショット 2022-08-21 0.30.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1976373/a058904d-e112-c836-bc77-85f3758fbbfe.png)

最後に導入プロジェクトの[nuxt.comfig.js]のhead項目にscriptを追加します。scriptの内容は上記でコピーしたClarityのscriptタグのfunctionを貼り付けます。
```nuxt.config.js
export default {
  // 省略
  head : {
    script: [      
      {
        innerHTML: `
        // ※※※※※※※※※※※※※※※※※※※※※※※※※※※※
        // ここにClarityでコピーしたfunctionの情報を貼り付けます
        // ※※※※※※※※※※※※※※※※※※※※※※※※※※※※
        `
      }
    ],
    __dangerouslyDisableSanitizers: ['script'],
  }
  // 省略
}
```

これで準備は完了です。サービス公開後に分析がスタートしていることを確認してください。

### Firebaseの料金情報をリアルタイムで取得する→指定額を超えたらFirestore停止
サービスを運営する上で、コストは発生します。
Firebaseの利用料金は非常に安いのですが、私のような心配性は料金が気になってしまいます。
そのため、簡単にFirebaseの利用料金を把握できて、もし出来ればある一定額の利用料金を超えたらサービス停止をする仕組みが欲しいと考えました。

もともとFirestoreの1日の使用量の制限を設定することが可能でしたが、2019年の12月より設定が不可になりました。

https://firebase.google.com/docs/firestore/quotas?hl=ja#manage_spending

そのため、今回はFirebaseのfunctionsを使用して、料金情報を監視する仕組みを作成します。
なお、こちらの仕組みはfunctionsを使用することから[Spark(無料)]プランから[Blaze(従量制)]に変更する必要があります。
下記ページを参考にさせていただきました。

https://rinoguchi.net/2020/08/firestore-cost-risk.html#:~:text=%E3%81%8C%E8%89%AF%E3%81%95%E3%81%9D%E3%81%86%E3%81%A7%E3%81%99%E3%81%AD%E3%80%82-,%E2%91%A1%E4%BA%88%E7%AE%97%E6%9E%A0%E3%82%92%E8%B6%85%E3%81%88%E3%81%9F%E3%82%89Firestore%E3%82%92%E8%87%AA%E5%8B%95%E7%84%A1%E5%8A%B9%E5%8C%96,-%E3%81%93%E3%81%A1%E3%82%89%E3%82%92%E3%81%BF%E3%82%8B

最終的に作成したfunctionsの関数です。今回はjsで作成しております。
料金情報をSlackで通知したいので、予め下記コマンドを実行して必要ライブラリをインストールします。
```bash
npm i firebase-functions
npm i firebase-admin
npm i googleapis
npm i @slack/webhook # slack通知
```

Firebaseの料金情報をSlackに送信するコードです。
```billing.js
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const {google} = require("googleapis")

const { IncomingWebhook } = require("@slack/webhook");
// URLは通知したいslackのwebhook URLを指定
const webhook = new IncomingWebhook("https://hooks.slack.com/services/...");


exports.disableAppEngine = functions
  .region('asia-northeast1')
  .pubsub.topic('gcp-topic') // トピック名を指定する ※1
  .onPublish(async (message) => {
    const data = JSON.parse(Buffer.from(message.data, 'base64').toString())
    // 現在の請求額
    const costAmount = data.costAmount
    // Firebaseに設定した請求額の閾値
    const budgetAmount = data.budgetAmount
    
    try {
        (async () => {
            await webhook.send({
              text: `costAmount: ${costAmount}, budgetAmount: ${budgetAmount}`,
            });
        })();
    } catch (error) {
        console.log("error send slack!")
        console.log(error)
    }
        
    
    
    if (costAmount <= budgetAmount) {
      // 閾値以下の場合. 処理を終了
      return
    }

    const auth = new google.auth.GoogleAuth({
      scopes: ['https://www.googleapis.com/auth/cloud-platform'],
    })
    const authClient = await auth.getClient()
    google.options({ auth: authClient })

    const appengine = google.appengine('v1')
    const res = await appengine.apps.patch({
      appsId: 'firebase-project-name', // FirebaseのプロジェクトIDを指定 ※2
      updateMask: 'serving_status',
      requestBody: { servingStatus: 'USER_DISABLED' },
    })
})

```
上記コードについて捕捉します。

##### ※1 Pub/Subのトピック名について
参考ページの通り、Pub/Sub名を指定します。functionsで指定するのは自身で入力したトピックIDを指定します。
![スクリーンショット 2022-08-21 1.06.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1976373/f6b37166-b465-98b0-8e27-42b3eb18b1c6.png)

##### ※2 FirebaseのプロジェクトIDについて
Firebaseのプロジェクト設定画面にプロジェクトID項目があります。このプロジェクトIDを指定します。

この関数をデプロイするとSlackに料金情報が通知されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1976373/b1e2edd0-a400-f93e-ccf8-dc2d1252ea2e.png)
料金情報を定期的に確認しておくことが可能です。

# 最後に
個人開発は楽しいことです。サービスを公開するときのワクワク感は忘れられません。そのワクワクに[ユーザー]がついて[レビュー]を貰えることでさらにワクワクするような、そんなサービスにしたいと思っています。
サービスの運営や改善等頑張っていきますので、よろしくお願いします。

https://sougo-review.com/

https://sougo-review-article.vercel.app/

https://forms.gle/PdgtVB6pns1pEFRj7

# 参考
https://clarity.microsoft.com/

https://www.axis-corp.com/quest/what-is-microsoft-clarity/

https://rinoguchi.net/2020/08/firestore-cost-risk.html

# 
本記事はQiitaイベント[エンジニア夏休み企画！]の個人開発の成果として作成しました。

https://qiita.com/official-events/6d31965c499a69377c0b