ShipNow LogoShipNow

数据库

ShipNow 默认使用 Supabase 作为数据库解决方案,内置用户管理功能。

概述

Supabase 数据库的主要功能包括:

  • 🔄 无限 API 请求
  • 🚀 支持运行在 Edge Runtime
  • 💻 支持在浏览器查询数据
  • 🔐 内置文件存储功能

设置 Supabase 环境变量

Supabase 创建账号并新建项目

从项目的 API 设置 复制 Supabase 项目 URL、anon key 和 service role key

复制 Supabase 项目 URL、anon key 和 service role key

将项目 URL、anon key 和 service role key 添加到 .env.local 文件:

.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语句

apps/web/migrations/1000_init.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 语句并执行。

在数据库中执行 SQL schema

前往 Supabase API 设置,将 next_auth 添加到 "Exposed schemas" 列表中。

在 Supabase 中暴露 NextAuth schema

数据库 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 上提交问题

On this page

ShipNow Logo获取 ShipNow