難しく考えずに、簡単にfirebaseに画像をアップロードしよう！

今回は、firebaseのStorageを利用して、firebase上に画像をアップロードしていきます。

また、その画像を使ってユーザーのプロフィール画像(photoURL)に変更を反映させます。

内容的には、そこまで難しくないのでぜひご覧ください！

流れとしては以下のようになります。

1, 画像をアップロードするinputタグを作成
2, 画像をアップロードした際に、firebaseのStorageに保管（この時、userごとにフォルダを作成していきます）し、firebaseのユーザー情報を更新
3, アップロードする前の画像を破棄
4, firebaseのルール設定
5, Web上に反映

このような流れになっております。

全部を理解しなくてもいいと思います。

しっかり流れを把握しつつ、一緒に説明を見ていきましょう！

#1, 画像をアップロードするinputタグを作成#

今回は、``<input type="file">``としてやっても良いのですが、僕の大好きなBuefyを用いて使っていきます。

Buefyの使い方が分からないという方は、こちらの記事[初心者必見！サイト制作は楽してなんぼ。CSSフレームワークBuefyの紹介！！](https://qiita.com/akki-memo/items/278ff2982961274955c4)に書いてあるので、ぜひ時間がある方は目を通してください。

```App.vue
               <b-upload type="file" style="margin-top: 1rem" @change.native="uploadImage">
                  <a class="button is-info">
                    <b-icon
                      pack="fas"
                      icon="upload"
                      size="medium"
                      style="margin-right: 0.5rem; margin-bottom: 0.05rem; vertical-align: middle"
                    ></b-icon>
                    プロフィール画像を変更する
                  </a>
                </b-upload>
```

``@change.native``で画像がアップロードされたかを検知しています。

inputの場合は``<input type="file" @change="uploadImage" />``でOKです。

#2, firebaseのストレージに保管し、firebaseのユーザー情報を更新#

methodsにて``uploadImage``という関数を定義し、以下のように記述します。

また、画像ファイルは、imagesフォルダにユーザーID(uid)ごとにファルダを作成し、そこに画像ファイルを格納します。

images/
　├ uid/
　│　└ image.jpg

イメージとしてはこんな感じです。

ユーザー情報を更新するのには``updateProfile``を使用します。

ここで、注意なのですがupdateProfileは``firebase.auth().currentUser``で取得したuserで行わないと更新されません。``firebase.auth().onAuthStateChanged()``でやらないようにお願いします。ちなみに理由は分かりません！

これらを踏まえて、以下の記述を参考に書いてください。

```App.vue
<script>
  methods: {
      async uploadImage(event) {
      event.preventDefault() //要素のデフォルトのイベントを無効
      event.stopPropagation() //子要素のイベントが親要素にも伝播することを防ぐ
      const userStorageRef = firebase.app().storage().ref()

      // type="file"を指定されたinput要素のchangeイベントは「ファイルのリスト」を返す
      const file = event.target.files[0]

      // ファイルが存在しないか、ファイル形式が"image/*"ではないとき
      if (!file || !file.type.match(/image\/*/)) {
        alert('不適切なファイル形式です')
        return false
      }

      await userStorageRef
        // images/uid/に画像ファイルを保管
        .child(`images/${this.user.uid}/${file.name}`)
        .put(file)
        .then(() => {
          // 保管した画像ファイルのURLを取得
          userStorageRef
            .child(`images/${this.user.uid}/${file.name}`)
            .getDownloadURL()
            .then(async imageURL => {
              // ユーザーを取得
              // 注意: firebase.auth().onAuthStateChangedでやらないように！
              const user = firebase.auth().currentUser
              await user
                .updateProfile({
                  photoURL: imageURL
                })
                .then(async () => {
                  // vuex store state userの情報更新
                  this.$store.dispatch('changeUserPhotoURL', imageURL)
                  // これを行わいと変更内容が反映されない
                  // また、一番最後に行う
                  user.reload()
                })
            })
        })
    }
  }
</script>
```

#3, アップロードする前の画像を破棄#

次に、変更する前の画像はもう使わないので、これを消去します。

firebaseのStorageには、ファルダにあるファイルを一括で消去する機能が携わっていません。なので、``listAll()``という関数を用いて、ファイルの配列を取得し、１個１個消去していきます。
また、新しい画像をアップロードする前に行うということにも注意してください！
※これよりも良いやり方を知っている方は、ぜひコメント欄にて教えて頂くと幸いです。

```App.vue
<script>
  methods: {
      async uploadImage(event) {
      event.preventDefault() //要素のデフォルトのイベントを無効
      event.stopPropagation() //子要素のイベントが親要素にも伝播することを防ぐ
      const userStorageRef = firebase.app().storage().ref()

      // type="file"を指定されたinput要素のchangeイベントは「ファイルのリスト」を返す
      const file = event.target.files[0]

      // ファイルが存在しないか、ファイル形式が"image/*"ではないとき
      if (!file || !file.type.match(/image\/*/)) {
        alert('不適切なファイル形式です')
        return false
      }

    // 追加
    await userStorageRef
        .child(`images/${this.user.uid}`)
        .listAll()
        .then(result => {
          for (let i = 0; result.items.length; i++) {
        userStorageRef.child(`images/${this.user.uid}/${result.items[i].name}`).delete()
          }
        })
        // ファイルがない場合はスルー（初期値はstorageに保存していないため）
        .catch(() => {})
    }

   //ここまで

    await userStorageRef
        // images/uid/に画像ファイルを保管
        .child(`images/${this.user.uid}/${file.name}`)
        .put(file)
        .then(() => {
          // 保管した画像ファイルのURLを取得
          userStorageRef
            .child(`images/${this.user.uid}/${file.name}`)
            .getDownloadURL()
            .then(async imageURL => {
              // ユーザーを取得
              // 注意: firebase.auth().onAuthStateChangedでやらないように！
              const user = firebase.auth().currentUser
              await user
                .updateProfile({
                  photoURL: imageURL
                })
                .then(async () => {
                  // vuex store state userの情報更新
                  this.$store.dispatch('changeUserPhotoURL', imageURL)
                  // これを行わいと変更内容が反映されない
                  // また、一番最後に行う
                  user.reload()
                })
            })
        })
  }
</script>
```

#4, firebaseのルール設定#

次に、Firebase Consoleの画面を開き、左側の「Storage」という項目を開いてください。

そして、「Rules」を開いてください。

今回のルール設定は、ユーザー本人かつ認証済みかつ画像ファイルであれば変更を行えるというルール設定を行います。

```
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /images/{uid}/{allPaths=**}  {
      allow read, write: if request.auth.uid == uid && request.auth != null &&　request.resource.contentType.matches('image/.*')
    }
  }
}
```

``request.auth.uid == uid``：ユーザー本人かの確認
``request.auth != null``：認証済みかの判断
``request.resource.contentType.matches('image/.*')``：画像ファイルであるかの確認

#5, Web上に反映#

後は、user情報を取得してWeb上に反映するだけです！

一応、書いておきますね。

```App.vue
firebase.auth().onAuthStateChanged((user)) => {
  console.log(user.photoURL)
})
```

このようにしてユーザーの画像ファイルを更新できると思います！

最近、毎日知らないことだらけの連続で頭がパンクしそうです（笑）

でもめげずに頑張りたいと思います！

ぜひ皆さんも一緒に頑張りましょう！！

以上、「難しく考えずに、簡単にfirebaseに画像をアップロードしよう！」でした！


良かったら、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい！！

Thank you for reading