diff --git a/src/types/db.ts b/src/types/db.ts index 1a83ea1..777ba1f 100644 --- a/src/types/db.ts +++ b/src/types/db.ts @@ -96,6 +96,35 @@ export type Database = { }, ] } + hack_team: { + Row: { + created_at: string + hack_slug: string + name: string | null + userid: string | null + } + Insert: { + created_at?: string + hack_slug: string + name?: string | null + userid?: string | null + } + Update: { + created_at?: string + hack_slug?: string + name?: string | null + userid?: string | null + } + Relationships: [ + { + foreignKeyName: "hack_team_hack_slug_fkey" + columns: ["hack_slug"] + isOneToOne: true + referencedRelation: "hacks" + referencedColumns: ["slug"] + }, + ] + } hacks: { Row: { approved: boolean @@ -110,6 +139,7 @@ export type Database = { downloads: number estimated_release: string | null language: string + original_author: string | null patch_url: string search: unknown slug: string @@ -132,6 +162,7 @@ export type Database = { downloads?: number estimated_release?: string | null language: string + original_author?: string | null patch_url: string search?: unknown slug: string @@ -154,6 +185,7 @@ export type Database = { downloads?: number estimated_release?: string | null language?: string + original_author?: string | null patch_url?: string search?: unknown slug?: string diff --git a/supabase/migrations/20251106214743_remote_schema.sql b/supabase/migrations/20251106214743_remote_schema.sql new file mode 100644 index 0000000..1f035fb --- /dev/null +++ b/supabase/migrations/20251106214743_remote_schema.sql @@ -0,0 +1,275 @@ +create table "public"."hack_team" ( + "created_at" timestamp with time zone not null default now(), + "userid" uuid, + "name" text, + "hack_slug" text not null +); + + +alter table "public"."hack_team" enable row level security; + +alter table "public"."hacks" add column "original_author" text; + +CREATE UNIQUE INDEX hack_team_pkey ON public.hack_team USING btree (hack_slug); + +alter table "public"."hack_team" add constraint "hack_team_pkey" PRIMARY KEY using index "hack_team_pkey"; + +alter table "public"."hack_team" add constraint "hack_team_hack_slug_fkey" FOREIGN KEY (hack_slug) REFERENCES hacks(slug) ON UPDATE CASCADE ON DELETE CASCADE not valid; + +alter table "public"."hack_team" validate constraint "hack_team_hack_slug_fkey"; + +alter table "public"."hack_team" add constraint "hack_team_userid_fkey" FOREIGN KEY (userid) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; + +alter table "public"."hack_team" validate constraint "hack_team_userid_fkey"; + +alter table "public"."hack_team" add constraint "only_one_user_column_defined" CHECK ((((userid IS NOT NULL) AND (name IS NULL)) OR ((userid IS NULL) AND (name IS NOT NULL)))) not valid; + +alter table "public"."hack_team" validate constraint "only_one_user_column_defined"; + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.delete_claim(uid uuid, claim text) + RETURNS text + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO 'public' +AS $function$ + BEGIN + IF NOT is_claims_admin() THEN + RETURN 'error: access denied'; + ELSE + update auth.users set raw_app_meta_data = + raw_app_meta_data - claim where id = uid; + return 'OK'; + END IF; + END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.get_claim(uid uuid, claim text) + RETURNS jsonb + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO 'public' +AS $function$ + DECLARE retval jsonb; + BEGIN + IF NOT is_claims_admin() THEN + RETURN '{"error":"access denied"}'::jsonb; + ELSE + select coalesce(raw_app_meta_data->claim, null) from auth.users into retval where id = uid::uuid; + return retval; + END IF; + END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.get_claims(uid uuid) + RETURNS jsonb + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO 'public' +AS $function$ + DECLARE retval jsonb; + BEGIN + IF NOT is_claims_admin() THEN + RETURN '{"error":"access denied"}'::jsonb; + ELSE + select raw_app_meta_data from auth.users into retval where id = uid::uuid; + return retval; + END IF; + END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.get_my_claim(claim text) + RETURNS jsonb + LANGUAGE sql + STABLE +AS $function$ + select + coalesce(nullif(current_setting('request.jwt.claims', true), '')::jsonb -> 'app_metadata' -> claim, null) +$function$ +; + +CREATE OR REPLACE FUNCTION public.get_my_claims() + RETURNS jsonb + LANGUAGE sql + STABLE +AS $function$ + select + coalesce(nullif(current_setting('request.jwt.claims', true), '')::jsonb -> 'app_metadata', '{}'::jsonb)::jsonb +$function$ +; + +CREATE OR REPLACE FUNCTION public.hacks_update_guard() + RETURNS trigger + LANGUAGE plpgsql +AS $function$begin + if current_user in ('postgres', 'dashboard_user') then + return new; + end if; + + if not public.is_admin() then + if new.slug is distinct from old.slug then + raise exception 'non-admins cannot change slug'; + end if; + if new.created_by is distinct from old.created_by then + raise exception 'non-admins cannot change created_by'; + end if; + if new.approved is distinct from old.approved then + raise exception 'non-admins cannot change approved'; + end if; + end if; + return new; +end;$function$ +; + +CREATE OR REPLACE FUNCTION public.handle_new_user() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + insert into public.profiles (id, full_name, avatar_url) + values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url'); + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION public.is_admin() + RETURNS boolean + LANGUAGE sql + STABLE +AS $function$ + select + coalesce(get_my_claim('claims_admin')::bool,false) +$function$ +; + +CREATE OR REPLACE FUNCTION public.is_claims_admin() + RETURNS boolean + LANGUAGE plpgsql +AS $function$ + BEGIN + IF session_user = 'authenticator' THEN + -------------------------------------------- + -- To disallow any authenticated app users + -- from editing claims, delete the following + -- block of code and replace it with: + -- RETURN FALSE; + -------------------------------------------- + IF extract(epoch from now()) > coalesce((current_setting('request.jwt.claims', true)::jsonb)->>'exp', '0')::numeric THEN + return false; -- jwt expired + END IF; + If current_setting('request.jwt.claims', true)::jsonb->>'role' = 'service_role' THEN + RETURN true; -- service role users have admin rights + END IF; + IF coalesce((current_setting('request.jwt.claims', true)::jsonb)->'app_metadata'->'claims_admin', 'false')::bool THEN + return true; -- user has claims_admin set to true + ELSE + return false; -- user does NOT have claims_admin set to true + END IF; + -------------------------------------------- + -- End of block + -------------------------------------------- + ELSE -- not a user session, probably being called from a trigger or something + return true; + END IF; + END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.patch_downloads_dec_hack_downloads() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +declare + hack_slug text; +begin + select p.parent_hack into hack_slug + from public.patches p + where p.id = OLD.patch; + + if hack_slug is not null then + update public.hacks h + set downloads = greatest(h.downloads - 1, 0) + where h.slug = hack_slug; + end if; + + return OLD; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION public.patch_downloads_inc_hack_downloads() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +declare + hack_slug text; +begin + select p.parent_hack into hack_slug + from public.patches p + where p.id = NEW.patch; + + if hack_slug is not null then + update public.hacks h + set downloads = h.downloads + 1 + where h.slug = hack_slug; + end if; + + return NEW; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION public.set_claim(uid uuid, claim text, value jsonb) + RETURNS text + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO 'public' +AS $function$ + BEGIN + IF NOT is_claims_admin() THEN + RETURN 'error: access denied'; + ELSE + update auth.users set raw_app_meta_data = + raw_app_meta_data || + json_build_object(claim, value)::jsonb where id = uid; + return 'OK'; + END IF; + END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.set_updated_at() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +begin + new.updated_at = now(); + return new; +end; +$function$ +; + +create policy "Team members can update hacks." +on "public"."hacks" +as permissive +for update +to public +using ((EXISTS ( SELECT 1 + FROM hack_team + WHERE ((hack_team.hack_slug = hacks.slug) AND (hack_team.userid = auth.uid()))))) +with check ((EXISTS ( SELECT 1 + FROM hack_team + WHERE ((hack_team.hack_slug = hacks.slug) AND (hack_team.userid = auth.uid()))))); + + + +