diff --git a/src/types/db.ts b/src/types/db.ts new file mode 100644 index 0000000..7e8180c --- /dev/null +++ b/src/types/db.ts @@ -0,0 +1,458 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + // Allows to automatically instantiate createClient with right options + // instead of createClient(URL, KEY) + __InternalSupabase: { + PostgrestVersion: "13.0.5" + } + public: { + Tables: { + hack_covers: { + Row: { + alt: string | null + hack_slug: string + id: number + position: number + url: string + } + Insert: { + alt?: string | null + hack_slug: string + id?: number + position?: number + url: string + } + Update: { + alt?: string | null + hack_slug?: string + id?: number + position?: number + url?: string + } + Relationships: [ + { + foreignKeyName: "hack_covers_hack_slug_fkey" + columns: ["hack_slug"] + isOneToOne: false + referencedRelation: "hacks" + referencedColumns: ["slug"] + }, + ] + } + hack_tags: { + Row: { + hack_slug: string + tag_id: number + } + Insert: { + hack_slug: string + tag_id: number + } + Update: { + hack_slug?: string + tag_id?: number + } + Relationships: [ + { + foreignKeyName: "hack_tags_hack_slug_fkey" + columns: ["hack_slug"] + isOneToOne: false + referencedRelation: "hacks" + referencedColumns: ["slug"] + }, + { + foreignKeyName: "hack_tags_tag_id_fkey" + columns: ["tag_id"] + isOneToOne: false + referencedRelation: "tags" + referencedColumns: ["id"] + }, + ] + } + hacks: { + Row: { + approved: boolean + approved_at: string | null + approved_by: string | null + author: string + base_rom: string + box_art: string | null + covers: string[] + created_at: string + created_by: string + current_patch: number | null + description: string + downloads: number + estimated_release: string | null + language: string + patch_url: string + search: unknown | null + slug: string + social_links: Json | null + summary: string + tags: string[] + title: string + updated_at: string | null + version: string + } + Insert: { + approved?: boolean + approved_at?: string | null + approved_by?: string | null + author: string + base_rom: string + box_art?: string | null + covers?: string[] + created_at?: string + created_by: string + current_patch?: number | null + description: string + downloads?: number + estimated_release?: string | null + language: string + patch_url: string + search?: unknown | null + slug: string + social_links?: Json | null + summary: string + tags?: string[] + title: string + updated_at?: string | null + version: string + } + Update: { + approved?: boolean + approved_at?: string | null + approved_by?: string | null + author?: string + base_rom?: string + box_art?: string | null + covers?: string[] + created_at?: string + created_by?: string + current_patch?: number | null + description?: string + downloads?: number + estimated_release?: string | null + language?: string + patch_url?: string + search?: unknown | null + slug?: string + social_links?: Json | null + summary?: string + tags?: string[] + title?: string + updated_at?: string | null + version?: string + } + Relationships: [ + { + foreignKeyName: "hacks_current_patch_fkey" + columns: ["current_patch"] + isOneToOne: false + referencedRelation: "patches" + referencedColumns: ["id"] + }, + ] + } + invite_codes: { + Row: { + code: string + created_at: string + used_by: string | null + } + Insert: { + code: string + created_at?: string + used_by?: string | null + } + Update: { + code?: string + created_at?: string + used_by?: string | null + } + Relationships: [] + } + patch_downloads: { + Row: { + created_at: string + id: number + patch: number | null + } + Insert: { + created_at?: string + id?: number + patch?: number | null + } + Update: { + created_at?: string + id?: number + patch?: number | null + } + Relationships: [ + { + foreignKeyName: "patch_downloads_patch_fkey" + columns: ["patch"] + isOneToOne: false + referencedRelation: "patches" + referencedColumns: ["id"] + }, + ] + } + patches: { + Row: { + bucket: string + created_at: string + filename: string + id: number + parent_hack: string | null + version: string + } + Insert: { + bucket: string + created_at?: string + filename: string + id?: number + parent_hack?: string | null + version: string + } + Update: { + bucket?: string + created_at?: string + filename?: string + id?: number + parent_hack?: string | null + version?: string + } + Relationships: [ + { + foreignKeyName: "patches_parent_hack_fkey" + columns: ["parent_hack"] + isOneToOne: false + referencedRelation: "hacks" + referencedColumns: ["slug"] + }, + ] + } + profiles: { + Row: { + avatar_url: string | null + full_name: string | null + id: string + updated_at: string | null + username: string | null + website: string | null + } + Insert: { + avatar_url?: string | null + full_name?: string | null + id: string + updated_at?: string | null + username?: string | null + website?: string | null + } + Update: { + avatar_url?: string | null + full_name?: string | null + id?: string + updated_at?: string | null + username?: string | null + website?: string | null + } + Relationships: [] + } + tags: { + Row: { + id: number + name: string + } + Insert: { + id?: number + name: string + } + Update: { + id?: number + name?: string + } + Relationships: [] + } + } + Views: { + [_ in never]: never + } + Functions: { + gtrgm_compress: { + Args: { "": unknown } + Returns: unknown + } + gtrgm_decompress: { + Args: { "": unknown } + Returns: unknown + } + gtrgm_in: { + Args: { "": unknown } + Returns: unknown + } + gtrgm_options: { + Args: { "": unknown } + Returns: undefined + } + gtrgm_out: { + Args: { "": unknown } + Returns: unknown + } + is_admin: { + Args: Record + Returns: boolean + } + set_limit: { + Args: { "": number } + Returns: number + } + show_limit: { + Args: Record + Returns: number + } + show_trgm: { + Args: { "": string } + Returns: string[] + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type DatabaseWithoutInternals = Omit + +type DefaultSchema = DatabaseWithoutInternals[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + public: { + Enums: {}, + }, +} as const diff --git a/src/utils/supabase/client.ts b/src/utils/supabase/client.ts index 30f697a..82fa927 100644 --- a/src/utils/supabase/client.ts +++ b/src/utils/supabase/client.ts @@ -1,8 +1,9 @@ import { createBrowserClient } from "@supabase/ssr"; +import { Database } from "@/types/db"; export function createClient() { // Create a supabase client on the browser with project's credentials - return createBrowserClient( + return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY! ); diff --git a/src/utils/supabase/server.ts b/src/utils/supabase/server.ts index 5b32e5b..5314470 100644 --- a/src/utils/supabase/server.ts +++ b/src/utils/supabase/server.ts @@ -1,12 +1,13 @@ import { createServerClient } from "@supabase/ssr"; import { cookies } from "next/headers"; +import { Database } from "@/types/db"; export async function createClient() { const cookieStore = await cookies(); // Create a server's supabase client with newly configured cookie, // which could be used to maintain user's session - return createServerClient( + return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!, { diff --git a/supabase/migrations/20251011204316_remote_schema.sql b/supabase/migrations/20251011204316_remote_schema.sql new file mode 100644 index 0000000..e6840c3 --- /dev/null +++ b/supabase/migrations/20251011204316_remote_schema.sql @@ -0,0 +1,431 @@ +revoke delete on table "public"."hack_covers" from "anon"; + +revoke insert on table "public"."hack_covers" from "anon"; + +revoke references on table "public"."hack_covers" from "anon"; + +revoke select on table "public"."hack_covers" from "anon"; + +revoke trigger on table "public"."hack_covers" from "anon"; + +revoke truncate on table "public"."hack_covers" from "anon"; + +revoke update on table "public"."hack_covers" from "anon"; + +revoke delete on table "public"."hack_covers" from "authenticated"; + +revoke insert on table "public"."hack_covers" from "authenticated"; + +revoke references on table "public"."hack_covers" from "authenticated"; + +revoke select on table "public"."hack_covers" from "authenticated"; + +revoke trigger on table "public"."hack_covers" from "authenticated"; + +revoke truncate on table "public"."hack_covers" from "authenticated"; + +revoke update on table "public"."hack_covers" from "authenticated"; + +revoke delete on table "public"."hack_covers" from "service_role"; + +revoke insert on table "public"."hack_covers" from "service_role"; + +revoke references on table "public"."hack_covers" from "service_role"; + +revoke select on table "public"."hack_covers" from "service_role"; + +revoke trigger on table "public"."hack_covers" from "service_role"; + +revoke truncate on table "public"."hack_covers" from "service_role"; + +revoke update on table "public"."hack_covers" from "service_role"; + +revoke delete on table "public"."hack_tags" from "anon"; + +revoke insert on table "public"."hack_tags" from "anon"; + +revoke references on table "public"."hack_tags" from "anon"; + +revoke select on table "public"."hack_tags" from "anon"; + +revoke trigger on table "public"."hack_tags" from "anon"; + +revoke truncate on table "public"."hack_tags" from "anon"; + +revoke update on table "public"."hack_tags" from "anon"; + +revoke delete on table "public"."hack_tags" from "authenticated"; + +revoke insert on table "public"."hack_tags" from "authenticated"; + +revoke references on table "public"."hack_tags" from "authenticated"; + +revoke select on table "public"."hack_tags" from "authenticated"; + +revoke trigger on table "public"."hack_tags" from "authenticated"; + +revoke truncate on table "public"."hack_tags" from "authenticated"; + +revoke update on table "public"."hack_tags" from "authenticated"; + +revoke delete on table "public"."hack_tags" from "service_role"; + +revoke insert on table "public"."hack_tags" from "service_role"; + +revoke references on table "public"."hack_tags" from "service_role"; + +revoke select on table "public"."hack_tags" from "service_role"; + +revoke trigger on table "public"."hack_tags" from "service_role"; + +revoke truncate on table "public"."hack_tags" from "service_role"; + +revoke update on table "public"."hack_tags" from "service_role"; + +revoke delete on table "public"."hacks" from "anon"; + +revoke insert on table "public"."hacks" from "anon"; + +revoke references on table "public"."hacks" from "anon"; + +revoke select on table "public"."hacks" from "anon"; + +revoke trigger on table "public"."hacks" from "anon"; + +revoke truncate on table "public"."hacks" from "anon"; + +revoke update on table "public"."hacks" from "anon"; + +revoke delete on table "public"."hacks" from "authenticated"; + +revoke insert on table "public"."hacks" from "authenticated"; + +revoke references on table "public"."hacks" from "authenticated"; + +revoke select on table "public"."hacks" from "authenticated"; + +revoke trigger on table "public"."hacks" from "authenticated"; + +revoke truncate on table "public"."hacks" from "authenticated"; + +revoke update on table "public"."hacks" from "authenticated"; + +revoke delete on table "public"."hacks" from "service_role"; + +revoke insert on table "public"."hacks" from "service_role"; + +revoke references on table "public"."hacks" from "service_role"; + +revoke select on table "public"."hacks" from "service_role"; + +revoke trigger on table "public"."hacks" from "service_role"; + +revoke truncate on table "public"."hacks" from "service_role"; + +revoke update on table "public"."hacks" from "service_role"; + +revoke delete on table "public"."profiles" from "anon"; + +revoke insert on table "public"."profiles" from "anon"; + +revoke references on table "public"."profiles" from "anon"; + +revoke select on table "public"."profiles" from "anon"; + +revoke trigger on table "public"."profiles" from "anon"; + +revoke truncate on table "public"."profiles" from "anon"; + +revoke update on table "public"."profiles" from "anon"; + +revoke delete on table "public"."profiles" from "authenticated"; + +revoke insert on table "public"."profiles" from "authenticated"; + +revoke references on table "public"."profiles" from "authenticated"; + +revoke select on table "public"."profiles" from "authenticated"; + +revoke trigger on table "public"."profiles" from "authenticated"; + +revoke truncate on table "public"."profiles" from "authenticated"; + +revoke update on table "public"."profiles" from "authenticated"; + +revoke delete on table "public"."profiles" from "service_role"; + +revoke insert on table "public"."profiles" from "service_role"; + +revoke references on table "public"."profiles" from "service_role"; + +revoke select on table "public"."profiles" from "service_role"; + +revoke trigger on table "public"."profiles" from "service_role"; + +revoke truncate on table "public"."profiles" from "service_role"; + +revoke update on table "public"."profiles" from "service_role"; + +revoke delete on table "public"."tags" from "anon"; + +revoke insert on table "public"."tags" from "anon"; + +revoke references on table "public"."tags" from "anon"; + +revoke select on table "public"."tags" from "anon"; + +revoke trigger on table "public"."tags" from "anon"; + +revoke truncate on table "public"."tags" from "anon"; + +revoke update on table "public"."tags" from "anon"; + +revoke delete on table "public"."tags" from "authenticated"; + +revoke insert on table "public"."tags" from "authenticated"; + +revoke references on table "public"."tags" from "authenticated"; + +revoke select on table "public"."tags" from "authenticated"; + +revoke trigger on table "public"."tags" from "authenticated"; + +revoke truncate on table "public"."tags" from "authenticated"; + +revoke update on table "public"."tags" from "authenticated"; + +revoke delete on table "public"."tags" from "service_role"; + +revoke insert on table "public"."tags" from "service_role"; + +revoke references on table "public"."tags" from "service_role"; + +revoke select on table "public"."tags" from "service_role"; + +revoke trigger on table "public"."tags" from "service_role"; + +revoke truncate on table "public"."tags" from "service_role"; + +revoke update on table "public"."tags" from "service_role"; + +create table "public"."invite_codes" ( + "created_at" timestamp with time zone not null default now(), + "code" text not null, + "used_by" uuid +); + + +alter table "public"."invite_codes" enable row level security; + +create table "public"."patch_downloads" ( + "id" bigint generated by default as identity not null, + "created_at" timestamp with time zone not null default now(), + "patch" bigint +); + + +alter table "public"."patch_downloads" enable row level security; + +create table "public"."patches" ( + "id" bigint generated by default as identity not null, + "created_at" timestamp with time zone not null default now(), + "version" text not null, + "filename" text not null, + "bucket" text not null, + "parent_hack" text +); + + +alter table "public"."patches" enable row level security; + +alter table "public"."hacks" add column "approved_at" timestamp without time zone; + +alter table "public"."hacks" add column "approved_by" uuid; + +alter table "public"."hacks" add column "current_patch" bigint; + +alter table "public"."hacks" add column "estimated_release" date; + +alter table "public"."hacks" add column "language" text not null; + +CREATE UNIQUE INDEX invite_codes_code_key ON public.invite_codes USING btree (code); + +CREATE UNIQUE INDEX invite_codes_pkey ON public.invite_codes USING btree (code); + +CREATE UNIQUE INDEX patch_downloads_pkey ON public.patch_downloads USING btree (id); + +CREATE UNIQUE INDEX patches_pkey ON public.patches USING btree (id); + +alter table "public"."invite_codes" add constraint "invite_codes_pkey" PRIMARY KEY using index "invite_codes_pkey"; + +alter table "public"."patch_downloads" add constraint "patch_downloads_pkey" PRIMARY KEY using index "patch_downloads_pkey"; + +alter table "public"."patches" add constraint "patches_pkey" PRIMARY KEY using index "patches_pkey"; + +alter table "public"."hacks" add constraint "hacks_approved_by_fkey" FOREIGN KEY (approved_by) REFERENCES auth.users(id) ON DELETE SET NULL not valid; + +alter table "public"."hacks" validate constraint "hacks_approved_by_fkey"; + +alter table "public"."hacks" add constraint "hacks_current_patch_fkey" FOREIGN KEY (current_patch) REFERENCES patches(id) ON DELETE SET NULL not valid; + +alter table "public"."hacks" validate constraint "hacks_current_patch_fkey"; + +alter table "public"."invite_codes" add constraint "invite_codes_code_key" UNIQUE using index "invite_codes_code_key"; + +alter table "public"."invite_codes" add constraint "invite_codes_used_by_fkey" FOREIGN KEY (used_by) REFERENCES auth.users(id) ON DELETE CASCADE not valid; + +alter table "public"."invite_codes" validate constraint "invite_codes_used_by_fkey"; + +alter table "public"."patch_downloads" add constraint "patch_downloads_patch_fkey" FOREIGN KEY (patch) REFERENCES patches(id) ON UPDATE CASCADE ON DELETE SET NULL not valid; + +alter table "public"."patch_downloads" validate constraint "patch_downloads_patch_fkey"; + +alter table "public"."patches" add constraint "patches_parent_hack_fkey" FOREIGN KEY (parent_hack) REFERENCES hacks(slug) ON UPDATE CASCADE ON DELETE CASCADE not valid; + +alter table "public"."patches" validate constraint "patches_parent_hack_fkey"; + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.hacks_update_guard() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +begin + 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 + -- If using Supabase auth, service role bypasses RLS anyway, but keep for completeness + (current_setting('request.jwt.claims', true)::jsonb ? 'role' and + (current_setting('request.jwt.claims', true)::jsonb ->> 'role') = 'service_role') + or + ( + -- Check common locations for an admin flag/role + coalesce((current_setting('request.jwt.claims', true)::jsonb #>> '{app_metadata,role}'), '') = 'admin' + or coalesce((current_setting('request.jwt.claims', true)::jsonb #>> '{user_metadata,role}'), '') = 'admin' + or coalesce((current_setting('request.jwt.claims', true)::jsonb #>> '{claims,role}'), '') = 'admin' + or coalesce((current_setting('request.jwt.claims', true)::jsonb ->> 'admin'), 'false')::boolean = true + ); +$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 "Enable insert for admin users only" +on "public"."invite_codes" +as permissive +for insert +to supabase_admin +with check (true); + + +create policy "Allow supabase_admins to select" +on "public"."patch_downloads" +as permissive +for select +to supabase_admin +using (true); + + +create policy "Enable delete for users based on user_id" +on "public"."patch_downloads" +as permissive +for delete +to supabase_admin +using (true); + + +create policy "Enable read access for all users" +on "public"."patches" +as permissive +for select +to public +using (true); + + +create policy "Only allow authenticated users to create patches for hacks that" +on "public"."patches" +as permissive +for insert +to public +with check ((( SELECT auth.uid() AS uid) IN ( SELECT hack.created_by + FROM hacks hack + WHERE (hack.slug = patches.parent_hack)))); + + + +drop policy "Anyone can upload an avatar." on "storage"."objects"; + +drop policy "Authenticated users can upload hack boxart." on "storage"."objects"; + +drop policy "Authenticated users can upload hack covers." on "storage"."objects"; + + + create policy "Anyone can upload an avatar." + on "storage"."objects" + as permissive + for insert + to authenticated +with check ((bucket_id = 'avatars'::text)); + + + + create policy "Authenticated users can upload hack boxart." + on "storage"."objects" + as permissive + for insert + to authenticated +with check ((bucket_id = 'hack-boxart'::text)); + + + + create policy "Authenticated users can upload hack covers." + on "storage"."objects" + as permissive + for insert + to authenticated +with check ((bucket_id = 'hack-covers'::text)); + + +