Files
avbeat-com/supabase/migrations/20260322210000_wp_user_migration.sql
2026-05-07 16:39:17 +00:00

270 lines
16 KiB
PL/PgSQL

-- ============================================================
-- 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 $$;