SvelteKit + Supabase Storageを使ってアイコン登録

# はじめに

Baas(Backend As A Service)で何かサービスを構築してみたいと思い、
firebaseを触ってみようと思ったところ、Firebase の代替と謳われているsupabaseを発見。

こちらの方が面白そうだと感じたため、構築してみることにしました。

今回はプロフィール画面を作成した際に、supabaseのstorage機能について触れたのでメモ。

## 使ったもの

* Vite + SvelteKit + Typescript
    * @supabase/supabase-js
    * Flowbite-svelte
    * tailwind css

* supabase
    * auth
    * db
    * storage

## 完成品

### マイページ(アイコンアップロード前)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/399b997f-44f2-923e-f451-1e657705064a.png)

### アップロード画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/9a350231-a054-4438-4742-25c5c7d514de.png)

### マイページ(アイコンアップロード後)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/4d95c4c8-27f5-13ea-e1f7-2d209763320e.png)


## 実装方法

1. Supabase管理画面より、`storage` > `bucket`を作成する
1. Supabase管理画面から、`profiles`テーブルを作成する
1. フロント側を整備する

### 1. Supabase管理画面より、`storage` > `bucket`を作成する

アイコンを保存するためのバケットを作成していきます。

1. サイドメニューから、`Storage`を選択する
1. 画面左上の`New bucket`を選択し、任意のタイトルで保存をする。
今回は公式のチュートリアル通りに`avatars`としました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/e2534f32-5572-3edc-1a08-033a6c44fd09.png)

### 2. Supabase管理画面から、`profiles`テーブルを作成する

ユーザーに対してアイコンのURLを紐づけるために`profiles`テーブルを作成します。

1. サイドメニューから、`SQL Editor`を選択する
1. 画面左上の`Welcome`を選択。
1. `Quick start`の欄の`User Management Starter`を選び、実行する
  (RUN or Ctrl + Enter)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/7ac314d3-70d4-e86b-95b5-715b714d03cc.png)

### 3. フロント側を整備する

1と2でsupabase側の用意は完了なので、今度はフロント側を整備していく。

まずはファイルのアップロードフォームを作成する。

```vue
<script lang="ts">
  import { Label } from 'flowbite-svelte'
  let files: FileList
</script>

<div class="flex">
    <Label class="w-1/5 pb-2">アイコンアップロード</Label>
    <input
        class="w-4/5 text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
        type="file"
        accept="image/*"
        bind:value={files}
    />
</div>
```

こんな感じになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/42705f53-1299-a434-0fec-6f66df3f5650.png)

次に、ファイルがアップロードされたら着火するイベントを作成する

```vue
<script lang="ts">
  let files: FileList

  const uploadIcon = async () => {
    if (!files || files.length === 0) {
      return
    }

    const file = files[0]
    const fileExt = file.name.split('.').pop()
    const filePath = `${session.user.id}/${Math.random()}.${fileExt}`

    await supabase.storage.from('avatars').upload(filePath, file)
  }
</script>

  <input
    class="w-4/5 block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
    type="file"
    accept="image/*"
    bind:files
    on:change={uploadIcon} // 追加
  />
```

`session`には現在のsession情報を格納しています。
`avatar storage`にユーザーごとのファイルを作成し、そこに画像を格納するようにしています。

次に、`profiles`テーブルに画像URLを保存します。

```vue
<script lang="ts">
  import { Label, Button } from 'flowbite-svelte'
  let files: FileList
  let url: string // 追加

  const updateUser = async () => {
    update_data = {
      id: $session.user.id,
      avatar_url: url
    }

    await supabase
      .from('profiles')
      .update(update_data)
  }

  const uploadIcon = async () => {
    if (!files || files.length === 0) {
      return
    }

    const file = files[0]
    const fileExt = file.name.split('.').pop()
    const filePath = `${session.user.id}/${Math.random()}.${fileExt}`

    await supabase.storage.from('avatars').upload(filePath, file)

    url = filePath // 追加
  }
</script>

<form class="w-full" on:submit|preventDefault="{updateUser}">
  // ------------------------------
  // 先ほどのファイルフォーム
  // ------------------------------
  <Button class="mt-10" type="submit" color="yellow" disabled={updating}>プロフィールを変更する</Button>
</form>
```

先ほど作った`uploadIcon`メソッドの最後に`filePath`を変数に格納します。
また、`form`タグのサブミットイベントに`updateUser`を定義し、サブミットボタンを押されたら着火するようにしました。

ニックネームや出身地など、一緒のフォームで入力できるようにすればプロフィール画面が作れますね


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/5b7902ce-e6f1-4d18-f7d3-6e58102030ab.png)