※ 書きかけです
supabase はテーブルの構造に合わせて GraphQL スキーマが自動生成され、それを利用してプログラムを組むことができるようになりました。ただしユーザーの作成や認証機能は GraphQL を通しては実装されておらず、その部分だけは RestAPI を用いる必要があります。また、GraphQL がアクセス可能なデータベーススキーマは public のみのため、auth 配下にあるユーザ情報は GraphQL 上では関連付けることができません。これらの問題にうまく対処していく必要があります。
https://github.com/supabase/cli
ローカル開発で必要となります。
supabase init
supabase start
これで supabase が起動できます。supabase-cli を使う場合の注意点があります。外部からのアクセス可能な環境では絶対に使用しないでください。jwt シークレットがsuper-secret-jwt-token-with-at-least-32-characters-long
という内容で決め打ちになっているので、外に出したら一瞬でクラックされます。
supabase init
でsupabase
ディレクトリが作成されます。その中に用意しておくと良いファイルです。
drop extension if exists pg_graphql;
create extension if not exists pg_graphql;
select graphql.rebuild_schema();
本来初期データを入れるためのファイルですが、マイグレーション後に GraphQL の機能を有効にするために必要です。supabase start
やsupabase db reset
でデータベースを作成した際に、一見 pg_graphql の拡張機能が有効になっているように見えるのですが、再起動しないと使えません。また、graphql.rebuild_schema()
はデーブルの構造を変更するたびに必要になります。
以下、ユーザ管理用のテーブルを作成するためのマイグレーションファイルです
CREATE OR REPLACE FUNCTION public.handle_users_update()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF SECURITY DEFINER
AS $BODY$
begin
IF (TG_OP = 'DELETE') THEN
delete from public."User" where id=old.id;
return old;
ELSEIF (TG_OP = 'UPDATE') THEN
update public."User"
set email=NEW.email,raw_user_meta_data=NEW.raw_user_meta_data where id=old.id;
return new;
ELSEIF (TG_OP = 'INSERT') THEN
insert into public."User"(id, email,raw_user_meta_data) values(NEW.id,NEW.email,NEW.raw_user_meta_data);
return new;
END IF;
return NULL;
end;
$BODY$;
ALTER FUNCTION public.handle_users_update()
OWNER TO postgres;
GRANT EXECUTE ON FUNCTION public.handle_users_update() TO authenticated;
GRANT EXECUTE ON FUNCTION public.handle_users_update() TO postgres;
GRANT EXECUTE ON FUNCTION public.handle_users_update() TO PUBLIC;
GRANT EXECUTE ON FUNCTION public.handle_users_update() TO anon;
GRANT EXECUTE ON FUNCTION public.handle_users_update() TO service_role;
CREATE TABLE IF NOT EXISTS public."User"
(
id uuid NOT NULL,
email character varying(255) COLLATE pg_catalog."default",
raw_user_meta_data text,
CONSTRAINT "User_pkey" PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public."User"
OWNER to postgres;
ALTER TABLE IF EXISTS public."User"
ENABLE ROW LEVEL SECURITY;
GRANT ALL ON TABLE public."User" TO anon;
GRANT ALL ON TABLE public."User" TO authenticated;
GRANT ALL ON TABLE public."User" TO postgres;
GRANT ALL ON TABLE public."User" TO service_role;
CREATE POLICY "Enable access to all users"
ON public."User"
AS PERMISSIVE
FOR SELECT
TO public
USING (true);
CREATE trigger on_auth_user_update
AFTER INSERT OR UPDATE OR DELETE ON auth.users
for each row execute procedure public.handle_users_update();
auth.users
への操作をフックして、public.User
に必要なデータを書き込みます。こうしておくと、GraphQL のスキーマーがユーザ情報へアクセスできるようになります。
タイトル、説明、日時、非公開属性、ユーザ情報を記憶します。ユーザ情報は public.User にリレーションを張ります。auth.users では無いので注意してください。
CREATE TABLE IF NOT EXISTS public."Todo"
(
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
created_at timestamp with time zone DEFAULT now(),
user_id uuid NOT NULL DEFAULT auth.uid(),
title text COLLATE pg_catalog."default",
published boolean NOT NULL DEFAULT false,
description text COLLATE pg_catalog."default",
CONSTRAINT "Todo_pkey" PRIMARY KEY (id),
CONSTRAINT "Todo_user_id_fkey" FOREIGN KEY (user_id)
REFERENCES public."User" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public."Todo"
OWNER to postgres;
ALTER TABLE IF EXISTS public."Todo"
ENABLE ROW LEVEL SECURITY;
GRANT ALL ON TABLE public."Todo" TO anon;
GRANT ALL ON TABLE public."Todo" TO authenticated;
GRANT ALL ON TABLE public."Todo" TO postgres;
GRANT ALL ON TABLE public."Todo" TO service_role;
CREATE POLICY "Enable access to all users"
ON public."Todo"
AS PERMISSIVE
FOR SELECT
TO public
USING (published or auth.uid() = user_id);
CREATE POLICY "Enable INSERT for authenticated users only"
ON public."Todo"
AS PERMISSIVE
FOR INSERT
TO public
WITH CHECK ((auth.role() = 'authenticated'::text) and auth.uid() = user_id);
CREATE POLICY "Enable DELETE/UPDATE for users based on user_id"
ON public."Todo"
AS PERMISSIVE
FOR ALL
TO public
USING ((auth.uid() = user_id));
Todo テーブルはCREATE POLICY
で PostgreSQL の RLS(行レベルセキュリティ)を作っています。
このあたりの設定は慣れが必要です。
認証に必要なユーザを作成します。cli では用意されていないので自分で作ります。ユーザの作成には service_role の方のキーを使います。テストユーザをさくっと作りたいので招待機能は使いません。
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
import { createClient } from "@supabase/supabase-js";
import { config } from "dotenv";
const { parsed } = config({ path: ".env.local" });
const endpoint = parsed?.NEXT_PUBLIC_SUPABASE_URL;
const key = parsed?.SUPABASE_KEY;
const createUser = async ({
email,
password,
}: {
email: string;
password: string;
}) => {
const supabase = createClient(endpoint!, key!);
const result = await supabase.auth.api.createUser({
email,
password,
email_confirm: true,
user_metadata: { name: email },
});
return result;
};
(async () => {
if (!endpoint || !key || process.argv.length < 4) {
console.log("create-user [email] [password]");
} else {
let result;
for (let i = 0; i < 3; i++) {
result = await createUser({
email: process.argv[2],
password: process.argv[3],
});
if (result.error?.status !== 500) break;
}
console.log(result);
}
})();
yarn ts-node -s bin/create-user a@example.com a
yarn ts-node -s bin/create-user b@example.com a
たまに 500 エラーを返す時があるので 3 回リトライするようにしています。とりあえずテスト用ユーザを二人作っておきます。
作成したユーザの認証と token の受け取りは GraphQL ではできないので RestAPI を直にたたくかsupabase.auth.signIn
を使うことになります。