-- 007_talent_rbac.sql -- Manual one-shot: the `talent` permission module (8 tags), a `talent_sourcing` -- bundle holding them, and the bundle attached to the staff roles that source -- candidates. Mirrors 004's idempotent pattern; applied automatically at startup -- by alembic_setup.run_manual_sql() and recorded in manual_migrations. -- -- The all_access bundle is a fixed id list seeded before this module existed, -- so system_administrator gets talent access through THIS bundle, not that one. -- Users must log in again after this applies — permissions are resolved from -- the DB per request, but the frontend caches the list from /users/me. -- ============================================================================= -- 1. The 8 talent.* permission tags -- ============================================================================= INSERT INTO app.permission_tags (tag_name, module, action, description, created_at, updated_at, is_active, is_deleted) VALUES ('talent.view', 'talent', 'view', NULL, NOW(), NOW(), true, false), ('talent.create', 'talent', 'create', NULL, NOW(), NOW(), true, false), ('talent.edit', 'talent', 'edit', NULL, NOW(), NOW(), true, false), ('talent.delete', 'talent', 'delete', NULL, NOW(), NOW(), true, false), ('talent.approve', 'talent', 'approve', NULL, NOW(), NOW(), true, false), ('talent.export', 'talent', 'export', NULL, NOW(), NOW(), true, false), ('talent.manage', 'talent', 'manage', NULL, NOW(), NOW(), true, false), ('talent.configure', 'talent', 'configure', NULL, NOW(), NOW(), true, false) ON CONFLICT (tag_name) DO NOTHING; -- ============================================================================= -- 2. Bundle holding all eight talent tags -- ============================================================================= INSERT INTO app.permissions (name, description, permission_tags, is_system, created_at, updated_at, is_active, is_deleted) SELECT 'talent_sourcing', 'LinkedIn talent sourcing: run Apify searches and view sourced profiles', ( SELECT COALESCE(jsonb_agg(id ORDER BY id), '[]'::jsonb) FROM app.permission_tags WHERE is_deleted = false AND module = 'talent' ), true, NOW(), NOW(), true, false WHERE NOT EXISTS ( SELECT 1 FROM app.permissions WHERE name = 'talent_sourcing' ); -- ============================================================================= -- 3. Attach the bundle to the staff roles (idempotent; same role list as 004) -- ============================================================================= UPDATE app.roles r SET permissions = COALESCE(r.permissions, '[]'::jsonb) || jsonb_build_array(p.id), updated_at = NOW() FROM app.permissions p WHERE p.name = 'talent_sourcing' AND r.role_name IN ( 'system_administrator', 'hr_administrator', 'recruiter', 'hiring_manager', 'department_head', 'ceo' ) AND NOT (COALESCE(r.permissions, '[]'::jsonb) @> jsonb_build_array(p.id));