361 lines
14 KiB
PL/PgSQL
361 lines
14 KiB
PL/PgSQL
-- ============================================================
|
|
-- BROADCAST BEAT MARKETPLACE — PHASE 1 MVP MIGRATION
|
|
-- Additive only: all tables prefixed marketplace_
|
|
-- ============================================================
|
|
|
|
-- ── TYPES ────────────────────────────────────────────────────
|
|
DROP TYPE IF EXISTS public.marketplace_profile_type CASCADE;
|
|
CREATE TYPE public.marketplace_profile_type AS ENUM ('crew', 'vendor');
|
|
|
|
DROP TYPE IF EXISTS public.marketplace_profile_status CASCADE;
|
|
CREATE TYPE public.marketplace_profile_status AS ENUM ('pending', 'active', 'rejected');
|
|
|
|
DROP TYPE IF EXISTS public.marketplace_subscription_tier CASCADE;
|
|
CREATE TYPE public.marketplace_subscription_tier AS ENUM ('free', 'pro', 'featured');
|
|
|
|
DROP TYPE IF EXISTS public.marketplace_job_type CASCADE;
|
|
CREATE TYPE public.marketplace_job_type AS ENUM ('full_time', 'part_time', 'contract', 'temp');
|
|
|
|
DROP TYPE IF EXISTS public.marketplace_listing_status CASCADE;
|
|
CREATE TYPE public.marketplace_listing_status AS ENUM ('active', 'expired', 'draft');
|
|
|
|
-- ── CORE TABLES ───────────────────────────────────────────────
|
|
|
|
-- Marketplace profiles (one per user)
|
|
CREATE TABLE IF NOT EXISTS public.marketplace_profiles (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
profile_type public.marketplace_profile_type NOT NULL,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
status public.marketplace_profile_status NOT NULL DEFAULT 'pending',
|
|
subscription_tier public.marketplace_subscription_tier NOT NULL DEFAULT 'free',
|
|
stripe_customer_id TEXT,
|
|
stripe_subscription_id TEXT,
|
|
rejection_reason TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Crew member details
|
|
CREATE TABLE IF NOT EXISTS public.marketplace_crew_details (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
profile_id UUID NOT NULL REFERENCES public.marketplace_profiles(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
headshot_url TEXT,
|
|
location TEXT NOT NULL,
|
|
union_status TEXT DEFAULT 'non_union',
|
|
day_rate INTEGER,
|
|
bio TEXT,
|
|
skills TEXT[] DEFAULT ARRAY[]::TEXT[],
|
|
equipment TEXT[] DEFAULT ARRAY[]::TEXT[],
|
|
reel_url TEXT,
|
|
resume_url TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Vendor / company details
|
|
CREATE TABLE IF NOT EXISTS public.marketplace_vendor_details (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
profile_id UUID NOT NULL REFERENCES public.marketplace_profiles(id) ON DELETE CASCADE,
|
|
company_name TEXT NOT NULL,
|
|
logo_url TEXT,
|
|
location TEXT NOT NULL,
|
|
category TEXT NOT NULL,
|
|
description TEXT,
|
|
website TEXT,
|
|
contact_email TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Job board
|
|
CREATE TABLE IF NOT EXISTS public.marketplace_jobs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
profile_id UUID NOT NULL REFERENCES public.marketplace_profiles(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
company TEXT NOT NULL,
|
|
location TEXT NOT NULL,
|
|
job_type public.marketplace_job_type NOT NULL DEFAULT 'full_time',
|
|
salary_min INTEGER,
|
|
salary_max INTEGER,
|
|
description TEXT NOT NULL,
|
|
apply_url TEXT,
|
|
apply_email TEXT,
|
|
expires_at TIMESTAMPTZ,
|
|
status public.marketplace_listing_status NOT NULL DEFAULT 'active',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Gig board
|
|
CREATE TABLE IF NOT EXISTS public.marketplace_gigs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
profile_id UUID NOT NULL REFERENCES public.marketplace_profiles(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
company TEXT NOT NULL,
|
|
location TEXT NOT NULL,
|
|
gig_type TEXT NOT NULL,
|
|
rate TEXT,
|
|
start_date DATE,
|
|
end_date DATE,
|
|
description TEXT NOT NULL,
|
|
contact_method TEXT,
|
|
expires_at TIMESTAMPTZ,
|
|
status public.marketplace_listing_status NOT NULL DEFAULT 'active',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── INDEXES ───────────────────────────────────────────────────
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_profiles_user_id ON public.marketplace_profiles(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_profiles_slug ON public.marketplace_profiles(slug);
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_profiles_status ON public.marketplace_profiles(status);
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_crew_profile_id ON public.marketplace_crew_details(profile_id);
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_vendor_profile_id ON public.marketplace_vendor_details(profile_id);
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_jobs_profile_id ON public.marketplace_jobs(profile_id);
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_jobs_status ON public.marketplace_jobs(status);
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_gigs_profile_id ON public.marketplace_gigs(profile_id);
|
|
CREATE INDEX IF NOT EXISTS idx_marketplace_gigs_status ON public.marketplace_gigs(status);
|
|
|
|
-- ── FUNCTIONS ─────────────────────────────────────────────────
|
|
|
|
-- Auto-update updated_at
|
|
CREATE OR REPLACE FUNCTION public.marketplace_set_updated_at()
|
|
RETURNS TRIGGER
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
-- Check if user is admin (uses auth metadata to avoid recursion)
|
|
CREATE OR REPLACE FUNCTION public.marketplace_is_admin()
|
|
RETURNS BOOLEAN
|
|
LANGUAGE sql
|
|
STABLE
|
|
SECURITY DEFINER
|
|
AS $$
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM auth.users
|
|
WHERE id = auth.uid()
|
|
AND (
|
|
raw_user_meta_data->>'role' = 'admin'
|
|
OR raw_app_meta_data->>'role' = 'admin'
|
|
)
|
|
)
|
|
$$;
|
|
|
|
-- Check if user has active pro/featured subscription
|
|
CREATE OR REPLACE FUNCTION public.marketplace_has_active_subscription()
|
|
RETURNS BOOLEAN
|
|
LANGUAGE sql
|
|
STABLE
|
|
SECURITY DEFINER
|
|
AS $$
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles
|
|
WHERE user_id = auth.uid()
|
|
AND subscription_tier IN ('pro', 'featured')
|
|
AND status = 'active'
|
|
)
|
|
$$;
|
|
|
|
-- ── ENABLE RLS ────────────────────────────────────────────────
|
|
ALTER TABLE public.marketplace_profiles ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.marketplace_crew_details ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.marketplace_vendor_details ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.marketplace_jobs ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.marketplace_gigs ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- ── RLS POLICIES ─────────────────────────────────────────────
|
|
|
|
-- marketplace_profiles
|
|
DROP POLICY IF EXISTS "mp_public_read_active" ON public.marketplace_profiles;
|
|
CREATE POLICY "mp_public_read_active" ON public.marketplace_profiles
|
|
FOR SELECT TO public
|
|
USING (status = 'active');
|
|
|
|
DROP POLICY IF EXISTS "mp_owner_read_own" ON public.marketplace_profiles;
|
|
CREATE POLICY "mp_owner_read_own" ON public.marketplace_profiles
|
|
FOR SELECT TO authenticated
|
|
USING (user_id = auth.uid());
|
|
|
|
DROP POLICY IF EXISTS "mp_owner_insert" ON public.marketplace_profiles;
|
|
CREATE POLICY "mp_owner_insert" ON public.marketplace_profiles
|
|
FOR INSERT TO authenticated
|
|
WITH CHECK (user_id = auth.uid());
|
|
|
|
DROP POLICY IF EXISTS "mp_owner_update" ON public.marketplace_profiles;
|
|
CREATE POLICY "mp_owner_update" ON public.marketplace_profiles
|
|
FOR UPDATE TO authenticated
|
|
USING (user_id = auth.uid())
|
|
WITH CHECK (user_id = auth.uid());
|
|
|
|
DROP POLICY IF EXISTS "mp_admin_all" ON public.marketplace_profiles;
|
|
CREATE POLICY "mp_admin_all" ON public.marketplace_profiles
|
|
FOR ALL TO authenticated
|
|
USING (public.marketplace_is_admin())
|
|
WITH CHECK (public.marketplace_is_admin());
|
|
|
|
-- marketplace_crew_details
|
|
DROP POLICY IF EXISTS "mcd_public_read" ON public.marketplace_crew_details;
|
|
CREATE POLICY "mcd_public_read" ON public.marketplace_crew_details
|
|
FOR SELECT TO public
|
|
USING (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.status = 'active'
|
|
));
|
|
|
|
DROP POLICY IF EXISTS "mcd_owner_all" ON public.marketplace_crew_details;
|
|
CREATE POLICY "mcd_owner_all" ON public.marketplace_crew_details
|
|
FOR ALL TO authenticated
|
|
USING (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.user_id = auth.uid()
|
|
))
|
|
WITH CHECK (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.user_id = auth.uid()
|
|
));
|
|
|
|
DROP POLICY IF EXISTS "mcd_admin_all" ON public.marketplace_crew_details;
|
|
CREATE POLICY "mcd_admin_all" ON public.marketplace_crew_details
|
|
FOR ALL TO authenticated
|
|
USING (public.marketplace_is_admin())
|
|
WITH CHECK (public.marketplace_is_admin());
|
|
|
|
-- marketplace_vendor_details
|
|
DROP POLICY IF EXISTS "mvd_public_read" ON public.marketplace_vendor_details;
|
|
CREATE POLICY "mvd_public_read" ON public.marketplace_vendor_details
|
|
FOR SELECT TO public
|
|
USING (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.status = 'active'
|
|
));
|
|
|
|
DROP POLICY IF EXISTS "mvd_owner_all" ON public.marketplace_vendor_details;
|
|
CREATE POLICY "mvd_owner_all" ON public.marketplace_vendor_details
|
|
FOR ALL TO authenticated
|
|
USING (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.user_id = auth.uid()
|
|
))
|
|
WITH CHECK (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.user_id = auth.uid()
|
|
));
|
|
|
|
DROP POLICY IF EXISTS "mvd_admin_all" ON public.marketplace_vendor_details;
|
|
CREATE POLICY "mvd_admin_all" ON public.marketplace_vendor_details
|
|
FOR ALL TO authenticated
|
|
USING (public.marketplace_is_admin())
|
|
WITH CHECK (public.marketplace_is_admin());
|
|
|
|
-- marketplace_jobs
|
|
DROP POLICY IF EXISTS "mj_public_read_active" ON public.marketplace_jobs;
|
|
CREATE POLICY "mj_public_read_active" ON public.marketplace_jobs
|
|
FOR SELECT TO public
|
|
USING (status = 'active');
|
|
|
|
DROP POLICY IF EXISTS "mj_owner_all" ON public.marketplace_jobs;
|
|
CREATE POLICY "mj_owner_all" ON public.marketplace_jobs
|
|
FOR ALL TO authenticated
|
|
USING (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.user_id = auth.uid()
|
|
))
|
|
WITH CHECK (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.user_id = auth.uid()
|
|
));
|
|
|
|
DROP POLICY IF EXISTS "mj_admin_all" ON public.marketplace_jobs;
|
|
CREATE POLICY "mj_admin_all" ON public.marketplace_jobs
|
|
FOR ALL TO authenticated
|
|
USING (public.marketplace_is_admin())
|
|
WITH CHECK (public.marketplace_is_admin());
|
|
|
|
-- marketplace_gigs
|
|
DROP POLICY IF EXISTS "mg_public_read_active" ON public.marketplace_gigs;
|
|
CREATE POLICY "mg_public_read_active" ON public.marketplace_gigs
|
|
FOR SELECT TO public
|
|
USING (status = 'active');
|
|
|
|
DROP POLICY IF EXISTS "mg_owner_all" ON public.marketplace_gigs;
|
|
CREATE POLICY "mg_owner_all" ON public.marketplace_gigs
|
|
FOR ALL TO authenticated
|
|
USING (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.user_id = auth.uid()
|
|
))
|
|
WITH CHECK (EXISTS (
|
|
SELECT 1 FROM public.marketplace_profiles mp
|
|
WHERE mp.id = profile_id AND mp.user_id = auth.uid()
|
|
));
|
|
|
|
DROP POLICY IF EXISTS "mg_admin_all" ON public.marketplace_gigs;
|
|
CREATE POLICY "mg_admin_all" ON public.marketplace_gigs
|
|
FOR ALL TO authenticated
|
|
USING (public.marketplace_is_admin())
|
|
WITH CHECK (public.marketplace_is_admin());
|
|
|
|
-- ── TRIGGERS ─────────────────────────────────────────────────
|
|
DROP TRIGGER IF EXISTS marketplace_profiles_updated_at ON public.marketplace_profiles;
|
|
CREATE TRIGGER marketplace_profiles_updated_at
|
|
BEFORE UPDATE ON public.marketplace_profiles
|
|
FOR EACH ROW EXECUTE FUNCTION public.marketplace_set_updated_at();
|
|
|
|
DROP TRIGGER IF EXISTS marketplace_crew_updated_at ON public.marketplace_crew_details;
|
|
CREATE TRIGGER marketplace_crew_updated_at
|
|
BEFORE UPDATE ON public.marketplace_crew_details
|
|
FOR EACH ROW EXECUTE FUNCTION public.marketplace_set_updated_at();
|
|
|
|
DROP TRIGGER IF EXISTS marketplace_vendor_updated_at ON public.marketplace_vendor_details;
|
|
CREATE TRIGGER marketplace_vendor_updated_at
|
|
BEFORE UPDATE ON public.marketplace_vendor_details
|
|
FOR EACH ROW EXECUTE FUNCTION public.marketplace_set_updated_at();
|
|
|
|
-- ── STORAGE BUCKETS (via SQL) ─────────────────────────────────
|
|
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
VALUES
|
|
('marketplace-headshots', 'marketplace-headshots', true, 5242880,
|
|
ARRAY['image/jpeg','image/jpg','image/png','image/webp']::TEXT[]),
|
|
('marketplace-logos', 'marketplace-logos', true, 5242880,
|
|
ARRAY['image/jpeg','image/jpg','image/png','image/webp','image/svg+xml']::TEXT[]),
|
|
('marketplace-resumes', 'marketplace-resumes', false, 10485760,
|
|
ARRAY['application/pdf','application/msword',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document']::TEXT[])
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
-- Storage RLS
|
|
DROP POLICY IF EXISTS "marketplace_headshots_public_read" ON storage.objects;
|
|
CREATE POLICY "marketplace_headshots_public_read" ON storage.objects
|
|
FOR SELECT TO public
|
|
USING (bucket_id = 'marketplace-headshots');
|
|
|
|
DROP POLICY IF EXISTS "marketplace_headshots_auth_write" ON storage.objects;
|
|
CREATE POLICY "marketplace_headshots_auth_write" ON storage.objects
|
|
FOR INSERT TO authenticated
|
|
WITH CHECK (bucket_id = 'marketplace-headshots');
|
|
|
|
DROP POLICY IF EXISTS "marketplace_logos_public_read" ON storage.objects;
|
|
CREATE POLICY "marketplace_logos_public_read" ON storage.objects
|
|
FOR SELECT TO public
|
|
USING (bucket_id = 'marketplace-logos');
|
|
|
|
DROP POLICY IF EXISTS "marketplace_logos_auth_write" ON storage.objects;
|
|
CREATE POLICY "marketplace_logos_auth_write" ON storage.objects
|
|
FOR INSERT TO authenticated
|
|
WITH CHECK (bucket_id = 'marketplace-logos');
|
|
|
|
DROP POLICY IF EXISTS "marketplace_resumes_auth_read" ON storage.objects;
|
|
CREATE POLICY "marketplace_resumes_auth_read" ON storage.objects
|
|
FOR SELECT TO authenticated
|
|
USING (bucket_id = 'marketplace-resumes' AND auth.uid()::TEXT = (storage.foldername(name))[1]);
|
|
|
|
DROP POLICY IF EXISTS "marketplace_resumes_auth_write" ON storage.objects;
|
|
CREATE POLICY "marketplace_resumes_auth_write" ON storage.objects
|
|
FOR INSERT TO authenticated
|
|
WITH CHECK (bucket_id = 'marketplace-resumes');
|