React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発④

前回では`Firebase Authentication`を使ってログイン機能の実装と周辺機能の実装を終えた．

今回は遂に完成させてデプロイまでいく．

[React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発①](https://qiita.com/momosuke/items/245eabb8dbdc3493aac0)　
[React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発②](https://qiita.com/momosuke/items/57295ca99387ab243ce8)　
[React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発③](https://qiita.com/momosuke/items/a79564e2234d9e4fc0bd)　
[React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発④]()　←今ここ！！！

#タイムスタンプを適切な時間フォーマットへ
コンテンツ外観はかなり煮詰まってきたが，`ProjectSummary`と`ProjectDetails`での日付・時間表示がハードコーディングのままだ．

思い出して欲しいのは新しいプロジェクトを追加した時に`createdAt`というフィールドを`firestore`の`projects`コレクションに保存したはずだ．この値は`タイムスタンプ`だ．

これを適切なフォーマットに変換して表示するように，まずは`ProjectSummary`から修正していこう．

`toDate()`メソッドはタイムスタンプをうまく変換してくれるが，こいつは`Dateオブジェクト`と呼ばれるものなのでこのままだとエラーが出る．

なので`toString()`メソッドで文字列に変換してあげる．

後，`projects`の中にテスト用に作った`createdAt`プロパティを持たないドキュメントがあるので手動で削除しよう．しないとエラーが出る．

```projects/ProjectsSummary.js
import React from 'react'

const ProjectSummary = ({project}) => {
    return (
        <div className="card z-depth-0 project-summary">
            <div className="card-content grey-text text-darken-3">
                <span className="card-title">{project.title}</span>
                <p>Posted by the {project.authorFirstName} {project.authorLastName}</p>
                <p className="grey-text">{project.createdAt.toDate().toString()}</p>
            </div>
        </div>
    )
}

export default ProjectSummary;
```
<img width="1680" alt="スクリーンショット 2019-10-28 22.22.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/03e4209f-ae6a-47ca-e6d1-c6c2135d9d9e.png">
一応，人間様がみれるフォーマットにはなったが，なんだか不格好だ．

なので[Moment.js](https://momentjs.com/) と呼ばれるパッケージを使ってお好みなフォーマットを実装しよう．

```terminal
npm install moment
```

インポートして使ってみる．
`Moment.js`ではDateオブジェクトを扱うので`toString`は消して全体を`moment`関数で囲み，そこにお好みのメソッドをつける．
今回は`calendar`メソッドを使う．他にもたくさん綺麗なフォーマットがあるので公式参照．

```projects/ProjectSummary.js
import React from 'react'
import moment from 'moment'

const ProjectSummary = ({project}) => {
    return (
        <div className="card z-depth-0 project-summary">
            <div className="card-content grey-text text-darken-3">
                <span className="card-title">{project.title}</span>
                <p>Posted by the {project.authorFirstName} {project.authorLastName}</p>
                <p className="grey-text">{moment(project.createdAt.toDate()).calendar()}</p>
            </div>
        </div>
    )
}

export default ProjectSummary;
```
<img width="1680" alt="スクリーンショット 2019-10-28 22.32.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/31412d8a-77cf-f452-7407-d1bd31251550.png">

同じことを`ProjectDetails`にも実装していく．

```projects/ProjectDetails.js
import React from 'react'
import { connect } from 'react-redux'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
import { Redirect } from 'react-router-dom'
import moment from 'moment'

const ProjectDetails = (props) => {
    const { project, auth } = props;
    if (!auth.uid) return <Redirect to='/signin' />
    if (project) {
        return (
            <div className="container section project-details">
                <div className="card z-depth-0">
                    <div className="card-content">
                        <span className="card-title">{ project.title }</span>
                        <p>{ project.content }</p>
                    </div>
                    <div className="card-action gret lighten-4 grey-text">
                        <div>Posted by {project.authorFirstName} {project.authorLastName}</div>
                        <div>{moment(project.createdAt.toDate()).calendar()}</div>
                    </div>
                </div>
            </div>
        )
    } else {
        return (
            <div className="container center">
                <p>Loaging project...</p>
            </div>
        )
    }
    
}

const mapStateToProps = (state, ownProps) => {
    const id = ownProps.match.params.id;
    const projects = state.firestore.data.projects;
    const project = projects ? projects[id] : null
    return {
        project: project,
        auth: state.firebase.auth
    }
}

export default compose(
    connect(mapStateToProps),
    firestoreConnect([
        { collection: 'projects' }
    ])
)(ProjectDetails);
```
<img width="1680" alt="スクリーンショット 2019-10-28 22.35.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/1815a185-d876-9a67-9329-f2ee452b2dc8.png">

#Firestoreセキュリティルール
今，`Firestore`はテストモードなので，誰でも読み書きが可能な状態にある．

現在のfirestoreのセキュリティルールをみると以下のようになっている．

<img width="1680" alt="スクリーンショット 2019-10-28 22.40.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/6d0eba6e-0020-e960-f6c7-e0974fe2600f.png">
1行目はセキュリティルール言語のバージョン情報なのであまり気にしないでいい．
2行目はルールが`firestore`データベースのみに適用されることを表している．
3行目はルールがプロジェクト内の全ての`firestore`データベース（我々は元々1つしかないけど）に適用されることを表している．

なのでこの3行には手を加えないでいい．

4行目はルールがデータベースの全てのドキュメントに適用されることを表している．
5行目はデータベースの全てのドキュメントが誰にでも読み書き可能であることを設定している．

この設定は開発にはとても快適だが，いざデプロイする際には絶対にセキュリティをロックする必要がある．

シミュレータを使用して様々な状況をシミュレーションできる．
<img width="1680" alt="スクリーンショット 2019-10-28 22.55.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/5715d492-ee65-2d21-c5d8-8571b95faa06.png">
上画像では特定のプロジェクトの情報をログインしていないユーザーが`GET`リクエストした場合のシミュレートだが，しっかりアクセスできてしまっている．

こんな感じでこのままだとデータベース内の情報にアクセスされるだけでなく，更新や削除もされてしまう危険性がある．

なので早速セキュリティルールを書き換えていこう．

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{project} {
      allow read, write: if request.auth.uid != null
    }
  }
}
```
4行目はルールを`projects`コレクションの全てのドキュメントに適用することを表している．
5行目で`request.auth.uid`でログインステータスを確認し，ログインしているユーザーには読み込みと書き込みを許可している．

次に`users`コレクションに対するルールも作っていく．

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{project} {
      allow read, write: if request.auth.uid != null
    }
    match /users/{useId} {
      allow create 
      allow read: if request.auth.uid != null
    }
  }
}
```
7行目はルールが`users`コレクションの全てのドキュメントにも適用されることを表している．
8行目は誰でもユーザーを登録することを許可している．
9行目はログインしているユーザーなら他のユーザー情報をみることを許可している．（ダッシュボード画面で他人のプロジェクトが見られるように）

以上のルールを書き込み`公開`を押す．適用には数分かかることがあるので，少し待ってプロジェクトを作ったりしてアプリの動作がおかしくなってないか確認しよう．

今回実装してセキュリティルールはとても基本的なことだけなので，実際の開発では[公式ドキュメント](https://firebase.google.com/docs/rules?authuser=0)を読んで他にどんなルールを敷けるのか調べてみよう．

#Cloud Functionイントロ＆セットアップ
ここまででReact Appを作ってきたが，ここまでのJavaScriptコードが全てクライアントサイド，ブラウザで実行されるものだ．コードを実行するのにサーバーを使用しなかった．

しかしいくつかのタスクに取り組むに際してサーバーサイドでコードを実行したい時が出てくる．

例えばクライアントサイドからはアクセスできないデータの編集などだ．

そのために`Cloud Function`を使えばコードをサーバーサイドで実行してくれる．
ローカルでコードを書いてfirebaseにデプロイするだけでいい．

それでは書く前に`Cloud Function`のセットアップをしていこう．

Firebaseプロジェクトの`functions`ページに行って`使ってみる`をクリックすると，cloud functionを使うためのステップが出てくるの1つ1つやっていこう．

まず`firebase-tools`のインストール

```terminal
npm install -g firebase-tools
```
次に`firebase init`と`firebaes deploy`しろと出てくるが，その前にログインしないといけない．
もし初回ログインなら，ブラウザが開くと思う．
このコマンドによりローカルマシンが Firebase に接続され，Firebase プロジェクトへのアクセスが許可される．

```terminal
firebase login
```
次に`firebase init`でfirebaseプロジェクトをフロントエンド側で初期化して`deploy`するための準備をする．

```terminal
firebase init
```
`Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices.`のところは`Function`と`Hosting`だけを選択しよう．

`Firestore`を選択しないのはもうオンライン上でルールを設定しデプロイしたからだ．

次のところは`Use an existing project `を選択．
次のところは今回作成しているfirebaseプロジェクトを選択．
次のところは`JavaScript`を選択
次のところは`no`を選択
次のところは`yes`を選択
次のところは`build`を選択
次のところは`no`を選択

これでセットアップが完了した．アプリに`functions`ファイルができていると思う．ここに`Cloud Function`用のコードを書いてデプロイする．

#シンプルなCloud Fucntionを書いてみる

まず`functions`フォルダにある`index.js`にあらかじめ用意されたダミーコードを説いていく．

1行目でモジュールをインポートしている．このモジュールの様々なプロパティを使ってコードを書いていく．

node.jsを書いたことのある人には馴染みやすいコードだと思う．特にexpressを書いたことのある人には．

```functions/index.js
const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello, ninjas!");
});
```
この状態でfunctionsだけをデプロイしてみる．

```
firebase deploy --only functions
```
デプロイ後に表示されるURLで`Cloud Function`が実行できるのでアクセスしてみよう．
<img width="1680" alt="スクリーンショット 2019-10-29 0.21.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/d67c2b1b-bd0a-2c02-d1f7-5e7bb31b59cc.png">
firebaseプロジェクトのfuncionsページでも確認します．
`helloWorld`という関数が`HTTPリクエスト`をトリガーに実行されることがちゃんと書いてある．
<img width="1680" alt="スクリーンショット 2019-10-29 0.22.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/f098d7a5-1b49-eeee-aa52-89682fc4685b.png">
ということでとりあえず`Cloud Fucntion`の基礎がわかったところで次は`Cloud Function`を使って，`Notifications`の実装に取り組んでいきたいと思う．
Notificationsは誰かがサインアップしたりプロジェクトを作ったことをトリガーに通知するパネルのようなものである．

#Notificationコンポネント
具体的に何をトリガーにするかというと，誰かが`projects`コレクションにドキュメントを追加した時か，誰かが`Firebase Authentication`を使ってサインアップしたことをトリガーにしたい．

とはいえ現時点での`Notification`コンポネントはあまりに質素すぎるので，`Cloud Function`云々よりまずコンポネントの外観を作っていこう．

```dashboard/Notification.js
import React from 'react'

const Notification = (props) => {
    return (
        <div className="section">
            <div className="card z-depth-0">
                <div className="card-content">
                    <span className="card-title">Notifications</span>
                    <ul className="notifications">
                        <li>Notification</li>
                        <li>Notification</li>
                        <li>Notification</li>
                        <li>Notification</li>
                    </ul>
                </div>
            </div>
        </div>
    )
}

export default Notification;
```
これで外観はいい感じなので，次から実際にCloud Funcitonを書いていこうと思う．

#Firestoreのトリガー設定①
`Cloud Function`で2つの関数を実装していきたい．

1つはユーザーが新しいプロジェクトを作成した時，2つ目は新しいユーザーがサインアップした時をトリガーにする関数だ．

まずは前者に関する関数を実装していこう．

`firebase`内で他のサービス（AuthやFirestore）にアクセスするためにはまず`firebase-admin`をインポートして初期化する必要がある．

`projectCreated`という関数を作成する．
詳しく知りたい人は[Cloud Functions 公式ドキュメント](https://firebase.google.com/docs/functions/firestore-events?hl=ja)を参照して欲しいのだが，ここでは`projects`コレクションに新しいドキュメントが作られたことをトリガーに設定している．

中では，`doc`というレスポンスを使って`notification`オブジェクトを作成している．

```functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase)

exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello, ninjas!");
});

exports.projectCreated = functions.firestore
    .document('projects/{projectId}')
    .onCreate(doc => {

        const project = doc.data();
        const notification = {
            content: 'Added a new project',
            user: `${project.authorFirstName} ${project.authorLastName}`,
            time: admin.firestore.FieldValue.serverTimestamp()
        }
})
```

ここまではトリガーと前処理しか書いていない．
あとはこの`notification`オブジェクトをfirestoreの`notifications`コレクションに追加したい．

そこで`notifications`コレクションにドキュメントを追加する関数を外側に定義して`projectCreated`で呼び出してあげよう．
この関数は単に`projectCreated`で呼び出すだけのものなので，Cloud Functions用の関数の書き方でなく，通常のJS文法でかける．
なお，Cloud Functions用の関数は何かしらのレスポンスが必要なので`return`する形で関数を呼び出している．

```functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase)

exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello, ninjas!");
});

const createNotification = (notification => {
    return admin.firestore().collection('notifications')
        .add(notification)
        .then(doc => console.log('notification added', doc));
})

exports.projectCreated = functions.firestore
    .document('projects/{projectId}')
    .onCreate(doc => {

        const project = doc.data();
        const notification = {
            content: 'Added a new project',
            user: `${project.authorFirstName} ${project.authorLastName}`,
            time: admin.firestore.FieldValue.serverTimestamp()
        }

        return createNotification(Notification)

})
```

それではこの関数をデプロイして確認してみよう．

```terminal
firebase deploy --only functions
```
<img width="1680" alt="スクリーンショット 2019-10-29 10.10.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/f3aab8b7-e822-c68e-6081-de864adb1917.png">
ちゃんとトリガーが起動するかどうか確かめるために新しいプロジェクトを作ってみよう．
<img width="1680" alt="スクリーンショット 2019-10-29 10.12.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/3211e4bf-54af-04b8-520e-f52e1335d99f.png">
<img width="1680" alt="スクリーンショット 2019-10-29 10.16.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/4dc5c360-0a3f-3b54-1131-42e8b5f74e34.png">

#Firestoreのトリガー設定②
次は誰かがサインアップしたことをトリガーにする関数を実装していく．

ここでも詳しい説明は[Cloud Functions 公式ドキュメント](https://firebase.google.com/docs/functions/firestore-events?hl=ja)に任せて，概要を説明していく．

新しいユーザーが作られたことをトリガーに`.doc(user.uid).get()`で作られたユーザーの情報を`users`コレクションから取ってきて，データを元に`notification`オブジェクトを作成．それを`createNotification`関数へ渡す．

```functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase)

exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello, ninjas!");
});

const createNotification = (notification => {
    return admin.firestore().collection('notifications')
        .add(notification)
        .then(doc => console.log('notification added', doc));
})

exports.projectCreated = functions.firestore
    .document('projects/{projectId}')
    .onCreate(doc => {

        const project = doc.data();
        const notification = {
            content: 'Added a new project',
            user: `${project.authorFirstName} ${project.authorLastName}`,
            time: admin.firestore.FieldValue.serverTimestamp()
        }

        return createNotification(notification);

})

exports.userJoined = functions.auth.user()
    .onCreate(user => {

        return admin.firestore().collection('users')
            .doc(user.uid).get().then(doc => {
                
                const newUser = doc.data();
                const notification = {
                    content: 'Joined the party',
                    user: `${newUser.firstName} ${newUser.lastName}`,
                    time: admin.firestore.FieldValue.serverTimestamp()
                }

                return createNotification(notification);

            })

})
```

先ほどと同じようにデプロイして確認してみよう．

```terminal
firebase deploy --only functions
```
<img width="1680" alt="スクリーンショット 2019-10-29 10.35.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/e9cc9df4-5c85-cd8d-df5d-c80e173394ac.png">
ユーザーを作ってみて`notifications`コレクションに適切に保存されるか確認しよう．
<img width="1680" alt="スクリーンショット 2019-10-29 10.45.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/9bc8d7ed-b2e8-6b81-0b7e-21779e44c28f.png">
<img width="1680" alt="スクリーンショット 2019-10-29 10.46.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/3af7da06-0c6c-c633-1cb8-d875742431bb.png">

#Notification表示
あとは`notifications`コレクションのドキュメントを取得して`Notification`コンポネントで表示してやろう．

そのためにはこのコンポネントとRedux`state`とを`connect`しないといけない．
`Notification`をラップしている`Dashboard`ではすでにつながっているので後は`notifications`コレクションとの同期をするだけだ．

特定の数（3）しか通知を表示したくないので`limit`を3に設定した．

```dashboard/Dashboard.js
import React, { Component } from 'react'
import Notification from './Notification'
import ProjectList from '../projects/ProjectList'
import { connect } from 'react-redux'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
import { Redirect } from 'react-router-dom'

class Dashboard extends Component {
    render() {
        const { projects, auth, notifications } = this.props;
        if (!auth.uid) return <Redirect to='/signin' />

        return (
            <div className="dashboard container">
                <div className="row">
                    <div className="col s12 m6">
                        <ProjectList projects={projects} />
                    </div>
                    <div className="col s12 m5 offset-m1">
                        <Notification notifications={notifications} />
                    </div>
                </div>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        projects: state.firestore.ordered.projects,
        auth: state.firebase.auth,
        notifications: state.firestore.ordered.notifications
    }
}

export default compose(
    connect(mapStateToProps),
    firestoreConnect([
        { collection: 'projects' },
        { collection: 'notifications', limit: 3 }
    ])
)(Dashboard);
```

後は`Notification`コンポネント側で適切に処理する．

```dashboard/Notification.js
import React from 'react'
import moment from 'moment'

const Notification = (props) => {
    const { notifications } = props;
    return (
        <div className="section">
            <div className="card z-depth-0">
                <div className="card-content">
                    <span className="card-title">Notifications</span>
                    <ul className="notifications">
                        { notifications && notifications.map(item => {
                            return (
                                <li key={item.id}>
                                    <span className="pink-text">{item.user} </span>
                                    <span>{item.content}</span>
                                    <div className="grey-text note-date">
                                        {moment(item.time.toDate()).fromNow()}
                                    </div>
                                </li>
                            )
                        })}
                    </ul>
                </div>
            </div>
        </div>
    )
}

export default Notification;
```

これでもう終わりに見えるが，実はまだやってないことがある．
`notifications`コレクションのセキュリティルールを設定していないのだ．
なのでこの状態ではエラーが出ると思う．

ということでルールを実装していこう．

ログインしているユーザーならいかなる`notification`データを読めるようにする．そうしないと`Notification`コンポネントのところが見れなくなってしまう．

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{project} {
      allow read, write: if request.auth.uid != null
    }
    match /users/{useId} {
    	allow create 
      allow read: if request.auth.uid != null
    }
    match /notifications/{notification} {
      allow read: if request.auth.uid != null
    }
  }
}
```

これでうまく表示されるはずだ．新しいプロジェクトを追加してみて数秒後に`Notification`の載るかどうか確認してみよう．
<img width="1680" alt="スクリーンショット 2019-10-29 11.08.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/63a51bd6-e2a5-b4d8-8f67-ee7fe671c9bf.png">

#データの順番
実装中に気づいていた人も多いだろう．

今の段階では`ProjectList`も`Notification`もランダムな順番でデータを羅列しているのだ．
プロジェクト一覧はまだしも通知欄がランダムに3つ取ってくるようでは致命的にダメだろう．

今回はその問題を解決していきたいと思う．

方法はめちゃくちゃ簡単だ．

`Dashboard`の`firestoreConnect`のところで`orderBy`で基準にしたいプロパティ整列の方向を指定してあげるだけだ．

```dashboard/Dashboard.js
import React, { Component } from 'react'
import Notification from './Notification'
import ProjectList from '../projects/ProjectList'
import { connect } from 'react-redux'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
import { Redirect } from 'react-router-dom'

class Dashboard extends Component {
    render() {
        const { projects, auth, notifications } = this.props;
        if (!auth.uid) return <Redirect to='/signin' />

        return (
            <div className="dashboard container">
                <div className="row">
                    <div className="col s12 m6">
                        <ProjectList projects={projects} />
                    </div>
                    <div className="col s12 m5 offset-m1">
                        <Notification notifications={notifications} />
                    </div>
                </div>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        projects: state.firestore.ordered.projects,
        auth: state.firebase.auth,
        notifications: state.firestore.ordered.notifications
    }
}

export default compose(
    connect(mapStateToProps),
    firestoreConnect([
        { collection: 'projects', orderBy: ['createdAt', 'desc'] },
        { collection: 'notifications', limit: 3, orderBy: ['time', 'desc'] }
    ])
)(Dashboard);
```
新しいプロジェクトを作ってみて，プロジェクト一覧のトップに表示されるかどうか，通知欄のトップの表示されるかを確認してみよう．
<img width="1680" alt="スクリーンショット 2019-10-29 11.20.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/6fbafd2c-d450-6dc5-ec98-c3de36b07ffc.png">

#Firebase Hostingへデプロイ

ついにアプリが完成した．後は`Firebase Hosting`へアプリをデプロイするだけだ．

まずは`Firebase Hosting`のセットアップから始めよう．

FirebaseプロジェクトのHostingページへいき，`始める`を押して設定を進めていこう．

```
npm install -g firebase-tools
```
も

```
firebase login
```
も

```
firebase init
```
もすでにやっているので後は

```
firebase deploy
```
だけなのだが，その前にいくつかやることがある．アプリをビルドしないといけない．

なので以下のコマンドを打っていこう．

```
npm run build
```
するとアプリ内にデプロイするための`build`フォルダが作られる．

よし準備は全て整った！デプロイしよう！

```
firebase deploy
```

`deploy complete!`と出たら君の勝ちだ．

Firebase Hostingページに表示されたURLにアクセスしてリダイレクトされたログイン画面が表示されれば完全勝利だ！
ログインして動作確認してみるといい．
<img width="1680" alt="スクリーンショット 2019-10-29 11.33.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/17b70378-ea6d-d201-5a9f-5fd71f9c9531.png">