Supabase 设置
为 Android 模板配置 Supabase Auth、数据库表、存储和 Edge Functions。
这一页把 Android 模板接到一个可用的 Supabase 后端:Auth 回调、 PostgREST 数据表、权益读模型、FCM 测试推送,以及 RevenueCat Webhook。
Kotlin 模板当前只随仓库提供 device_tokens、subscriptions 和
payments 迁移。应用代码会使用 todos 和 profiles,但
soar-kotlin/supabase/migrations/ 里没有对应迁移,所以本页会直接给出
缺失 SQL。
创建项目
创建 Supabase 项目
在 Supabase 控制台创建项目,并复制项目 URL 和 publishable key。
添加移动端 key
把客户端可公开使用的值写入 Android 模板的 local.properties:
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_PUBLISHABLE_KEY=your_publishable_key它们会生成 BuildConfig.SUPABASE_URL 和
BuildConfig.SUPABASE_PUBLISHABLE_KEY。这不是服务端密钥。
允许 Auth 回调
在 Supabase Auth 设置里添加这个 Redirect URL:
soar://auth-callbackSupabaseClientProvider 安装 Auth 时使用 scheme = "soar" 和
host = "auth-callback",所以邮箱验证、OTP、密码恢复和 OAuth 回调都需要
这个 URL 在 allow-list 里。
应用数据库结构
在 soar-kotlin/supabase 目录中连接 CLI:
supabase login
supabase link --project-ref <ref>然后按下面顺序应用 schema。
缺失的 profiles 表
在控制台 SQL 编辑器运行,或先复制成
soar-kotlin/supabase/migrations/ 下的新迁移,再运行 supabase db push:
create table if not exists public.profiles (
id uuid primary key references auth.users(id) on delete cascade,
full_name text,
bio text,
phone text,
location text,
avatar_url text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
alter table public.profiles enable row level security;
drop policy if exists "Users can view own profile" on public.profiles;
create policy "Users can view own profile"
on public.profiles for select
to authenticated
using ((select auth.uid()) = id);
drop policy if exists "Users can update own profile" on public.profiles;
create policy "Users can update own profile"
on public.profiles for update
to authenticated
using ((select auth.uid()) = id)
with check ((select auth.uid()) = id);
drop policy if exists "Users can insert own profile" on public.profiles;
create policy "Users can insert own profile"
on public.profiles for insert
to authenticated
with check ((select auth.uid()) = id);
create or replace function public.handle_new_user()
returns trigger
language plpgsql
security definer
set search_path = public
as $$
begin
insert into public.profiles (id, full_name)
values (new.id, new.raw_user_meta_data ->> 'full_name')
on conflict (id) do nothing;
return new;
end;
$$;
drop trigger if exists on_auth_user_created on auth.users;
create trigger on_auth_user_created
after insert on auth.users
for each row execute function public.handle_new_user();应用会读取和更新 full_name、bio、phone、location、avatar_url。
当前的 SupabaseAuthRemoteDataSource.deleteAccount() 也正是把这些字段置为
null。
缺失的 todos 表
运行或迁移这段 SQL:
create table if not exists public.todos (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
title text not null,
completed boolean not null default false,
sort_order integer not null default 0,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
alter table public.todos
add column if not exists sort_order integer not null default 0;
create index if not exists todos_user_id_idx on public.todos (user_id);
create index if not exists todos_user_sort_idx
on public.todos (user_id, sort_order, created_at desc);
create or replace function public.set_updated_at()
returns trigger
language plpgsql
as $$
begin
new.updated_at = now();
return new;
end;
$$;
drop trigger if exists set_todos_updated_at on public.todos;
create trigger set_todos_updated_at
before update on public.todos
for each row execute function public.set_updated_at();
alter table public.todos enable row level security;
drop policy if exists "Users can view their todos" on public.todos;
create policy "Users can view their todos"
on public.todos for select
to authenticated
using ((select auth.uid()) = user_id);
drop policy if exists "Users can insert their todos" on public.todos;
create policy "Users can insert their todos"
on public.todos for insert
to authenticated
with check ((select auth.uid()) = user_id);
drop policy if exists "Users can update their todos" on public.todos;
create policy "Users can update their todos"
on public.todos for update
to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
drop policy if exists "Users can delete their todos" on public.todos;
create policy "Users can delete their todos"
on public.todos for delete
to authenticated
using ((select auth.uid()) = user_id);SupabaseTodosRemoteDataSource 会按 user_id 筛选,按 sort_order 升序、
created_at 降序排序,并写入 title、completed、sort_order。
建议作为模板修复:把 profiles 和 todos 做成正式迁移加入
soar-kotlin/supabase/migrations/,这样 supabase db push 才能创建
Android 应用实际会用到的全部表。
已随模板提供的迁移
模板已经提供:
20260508000001_create_device_tokens.sql:FCM token 表,(user_id, token)唯一,用户只能访问自己的 token。20260702000000_billing_entitlements.sql:共享的subscriptions和payments表以及 billing enum。客户端只能读取自己的行;写入由 service-role Edge Function 完成。
应用这些迁移:
supabase db push --project-ref <ref>存储桶
上传示例使用名为 sample-uploads 的私有 bucket。测试上传前,请按
存储 创建 bucket 和以用户文件夹为边界的策略。Kotlin
应用没有使用 Expo 示例里的 sample_uploads 元数据表。
Edge Functions
supabase/config.toml 固定了两个函数的认证模式:
[functions.revenuecat-webhook]
verify_jwt = false
[functions.send-test-push]
verify_jwt = true从模板的 supabase/ 目录部署:
supabase functions deploy revenuecat-webhook --no-verify-jwt --project-ref <ref>
supabase functions deploy send-test-push --project-ref <ref>
supabase functions list --project-ref <ref>revenuecat-webhook 由 RevenueCat 调用,没有 Supabase 用户 JWT,所以使用静态
Authorization header。send-test-push 由已登录 Android 用户调用,保留 JWT
校验,并通过该用户的 RLS 上下文读取 device_tokens。_shared/ 会被打包进这
两个函数,不需要单独部署。
Edge secrets
Prop
Type
设置密钥:
supabase secrets set REVENUECAT_WEBHOOK_AUTH=<long-random-string> --project-ref <ref>
supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='<service-account-json>' --project-ref <ref>不要在函数 .env 里设置 SUPABASE_URL、SUPABASE_ANON_KEY 或 service-role
key;Supabase 运行时会注入这些值。
本地函数开发
supabase functions serve --env-file supabase/functions/.env运行 helper 测试:
deno test --allow-env supabase/functions/_shared/revenuecat.test.ts
deno test supabase/functions/_shared/fcm.test.ts本地 smoke test RevenueCat webhook:
curl -i \
-X POST http://localhost:54321/functions/v1/revenuecat-webhook \
-H "Authorization: ${REVENUECAT_WEBHOOK_AUTH}" \
-H "Content-Type: application/json" \
--data '{
"api_version": "1.0",
"event": {
"type": "INITIAL_PURCHASE",
"store": "PLAY_STORE",
"app_user_id": "11111111-1111-1111-1111-111111111111",
"product_id": "pro_monthly",
"period_type": "NORMAL",
"purchased_at_ms": 1782940800000,
"expiration_at_ms": 1785532800000,
"transaction_id": "gpa.test",
"original_transaction_id": "gpa.test"
}
}'部署后验证时,把 host 换成:
https://<ref>.supabase.co/functions/v1/revenuecat-webhook验证
-
soar://auth-callback已加入 Supabase redirect allow-list。 - 新用户注册后会创建 auth user 和对应的
profiles行。 - Todos CRUD 能持久化并在重启应用后保留。
-
public.device_tokens、public.subscriptions、public.payments存在且启用了 RLS。 -
supabase functions list --project-ref <ref>能看到revenuecat-webhook和send-test-push。 - 合法 smoke event 会让 RevenueCat webhook 返回
{"ok":true}。
