快速入门: Svelte

介绍

这个例子提供了建立一个基本用户管理应用程序的步骤。它包括。

  • MemFire Cloud Database:一个用于存储用户数据的Postgres数据库。
  • MemFire Cloud Auth:用户可以用魔法链接登录(没有密码,只有电子邮件)。
  • MemFire Cloud 存储:用户可以上传照片。
  • 行级安全:数据受到保护,个人只能访问自己的数据。
  • 即时APIs。当你创建你的数据库表时,API将自动生成。

在本指南结束时,你将拥有一个允许用户登录和更新一些基本档案细节的应用程序。

Supabase用户管理实例

GitHub#

如果你在阅读指南时遇到困难,请参考此版本

项目设置

在我们开始构建之前,我们要设置我们的数据库和API。这就像在Supabase中启动一个新项目一样简单 然后在数据库中创建一个 "模式"。

创建一个项目

  1. 进入MemFire Cloud
  2. 点击 "新项目"。
  3. 输入你的项目细节。
  4. 等待新数据库的启动。

设置数据库模式

现在我们要设置数据库模式。我们可以使用SQL编辑器中的 "用户管理"的模板快速启动。 或者你可以直接复制/粘贴下面的SQL,然后自己运行它。

  1. 进入仪表版中的SQL编辑器页面。
  2. 点击 用户管理的模板。
  3. 点击 运行

获取API密钥#

现在你已经创建了一些数据库表,你已经准备好使用自动生成的API插入数据。 我们只需要从API设置中获得URL和anon密钥。

  1. 进入仪表板中的设置页面。
  2. 单击侧边栏中的API
  3. 在这个页面上找到你的APIURLanonservice_role键。

构建应用程序

让我们从头开始构建Svelte应用程序。

初始化一个Svelte应用程序#

我们可以使用Vite Svelte TypeScript模板来初始化一个名为supabase-svelte的应用程序。

npm create vite@latest supabase-svelte -- --template svelte-ts
cd supabase-svelte
npm install

然后让我们安装唯一的额外依赖:supabase-js

npm install @supabase/supabase-js

