数据库
ShipNow 默认使用 Supabase 作为数据库解决方案,内置用户管理功能。
概述
Supabase 数据库的主要功能包括:
- 🔄 无限 API 请求
- 🚀 支持运行在 Edge Runtime
- 💻 支持在浏览器查询数据
- 🔐 内置文件存储功能
设置 Supabase 环境变量
在 Supabase 创建账号并新建项目
从项目的 API 设置 复制 Supabase 项目 URL、anon key 和 service role key
将项目 URL、anon key 和 service role key 添加到 .env.local
文件:
NEXT_PUBLIC_SUPABASE_URL="https://your-project-id.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="eyJxxx"
SUPABASE_SERVICE_ROLE_KEY="eyJxxx"
创建 Supabase 数据库表
复制 apps/web/migrations/1000_init.sql
中的 SQL语句
--
-- Name: next_auth; Type: SCHEMA;
--
CREATE SCHEMA next_auth;
GRANT USAGE ON SCHEMA next_auth TO service_role;
GRANT ALL ON SCHEMA next_auth TO postgres;
--
-- Create users table
--
CREATE TABLE IF NOT EXISTS next_auth.users
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
name text,
email text,
"emailVerified" timestamp with time zone,
image text,
CONSTRAINT users_pkey PRIMARY KEY (id),
CONSTRAINT email_unique UNIQUE (email)
);
GRANT ALL ON TABLE next_auth.users TO postgres;
GRANT ALL ON TABLE next_auth.users TO service_role;
-- uid() function to be used in RLS policies
CREATE FUNCTION next_auth.uid() RETURNS uuid
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.sub', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
)::uuid
$$;
--
-- Create sessions table
--
CREATE TABLE IF NOT EXISTS next_auth.sessions
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
expires timestamp with time zone NOT NULL,
"sessionToken" text NOT NULL,
"userId" uuid,
CONSTRAINT sessions_pkey PRIMARY KEY (id),
CONSTRAINT sessionToken_unique UNIQUE ("sessionToken"),
CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.sessions TO postgres;
GRANT ALL ON TABLE next_auth.sessions TO service_role;
--
-- Create accounts table
--
CREATE TABLE IF NOT EXISTS next_auth.accounts
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
type text NOT NULL,
provider text NOT NULL,
"providerAccountId" text NOT NULL,
refresh_token text,
access_token text,
expires_at bigint,
token_type text,
scope text,
id_token text,
session_state text,
oauth_token_secret text,
oauth_token text,
"userId" uuid,
CONSTRAINT accounts_pkey PRIMARY KEY (id),
CONSTRAINT provider_unique UNIQUE (provider, "providerAccountId"),
CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.accounts TO postgres;
GRANT ALL ON TABLE next_auth.accounts TO service_role;
--
-- Create verification_tokens table
--
CREATE TABLE IF NOT EXISTS next_auth.verification_tokens
(
identifier text,
token text,
expires timestamp with time zone NOT NULL,
CONSTRAINT verification_tokens_pkey PRIMARY KEY (token),
CONSTRAINT token_unique UNIQUE (token),
CONSTRAINT token_identifier_unique UNIQUE (token, identifier)
);
GRANT ALL ON TABLE next_auth.verification_tokens TO postgres;
GRANT ALL ON TABLE next_auth.verification_tokens TO service_role;
CREATE TABLE IF NOT EXISTS public.users
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
name text,
email text,
"emailVerified" timestamp with time zone,
image text,
CONSTRAINT users_pkey PRIMARY KEY (id),
CONSTRAINT email_unique UNIQUE (email)
);
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
-- Inserts a row into public.users
CREATE FUNCTION public.handle_new_user()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER SET search_path = ''
AS $$
BEGIN
INSERT INTO public.users (id, name, email, "emailVerified", image)
VALUES (new.id, new.raw_user_meta_data ->> 'name', new.email, new.email_confirmed_at, new.raw_user_meta_data ->> 'avatar_url');
RETURN new;
END;
$$;
-- Trigger the function every time a user is created
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();
前往 Supabase SQL 编辑器,粘贴 SQL 语句并执行。
前往 Supabase API 设置,将 next_auth
添加到 "Exposed schemas" 列表中。
数据库 CRUD 操作
读取单个用户
import { createAdminClient } from '@/utils/supabase/server';
async function readUser(id: string) {
const supabase = createAdminClient();
const { data, error } = await supabase
.from('users')
.select()
.eq('id', id);
if (error) throw error;
return data?.[0];
}
const user = await readUser('user-uuid');
读取用户列表
import { createAdminClient } from '@/utils/supabase/server';
async function readUsers() {
const supabase = createAdminClient();
const { data, error } = await supabase
.from('users')
.select();
if (error) throw error;
return data;
}
const users = await readUsers();
创建用户
import { createAdminClient } from '@/utils/supabase/server';
async function createUser(userData: {
email: string;
name?: string;
}) {
const supabase = createAdminClient();
const { data, error } = await supabase
.from('users')
.insert([userData])
.select();
if (error) throw error;
return data?.[0];
}
// Usage
const newUser = await createUser({
email: '[email protected]',
name: 'John Doe'
});
更新用户
import { createAdminClient } from '@/utils/supabase/server';
async function updateUser(id: string, updates: {
name?: string;
email?: string;
}) {
const supabase = createAdminClient();
const { data, error } = await supabase
.from('users')
.update(updates)
.eq('id', id)
.select();
if (error) throw error;
return data?.[0];
}
// Usage
const updatedUser = await updateUser('user-uuid', {
name: 'New Name'
});
删除用户
import { createAdminClient } from '@/utils/supabase/server';
async function deleteUser(id: string) {
const supabase = createAdminClient();
const { error } = await supabase
.from('users')
.delete()
.eq('id', id);
if (error) throw error;
return true;
}
// Usage
await deleteUser('user-uuid');
使用 Supabase 客户端
服务端组件
在服务端,需要访问数据库,使用 createAdminClient
创建客户端:
import { createAdminClient } from '@/utils/supabase/server';
export async function MyServerComponent() {
const supabase = await createAdminClient();
const { data, error } = await supabase
.from('users')
.select('*')
.single();
if (error) throw error;
return <div>User: {data.email}</div>;
}
客户端组件
在浏览器中,需要访问数据库,使用 createClient
创建客户端:
'use client';
import { createClient } from '@/utils/supabase/client';
export function MyClientComponent() {
const supabase = createClient();
async function fetchUser() {
const { data, error } = await supabase
.from('users')
.select('*')
.single();
if (error) throw error;
}
return <button onClick={fetchUser}>Fetch User</button>;
}
在浏览器中,需要配置 RLS 策略,才能获取到数据。
需要帮助?
- 查看 Supabase 文档
- 加入我们的 Discord 社区
- 在 GitHub 上提交问题