initial commit: rocket.new export of broadcastbeat
This commit is contained in:
269
supabase/migrations/20260322210000_wp_user_migration.sql
Normal file
269
supabase/migrations/20260322210000_wp_user_migration.sql
Normal file
@@ -0,0 +1,269 @@
|
||||
-- ============================================================
|
||||
-- Migration: WordPress User Migration & Permissions
|
||||
-- Timestamp: 20260322210000
|
||||
-- Covers: wp_legacy_users table, phpass validation, role upgrades,
|
||||
-- featured category lock, post scheduling states
|
||||
-- ============================================================
|
||||
|
||||
-- 1. Add legacy WordPress user ID column to user_profiles
|
||||
ALTER TABLE public.user_profiles
|
||||
ADD COLUMN IF NOT EXISTS wp_user_id INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS wp_username TEXT,
|
||||
ADD COLUMN IF NOT EXISTS wp_hashed_password TEXT,
|
||||
ADD COLUMN IF NOT EXISTS password_upgraded BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS display_name TEXT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_profiles_wp_user_id ON public.user_profiles(wp_user_id) WHERE wp_user_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_user_profiles_wp_username ON public.user_profiles(wp_username) WHERE wp_username IS NOT NULL;
|
||||
|
||||
-- 2. wp_legacy_users staging table for import
|
||||
CREATE TABLE IF NOT EXISTS public.wp_legacy_users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
wp_id INTEGER NOT NULL UNIQUE,
|
||||
wp_username TEXT NOT NULL,
|
||||
wp_hashed_password TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
role TEXT NOT NULL DEFAULT 'contributor',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
auth_user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
imported_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at TIMESTAMPTZ,
|
||||
password_upgraded BOOLEAN NOT NULL DEFAULT false,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_wp_legacy_users_email ON public.wp_legacy_users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_wp_legacy_users_auth_user_id ON public.wp_legacy_users(auth_user_id) WHERE auth_user_id IS NOT NULL;
|
||||
|
||||
-- 3. Enable RLS on wp_legacy_users
|
||||
ALTER TABLE public.wp_legacy_users ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS "admin_manage_wp_legacy_users" ON public.wp_legacy_users;
|
||||
CREATE POLICY "admin_manage_wp_legacy_users"
|
||||
ON public.wp_legacy_users
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (public.is_admin_user())
|
||||
WITH CHECK (public.is_admin_user());
|
||||
|
||||
-- 4. Function: validate phpass password ($P$B... format)
|
||||
-- phpass uses MD5-based iterated hashing. We store the hash and validate server-side via API.
|
||||
-- This function records a login attempt and returns the stored hash for server-side validation.
|
||||
CREATE OR REPLACE FUNCTION public.get_wp_user_hash(p_email TEXT)
|
||||
RETURNS TABLE(
|
||||
wp_id INTEGER,
|
||||
wp_username TEXT,
|
||||
wp_hashed_password TEXT,
|
||||
role TEXT,
|
||||
is_active BOOLEAN,
|
||||
auth_user_id UUID,
|
||||
password_upgraded BOOLEAN
|
||||
)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT
|
||||
wlu.wp_id,
|
||||
wlu.wp_username,
|
||||
wlu.wp_hashed_password,
|
||||
wlu.role,
|
||||
wlu.is_active,
|
||||
wlu.auth_user_id,
|
||||
wlu.password_upgraded
|
||||
FROM public.wp_legacy_users wlu
|
||||
WHERE wlu.email = p_email
|
||||
LIMIT 1;
|
||||
$$;
|
||||
|
||||
-- 5. Function: mark password as upgraded after successful phpass login
|
||||
CREATE OR REPLACE FUNCTION public.mark_wp_password_upgraded(p_wp_id INTEGER, p_auth_user_id UUID)
|
||||
RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
BEGIN
|
||||
UPDATE public.wp_legacy_users
|
||||
SET password_upgraded = true,
|
||||
auth_user_id = p_auth_user_id,
|
||||
last_login_at = CURRENT_TIMESTAMP
|
||||
WHERE wp_id = p_wp_id;
|
||||
|
||||
UPDATE public.user_profiles
|
||||
SET password_upgraded = true,
|
||||
wp_user_id = p_wp_id
|
||||
WHERE id = p_auth_user_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 6. Function: get user role (extended to support new roles)
|
||||
CREATE OR REPLACE FUNCTION public.get_user_role()
|
||||
RETURNS TEXT
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT COALESCE(
|
||||
(SELECT role FROM public.user_profiles WHERE id = auth.uid() LIMIT 1),
|
||||
'reader'
|
||||
)
|
||||
$$;
|
||||
|
||||
-- 7. Function: check if current user can post to Featured category
|
||||
CREATE OR REPLACE FUNCTION public.can_post_to_featured()
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM public.user_profiles
|
||||
WHERE id = auth.uid()
|
||||
AND role IN ('administrator', 'admin', 'editor')
|
||||
)
|
||||
$$;
|
||||
|
||||
-- 8. Function: check if user is editor or admin
|
||||
CREATE OR REPLACE FUNCTION public.is_editor_or_admin()
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM public.user_profiles
|
||||
WHERE id = auth.uid()
|
||||
AND role IN ('administrator', 'admin', 'editor')
|
||||
)
|
||||
$$;
|
||||
|
||||
-- 9. Function: strip Featured category from contributor/author posts on save
|
||||
CREATE OR REPLACE FUNCTION public.enforce_featured_category_lock()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
v_role TEXT;
|
||||
BEGIN
|
||||
-- Get the author's role
|
||||
SELECT role INTO v_role
|
||||
FROM public.user_profiles
|
||||
WHERE id = NEW.author_id
|
||||
LIMIT 1;
|
||||
|
||||
-- If contributor or author tries to post to Featured, strip it and set pending
|
||||
IF v_role IN ('contributor', 'author') AND LOWER(COALESCE(NEW.category, '')) = 'featured' THEN
|
||||
NEW.category := 'Uncategorized';
|
||||
NEW.status := 'pending';
|
||||
END IF;
|
||||
|
||||
-- Contributors cannot publish directly
|
||||
IF v_role = 'contributor' AND NEW.status = 'published' THEN
|
||||
NEW.status := 'pending';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 10. Apply Featured category lock trigger to native_articles
|
||||
DROP TRIGGER IF EXISTS enforce_featured_lock_native ON public.native_articles;
|
||||
CREATE TRIGGER enforce_featured_lock_native
|
||||
BEFORE INSERT OR UPDATE ON public.native_articles
|
||||
FOR EACH ROW EXECUTE FUNCTION public.enforce_featured_category_lock();
|
||||
|
||||
-- 11. Add 'scheduled_pending_review' status support via check on native_articles
|
||||
-- (status column is TEXT, so we just document the allowed values via comment)
|
||||
COMMENT ON COLUMN public.native_articles.status IS
|
||||
'Allowed values: draft, pending, scheduled, scheduled_pending_review, published, archived';
|
||||
|
||||
-- 12. Update role_permissions to include new BB roles
|
||||
-- Upsert permissions for administrator role
|
||||
INSERT INTO public.role_permissions (role, can_publish_articles, can_edit_articles, can_delete_articles,
|
||||
can_manage_comments, can_moderate_forum, can_pin_threads, can_lock_threads, can_flag_content,
|
||||
can_view_analytics, can_manage_newsletter, can_invite_users, can_manage_users)
|
||||
VALUES
|
||||
('administrator', true, true, true, true, true, true, true, true, true, true, true, true),
|
||||
('editor', true, true, true, true, true, true, true, true, true, true, true, false),
|
||||
('author', true, true, true, false, false, false, false, true, false, false, false, false),
|
||||
('contributor', false, false, false, false, false, false, false, true, false, false, false, false)
|
||||
ON CONFLICT (role) DO UPDATE SET
|
||||
can_publish_articles = EXCLUDED.can_publish_articles,
|
||||
can_edit_articles = EXCLUDED.can_edit_articles,
|
||||
can_delete_articles = EXCLUDED.can_delete_articles,
|
||||
can_manage_comments = EXCLUDED.can_manage_comments,
|
||||
can_moderate_forum = EXCLUDED.can_moderate_forum,
|
||||
can_pin_threads = EXCLUDED.can_pin_threads,
|
||||
can_lock_threads = EXCLUDED.can_lock_threads,
|
||||
can_flag_content = EXCLUDED.can_flag_content,
|
||||
can_view_analytics = EXCLUDED.can_view_analytics,
|
||||
can_manage_newsletter = EXCLUDED.can_manage_newsletter,
|
||||
can_invite_users = EXCLUDED.can_invite_users,
|
||||
can_manage_users = EXCLUDED.can_manage_users,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
-- 13. Insert all WordPress users into wp_legacy_users staging table
|
||||
-- These users will be authenticated via phpass validation in the API layer
|
||||
-- Test accounts (IDs 757, 760, 761) are set to inactive
|
||||
DO $$
|
||||
BEGIN
|
||||
INSERT INTO public.wp_legacy_users (wp_id, wp_username, wp_hashed_password, email, display_name, role, is_active) VALUES
|
||||
-- ADMINISTRATORS
|
||||
(1, 'broadcastbeat', '$wp$2y$10$KelwIGTptpx3IlfaF7Anu.Ud8seDIsd726PV4Ts6CT9d.rWCpsba6', 'ryan.salazar@relevantmediaproperties.com', 'Broadcast Beat Magazine', 'administrator', true),
|
||||
(684, 'fiverrdeveloper', '$P$BnfPwlEqjA67bRABKPY9AHFNz3zDQN.', 'ryan.salazar@broadcastbeatstudios.com', 'developer', 'administrator', true),
|
||||
(728, 'deanna.jones', '$wp$2y$10$ycNw2SQELJKwhTpDbskVuO9tCmdacvoYg.9JSGtmczupiCFLFhUjK', 'deanna@broadcastbeatstudios.com', 'Broadcast Beat', 'administrator', true),
|
||||
(732, 'ruelwebinaradmin', '$P$Be2.Iyxq0QZSS.wNS5gzGzoq0eB3uz1', 'ruel@broadcastbeatstudios.com', 'ruelwebinaradmin', 'administrator', true),
|
||||
(776, 'chad.salazar', '$P$BDKUJTjLa6KwISuYfvLNg8VW0bZ5500', 'chad.salazar@broadcastbeatstudios.com', 'Chad Salazar', 'administrator', true),
|
||||
-- KEY STAFF (ryansalazar upgraded to administrator per instructions)
|
||||
(21, 'ryansalazar', '$P$BYVa0rEPUaKxZ.bZKWyhQdlGbhmNTg1', 'ryan@ryansalazar.net', 'Ryan Salazar', 'administrator', true),
|
||||
(50, 'pthompson', '$P$BhS7FLOqwYd9/0yKz7BNDmvy07SoYS0', 'deanna@broadcastbeat.com', 'Pamela Thompson', 'author', true),
|
||||
-- PR FIRM CONTRIBUTORS
|
||||
(6, 'desertmoon', '$wp$2y$10$vwAvPaqdJX/KHu.q/oIydeZGVtqKC7R3eRCf3/XGPcW6/bWIHe1qi', 'harriet@desertmoon.tv', 'Desert Moon Communications', 'contributor', true),
|
||||
(7, 'wallstcom', '$P$B.GJa2yl0pI2uxOSSJgKcV2T/4li5V.', 'sunnybranson@wallstcom.com', 'Wall Street Communications', 'contributor', true),
|
||||
(9, 'artisanspr', '$wp$2y$10$ZHzmzzO1lyuIFWwQf57jBOOa23wS29B6qq2xWIVrhvaXObarfwSZa', 'kgayhart@artisanspr.com', 'Keith Gayhart', 'contributor', true),
|
||||
(10, 'hummingbird', '$P$B61xZrfoctgjldBhkNvMWJ3C1H8Eol.', 'jeff@hummingbirdmedia.com', 'Hummingbird Media', 'contributor', true),
|
||||
(12, 'atomicpr', '$P$B4Y3L2aDEweGP7rEyk6wb0ajEvQsut/', 'jason@atomicpr.com', 'Atomic PR', 'contributor', true),
|
||||
(13, 'ingearpr', '$wp$2y$10$E1NonQFDeiXuWjGj8rPhC.fZ5.usgf57Q2guf3e8LkFmUVPEvvGgW', 'markops@ingearpr.com', 'InGear', 'contributor', true),
|
||||
(14, 'crossroads', '$P$BQg/o.gb.ycsdpsy2AgaOBBUnwAdri1', 'jharris@crossroads.com', 'Jennifer Harris', 'contributor', true),
|
||||
(15, 'ignite', '$wp$2y$10$kGW7PB3ga1dnNyDPO0NpMezSwyTWCerwI/Y0vW6Wq8WVxekoeyBpO', 'mimi@ignite.bz', 'Ignite Strategic Comms', 'contributor', true),
|
||||
(18, 'highrezpr', '$wp$2y$10$VBtrrNIZYwj80LCXU57tdOvqD/xM6Z/BVp6l.YADi0D/9cx8pi6em', 'veronique@highrezpr.com', 'High Rez PR', 'contributor', true),
|
||||
(19, 'redpinesgroup', '$P$BUSHOVcp6rPGe2EPijD/0doRClwmD0.', 'raemorrow@redpinesgroup.com', 'redpinesgroup', 'contributor', true),
|
||||
(20, 'toddsoundelux', '$P$BqGsvlrPM0MLAeLoJGuE3H4mAKdzc0.', 'CHellum@toddsoundelux.com', 'TODD-SOUNDELUX', 'contributor', true),
|
||||
(23, 'smpte', '$P$BMWB3H14zTZ1dZM2ciorIdnz6iqT7h.', 'aricca@smpte.org', 'smpte', 'contributor', true),
|
||||
(29, 'bourkepr', '$wp$2y$10$Dq9pQfWqyvkcZDxMjXB16.g0O337JE1if.iq1B/RO6SiMLroT34AG', 'kbourke@bourkepr.com', 'Kevin Bourke', 'contributor', true),
|
||||
(31, 'quantel', '$P$BB/jv2X305dCC1GBaTIOFullbUE0.41', 'kim.chaplin@s-a-m.com', 'Snell Advanced Media', 'contributor', true),
|
||||
(32, 'telestream', '$P$B4K1Nyqb/WvlNvNwxdiCN.yJbXt1xE1', 'janets@telestream.net', 'Telestream', 'contributor', true),
|
||||
(35, 'nab', '$P$BeB8/kbps7wUsZRRV5yCJnFZq8MKCa/', 'nab@broadcastbeat.com', 'NAB', 'contributor', true),
|
||||
(36, 'matrox', '$P$BPfT6ZHonQHEbTz90Bi73srj7sVR8r.', 'jmatey@matrox.com', 'matrox', 'contributor', true),
|
||||
(38, 'Quantum', '$P$B1DT1vWgF7R1rJL6hIYCfLj43j49ms/', 'quantum@broadcastbeat.com', 'Quantum', 'contributor', true),
|
||||
(40, 'ensembledesigns', '$P$BdXeAPVobF4PoMDBJTsPTWDlCt00Nn0', 'cindy@ensembledesigns.com', 'ensembledesigns', 'contributor', true),
|
||||
(41, 'cinegy', '$P$BWFW7U1wikUMe5EIr9Gr3fQGo2YDDC/', 'troy@cinegy.com', 'cinegy', 'contributor', true),
|
||||
(735, 'CWPR', '$wp$2y$10$Lx8eslb.PBltQdWwAI3d1.Hffv6LIt8Mj1C11dmOdQJATj7.xonxC', 'info@carriewick.com', 'Emma Buckley', 'contributor', true),
|
||||
(736, 'RISE Media Strategy','$wp$2y$10$l2PEnkFWi9Ycx2k78D96g.xukFgJg8PRXyp7H4hmsa.Tp/sjtmYwG', 'krissy@risemediastrategy.com', 'Krissy Rushing Tomlin', 'contributor', true),
|
||||
(738, 'EBComs', '$wp$2y$10$b8nSRpgP6JImRPtroRI6KezNxIdH.pkwjXXbYZZvl21znmch0oLgW', 'news@ebcoms.com', 'EBC', 'contributor', true),
|
||||
(749, 'Bitmovin', '$P$B.38uMQUYsqhvDQdP68hjvrc1nNKrL/', 'zoe.mumba@bitmovin.com', 'Zoe Mumba', 'contributor', true),
|
||||
(762, 'MASV', '$P$Bp7IacrIO6kePSk0ZTH1Gqe4f5Y43E1', 'anna@masv.io', 'Anna Mroczkowski', 'contributor', true),
|
||||
(763, 'Caster Comms', '$P$BZcmEVXD2.GVhcxoqRVLmc2rKqXDX0.', 'rachel@castercomm.com', 'Rachel Bradshaw', 'contributor', true),
|
||||
(765, 'VisualOn', '$wp$2y$10$T4MV.YCspYjX7PoqVynIo.A00w3044DrqrxY4FCElW1xC6lhgPnIm', 'savi@visualon.com', 'Savi Shi', 'contributor', true),
|
||||
(770, 'Radical Moves', '$wp$2y$10$Jq9.TDKaXrRyFwnXJED1d.W1ARQwiCUF2aE8UyMICILbrflDWvLuC', 'Helen@radicalmoves.co.uk', 'Radical Moves', 'contributor', true),
|
||||
(772, 'Humans Not Robots', '$wp$2y$10$VGyksF0RXSSyLYeyuvCxUug84vgdxJ6ifSdarPEBg/S/9OxfNPN4.', 'bea@humansnotrobots.com', 'Bea Alonso', 'contributor', true),
|
||||
(773, 'Atka', '$wp$2y$10$H9YE9ZCL28qGc6vsgyx3qOTsmM4lTVVH2h7wK7z4KSGC3r6vTNlrG', 'matt_smith@akta.tech', 'Matt Smith', 'contributor', true),
|
||||
(779, 'Projective', '$wp$2y$10$btb6I0.6NHcIitibnmnjXOxynqC/hEb8zbn.qWIM8IYQiisjmuKAq', 'bea.alonso@projective.io', 'Bea Alonso', 'contributor', true),
|
||||
(783, 'dimension', '$wp$2y$10$ZrnIyaWiVwb3hKx4yM8wm.d6qPNTAa5v.XRKB/PTdn.NkMSUhLtUG', 'alicia@dimensionpronline.com', 'dimension', 'contributor', true),
|
||||
(784, 'Julie Van Roy', '$P$B16qQBtKrVlG.or2Kwr2Rj40RnGc4j0', 'j.vanroy@intopix.com', 'Julie Van Roy', 'contributor', true),
|
||||
(777, 'Platform Comms', '$P$BREKNqKduOaVNa0PGTVd2pmDoZ38F/', 'darnell@platformcomms.com', 'Darnell Odeghe', 'contributor', true),
|
||||
(782, 'PRandMe', '$P$BobDp18rFSfMhkcOmj3pseSJd49ern0', 'denise@prandme.com', 'Denise Williams', 'contributor', true),
|
||||
-- TEST ACCOUNTS — imported but set inactive
|
||||
(757, 'haley_creed_test', '$P$BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'haleykcreed@gmail.com', 'haley creed test', 'contributor', false),
|
||||
(760, 'testaccount', '$P$BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'scheduling2@broadcastbeatstudios.com', 'testaccount', 'contributor', false),
|
||||
(761, 'haley_testing', '$P$BXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'hkbarron30@gmail.com', 'haley testing', 'contributor', false)
|
||||
ON CONFLICT (wp_id) DO UPDATE SET
|
||||
wp_username = EXCLUDED.wp_username,
|
||||
wp_hashed_password = EXCLUDED.wp_hashed_password,
|
||||
email = EXCLUDED.email,
|
||||
display_name = EXCLUDED.display_name,
|
||||
role = EXCLUDED.role,
|
||||
is_active = EXCLUDED.is_active;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user