最后,我们要把环境变量保存在.env中。 我们所需要的是API URL和你[早些时候]复制的anon密钥(#get-theapi-keys)。

.env
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

现在我们已经有了API凭证,让我们创建一个辅助文件来初始化Supabase客户端。这些变量将被暴露在 在浏览器上,这完全没有问题,因为我们的数据库已经启用了行级安全

src/supabaseClient.ts
1import { createClient } from '@supabase/supabase-js'
2
3const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
4const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
5
6export const supabase = createClient(supabaseUrl, supabaseAnonKey)

还有一个可选的步骤是更新CSS文件src/app.css以使应用程序看起来漂亮。 你可以找到这个文件的全部内容这里

设置一个登录组件

让我们建立一个Svelte组件来管理登录和注册。我们将使用Magic Links,这样用户就可以用他们的电子邮件登录,而无需使用密码。

src/lib/Auth.svelte
1<script lang="ts">
2  import { supabase } from 'src/supabaseClient'
3
4  let loading = false
5  let email = ''
6
7  const handleLogin = async () => {
8    try {
9      loading = true
10      const { error } = await supabase.auth.signInWithOtp({ email })
11      if (error) throw error
12      alert('Check your email for login link!')
13    } catch (error) {
14      if (error instanceof Error) {
15        alert(error.message)
16      }
17    } finally {
18      loading = false
19    }
20  }
21</script>
22
23<div class="row flex-center flex">
24  <div class="col-6 form-widget" aria-live="polite">
25    <h1 class="header">Supabase + Svelte</h1>
26    <p class="description">Sign in via magic link with your email below</p>
27    <form class="form-widget" on:submit|preventDefault="{handleLogin}">
28      <div>
29        <label for="email">Email</label>
30        <input
31          id="email"
32          class="inputField"
33          type="email"
34          placeholder="Your email"
35          bind:value="{email}"
36        />
37      </div>
38      <div>
39        <button type="submit" class="button block" aria-live="polite" disabled="{loading}">
40          <span>{loading ? 'Loading' : 'Send magic link'}</span>
41        </button>
42      </div>
43    </form>
44  </div>
45</div>

账号页面

在用户登录后,我们可以让他们编辑他们的个人资料细节和管理他们的账户。 让我们为其创建一个新的组件,名为Account.svelte

src/lib/Account.svelte
1<script lang="ts">
2  import { onMount } from "svelte";
3  import type { AuthSession } from "@supabase/supabase-js";
4  import { supabase } from "../supabaseClient";
5
6  export let session: AuthSession;
7
8  let loading = false
9  let username: string | null = null
10  let website: string | null = null
11  let avatarUrl: string | null = null
12
13  onMount(() => {
14    getProfile()
15  })
16
17  const getProfile = async () => {
18    try {
19      loading = true
20      const { user } = session
21
22      const { data, error, status } = await supabase
23        .from('profiles')
24        .select('username, website, avatar_url')
25        .eq('id', user.id)
26        .single()
27
28      if (error && status !== 406) throw error
29
30      if (data) {
31        username = data.username
32        website = data.website
33        avatarUrl = data.avatar_url
34      }
35    } catch (error) {
36      if (error instanceof Error) {
37        alert(error.message)
38      }
39    } finally {
40      loading = false
41    }
42  }
43
44  const updateProfile = async () => {
45    try {
46      loading = true
47      const { user } = session
48
49      const updates = {
50        id: user.id,
51        username,
52        website,
53        avatar_url: avatarUrl,
54        updated_at: new Date().toISOString(),
55      }
56
57      let { error } = await supabase.from('profiles').upsert(updates)
58
59      if (error) {
60        throw error
61      }
62    } catch (error) {
63      if (error instanceof Error) {
64        alert(error.message)
65      }
66    } finally {
67      loading = false
68    }
69  }
70</script>
71
72<form on:submit|preventDefault={updateProfile} class="form-widget">
73  <div>Email: {session.user.email}</div>
74  <div>
75    <label for="username">Name</label>
76    <input id="username" type="text" bind:value={username} />
77  </div>
78  <div>
79    <label for="website">Website</label>
80    <input id="website" type="text" bind:value={website} />
81  </div>
82  <div>
83    <button type="submit" class="button primary block" disabled={loading}>
84      {loading ? 'Saving ...' : 'Update profile'}
85    </button>
86  </div>
87  <button type="button" class="button block" on:click={() => supabase.auth.signOut()}>
88    Sign Out
89  </button>
90</form>

启动

现在我们已经有了所有的组件,让我们来更新App.svelte

src/App.svelte
1<script lang="ts">
2  import { onMount } from 'svelte'
3  import { supabase } from './supabaseClient'
4  import type { AuthSession } from '@supabase/supabase-js'
5  import Account from './lib/Account.svelte'
6  import Auth from './lib/Auth.svelte'
7
8  let session: AuthSession
9
10  onMount(() => {
11    supabase.auth.getSession().then(({ data }) => {
12      session = data.session
13    })
14
15    supabase.auth.onAuthStateChange((_event, _session) => {
16      session = _session
17    })
18  })
19</script>
20
21<div class="container" style="padding: 50px 0 100px 0">
22  {#if !session}
23  <Auth />
24  {:else}
25  <Account {session} />
26  {/if}
27</div>

一旦完成,在终端窗口运行这个程序。

npm run dev

然后打开浏览器到localhost:5173,你应该看到完成的应用程序。

⚠️ 警告:Svelte使用Vite,默认端口为5173,Supabase使用端口3000。要改变supabase的重定向端口,请进入。认证 > 设置并将网站网址改为localhost:5173

Supabase Svelte

个人照片

每个Supabase项目都配置了存储,用于管理照片和视频等大文件。

创建一个上传小组件

让我们为用户创建一个头像,以便他们可以上传个人资料照片。我们可以从创建一个新的组件开始。

src/lib/Avatar.svelte
1<script lang="ts">
2  import { createEventDispatcher } from 'svelte'
3  import { supabase } from '../supabaseClient'
4
5  export let size: number
6  export let url: string
7
8  let avatarUrl: string = null
9  let uploading = false
10  let files: FileList
11
12  const dispatch = createEventDispatcher()
13
14  const downloadImage = async (path: string) => {
15    try {
16      const { data, error } = await supabase.storage.from('avatars').download(path)
17
18      if (error) {
19        throw error
20      }
21
22      const url = URL.createObjectURL(data)
23      avatarUrl = url
24    } catch (error) {
25      if (error instanceof Error) {
26        console.log('Error downloading image: ', error.message)
27      }
28    }
29  }
30
31  const uploadAvatar = async () => {
32    try {
33      uploading = true
34
35      if (!files || files.length === 0) {
36        throw new Error('You must select an image to upload.')
37      }
38
39      const file = files[0]
40      const fileExt = file.name.split('.').pop()
41      const filePath = `${Math.random()}.${fileExt}`
42
43      let { error } = await supabase.storage.from('avatars').upload(filePath, file)
44
45      if (error) {
46        throw error
47      }
48
49      url = filePath
50      dispatch('upload')
51    } catch (error) {
52      if (error instanceof Error) {
53        alert(error.message)
54      }
55    } finally {
56      uploading = false
57    }
58  }
59
60  $: if (url) downloadImage(url)
61</script>
62
63<div style="width: {size}px" aria-live="polite">
64  {#if avatarUrl} <img src={avatarUrl} alt={avatarUrl ? 'Avatar' : 'No image'} class="avatar image"
65  style="height: {size}px, width: {size}px" /> {:else}
66  <div class="avatar no-image" style="height: {size}px, width: {size}px" />
67  {/if}
68  <div style="width: {size}px">
69    <label class="button primary block" for="single">
70      {uploading ? 'Uploading ...' : 'Upload avatar'}
71    </label>
72    <span style="display:none">
73      <input
74        type="file"
75        id="single"
76        accept="image/*"
77        bind:files
78        on:change="{uploadAvatar}"
79        disabled="{uploading}"
80      />
81    </span>
82  </div>
83</div>

添加新的小组件

然后我们就可以把这个小部件添加到账号页面:

src/lib/Account.svelte
1<script lang="ts">
2  // Import the new component
3  import Avatar from './Avatar.svelte'
4</script>
5
6<form on:submit|preventDefault="{updateProfile}" class="form-widget">
7  <!-- Add to body -->
8  <Avatar bind:url="{avatarUrl}" size="{150}" on:upload="{updateProfile}" />
9
10  <!-- Other form elements -->
11</form>

下一步

在这个阶段,你已经有了一个功能完备的应用程序!