From c220bbf98e116983ded877429ce7fb5ae2375740 Mon Sep 17 00:00:00 2001 From: Jared Schoeny Date: Tue, 23 Dec 2025 23:09:43 -1000 Subject: [PATCH] Add db migration for more hack features (like completion status, rejection, and patch groups) --- .../20251224083033_add_hack_features.sql | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 supabase/migrations/20251224083033_add_hack_features.sql diff --git a/supabase/migrations/20251224083033_add_hack_features.sql b/supabase/migrations/20251224083033_add_hack_features.sql new file mode 100644 index 0000000..7baefc0 --- /dev/null +++ b/supabase/migrations/20251224083033_add_hack_features.sql @@ -0,0 +1,143 @@ +create type public."Completion Status" as enum ('Complete', 'Demo', 'Alpha', 'Beta'); + +alter table if exists public.hacks + add column if not exists is_archive boolean not null default false; + +alter table if exists public.hacks + add column if not exists rejected boolean not null default false; + +alter table if exists public.hacks + add column if not exists rejected_at timestamp with time zone; + +alter table if exists public.hacks + add column if not exists rejected_reason text; + +alter table if exists public.hacks + add column if not exists rejected_by uuid; + +alter table if exists public.hacks + add column if not exists completion_status "Completion Status"; + +-- Foreign key for rejected_by -> auth.users(id) +alter table "public"."hacks" add constraint "hacks_rejected_by_fkey" FOREIGN KEY (rejected_by) REFERENCES auth.users(id) ON DELETE SET NULL not valid; +alter table "public"."hacks" validate constraint "hacks_rejected_by_fkey"; + + +CREATE OR REPLACE FUNCTION public.is_archive_hack_for_archiver(hack_slug text) +RETURNS boolean +LANGUAGE sql +STABLE +SECURITY DEFINER +AS $$ + SELECT EXISTS ( + SELECT 1 + FROM public.hacks h + WHERE h.slug = hack_slug + AND h.is_archive = true + ); +$$; + + +create table if not exists public.patch_groups ( + "id" bigint generated by default as identity not null, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone not null default now(), + "name" text not null, + "hack_slug" text not null, + "patch_id" bigint not null, + "order" integer not null +); + +alter table public.patch_groups owner to "postgres"; + +-- Primary key +CREATE UNIQUE INDEX patch_groups_pkey ON public.patch_groups USING btree (id); +alter table "public"."patch_groups" add constraint "patch_groups_pkey" PRIMARY KEY using index "patch_groups_pkey"; + +-- Foreign keys +alter table "public"."patch_groups" add constraint "patch_groups_patch_id_fkey" FOREIGN KEY (patch_id) REFERENCES patches(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; +alter table "public"."patch_groups" validate constraint "patch_groups_patch_id_fkey"; + +alter table "public"."patch_groups" add constraint "patch_groups_hack_slug_fkey" FOREIGN KEY (hack_slug) REFERENCES hacks(slug) ON UPDATE CASCADE ON DELETE CASCADE not valid; +alter table "public"."patch_groups" validate constraint "patch_groups_hack_slug_fkey"; + +-- Unique constraints +-- A patch can only appear once per group +CREATE UNIQUE INDEX patch_groups_hack_slug_name_patch_id_key ON public.patch_groups USING btree (hack_slug, name, patch_id); +alter table "public"."patch_groups" add constraint "patch_groups_hack_slug_name_patch_id_key" UNIQUE using index "patch_groups_hack_slug_name_patch_id_key"; + +-- Performance indexes +CREATE INDEX patch_groups_hack_slug_idx ON public.patch_groups USING btree (hack_slug); +CREATE INDEX patch_groups_patch_id_idx ON public.patch_groups USING btree (patch_id); +CREATE INDEX patch_groups_name_idx ON public.patch_groups USING btree (name); + +-- Enable RLS +alter table "public"."patch_groups" enable row level security; + +-- RLS Policies +-- Public read access (assuming patches are public) +create policy "Patch groups are viewable by everyone" +on "public"."patch_groups" +for select +using (true); + +-- Allow authenticated users to insert patch groups for their own hacks +create policy "Users can insert patch groups for own hacks" +on "public"."patch_groups" +for insert +with check ( + public.is_admin() OR + (public.is_archiver() AND public.is_archive_hack_for_archiver(patch_groups.hack_slug)) OR + EXISTS ( + SELECT 1 + FROM public.hacks h + WHERE h.slug = patch_groups.hack_slug + AND h.created_by = auth.uid() + ) +); + +-- Allow authenticated users to update patch groups for their own hacks +create policy "Users can update patch groups for own hacks" +on "public"."patch_groups" +for update +using ( + public.is_admin() OR + (public.is_archiver() AND public.is_archive_hack_for_archiver(patch_groups.hack_slug)) OR + EXISTS ( + SELECT 1 + FROM public.hacks h + WHERE h.slug = patch_groups.hack_slug + AND h.created_by = auth.uid() + ) +) +with check ( + public.is_admin() OR + (public.is_archiver() AND public.is_archive_hack_for_archiver(patch_groups.hack_slug)) OR + EXISTS ( + SELECT 1 + FROM public.hacks h + WHERE h.slug = patch_groups.hack_slug + AND h.created_by = auth.uid() + ) +); + +-- Allow authenticated users to delete patch groups for their own hacks +create policy "Users can delete patch groups for own hacks" +on "public"."patch_groups" +for delete +using ( + public.is_admin() OR + (public.is_archiver() AND public.is_archive_hack_for_archiver(patch_groups.hack_slug)) OR + EXISTS ( + SELECT 1 + FROM public.hacks h + WHERE h.slug = patch_groups.hack_slug + AND h.created_by = auth.uid() + ) +); + +-- Trigger for updated_at +CREATE OR REPLACE TRIGGER set_patch_groups_updated_at +BEFORE UPDATE ON public.patch_groups +FOR EACH ROW +EXECUTE FUNCTION public.set_updated_at();