270 lines
16 KiB
PL/PgSQL
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 $$;
|