Implements 6-tier role hierarchy: - ADMIN (100): Full system access - OFFICE_MANAGER (50): Admin panel without user management - MANAGER (40): Full company control + user management - EMPLOYEE (30): Edit company data (with delegated permissions) - MEMBER (20): Full content access (forum, contacts, chat) - UNAFFILIATED (10): Public profiles only Features: - SystemRole and CompanyRole enums in database.py - UserCompanyPermissions model for delegation - New decorators: @role_required(), @company_permission() - Auto-detection of MANAGER role from KRS data - Backward compatible with is_admin flag Migration: 035_add_role_system.sql Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
184 lines
7.1 KiB
SQL
184 lines
7.1 KiB
SQL
-- Migration: 035_add_role_system.sql
|
|
-- Description: Add hierarchical role system for granular access control
|
|
-- Author: Claude Code
|
|
-- Date: 2026-02-01
|
|
--
|
|
-- Role hierarchy:
|
|
-- UNAFFILIATED (10) - Firma spoza Izby - tylko publiczne profile
|
|
-- MEMBER (20) - Członek Norda bez firmy - pełny dostęp do treści
|
|
-- EMPLOYEE (30) - Pracownik firmy członkowskiej - edycja danych firmy
|
|
-- MANAGER (40) - Kadra zarządzająca - pełna kontrola firmy + użytkownicy
|
|
-- OFFICE_MANAGER (50) - Kierownik biura Norda - panel admina
|
|
-- ADMIN (100) - Administrator portalu - pełne prawa
|
|
--
|
|
-- Company roles:
|
|
-- NONE (0) - Brak powiązania z firmą
|
|
-- VIEWER (10) - Może przeglądać dashboard firmy
|
|
-- EMPLOYEE (20) - Może edytować dane firmy
|
|
-- MANAGER (30) - Pełna kontrola + zarządzanie użytkownikami
|
|
|
|
-- ============================================================
|
|
-- STEP 1: Add new columns
|
|
-- ============================================================
|
|
|
|
-- Add role column with default UNAFFILIATED
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS role VARCHAR(20) DEFAULT 'UNAFFILIATED' NOT NULL;
|
|
|
|
-- Add company_role column with default NONE
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS company_role VARCHAR(20) DEFAULT 'NONE' NOT NULL;
|
|
|
|
-- Add index for role lookups
|
|
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
|
|
|
|
-- ============================================================
|
|
-- STEP 2: Migrate existing admins
|
|
-- ============================================================
|
|
|
|
UPDATE users
|
|
SET role = 'ADMIN'
|
|
WHERE is_admin = TRUE AND (role IS NULL OR role = 'UNAFFILIATED');
|
|
|
|
-- ============================================================
|
|
-- STEP 3: Auto-detect MANAGER role from KRS data
|
|
-- Users linked to company management (zarząd) get MANAGER role
|
|
-- ============================================================
|
|
|
|
UPDATE users u
|
|
SET
|
|
role = 'MANAGER',
|
|
company_role = 'MANAGER'
|
|
WHERE
|
|
u.company_id IS NOT NULL
|
|
AND u.role NOT IN ('ADMIN', 'OFFICE_MANAGER')
|
|
AND EXISTS (
|
|
SELECT 1 FROM company_people cp
|
|
WHERE cp.person_id = u.person_id
|
|
AND cp.company_id = u.company_id
|
|
AND cp.role_category = 'zarzad'
|
|
);
|
|
|
|
-- ============================================================
|
|
-- STEP 4: Set EMPLOYEE role for users with company but no KRS link
|
|
-- ============================================================
|
|
|
|
UPDATE users
|
|
SET
|
|
role = 'EMPLOYEE',
|
|
company_role = 'EMPLOYEE'
|
|
WHERE
|
|
company_id IS NOT NULL
|
|
AND role NOT IN ('ADMIN', 'OFFICE_MANAGER', 'MANAGER');
|
|
|
|
-- ============================================================
|
|
-- STEP 5: Set MEMBER role for Norda members without company
|
|
-- ============================================================
|
|
|
|
UPDATE users
|
|
SET role = 'MEMBER'
|
|
WHERE
|
|
is_norda_member = TRUE
|
|
AND company_id IS NULL
|
|
AND role = 'UNAFFILIATED';
|
|
|
|
-- ============================================================
|
|
-- STEP 6: Create user_company_permissions table for delegation
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS user_company_permissions (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
|
|
-- Content editing permissions
|
|
can_edit_description BOOLEAN DEFAULT TRUE, -- Opis firmy, historia, wartości
|
|
can_edit_services BOOLEAN DEFAULT TRUE, -- Usługi, kompetencje, technologie
|
|
can_edit_contacts BOOLEAN DEFAULT FALSE, -- Email, telefon, adres
|
|
can_edit_social BOOLEAN DEFAULT FALSE, -- Social media, strona www
|
|
|
|
-- Feature permissions
|
|
can_manage_classifieds BOOLEAN DEFAULT TRUE, -- B2B ogłoszenia w imieniu firmy
|
|
can_post_forum BOOLEAN DEFAULT TRUE, -- Posty na forum w imieniu firmy
|
|
can_view_analytics BOOLEAN DEFAULT FALSE, -- Statystyki firmy, wyświetlenia
|
|
|
|
-- Audit trail
|
|
granted_by_id INTEGER REFERENCES users(id),
|
|
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
-- Unique constraint: one permission record per user-company pair
|
|
CONSTRAINT uq_user_company_permissions UNIQUE (user_id, company_id)
|
|
);
|
|
|
|
-- Indexes for lookups
|
|
CREATE INDEX IF NOT EXISTS idx_user_company_permissions_user ON user_company_permissions(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_company_permissions_company ON user_company_permissions(company_id);
|
|
|
|
-- Grant permissions to application user
|
|
GRANT ALL ON TABLE user_company_permissions TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON SEQUENCE user_company_permissions_id_seq TO nordabiz_app;
|
|
|
|
-- ============================================================
|
|
-- STEP 7: Create default permissions for existing EMPLOYEE users
|
|
-- ============================================================
|
|
|
|
INSERT INTO user_company_permissions (user_id, company_id, can_edit_description, can_edit_services)
|
|
SELECT id, company_id, TRUE, TRUE
|
|
FROM users
|
|
WHERE role = 'EMPLOYEE' AND company_id IS NOT NULL
|
|
ON CONFLICT (user_id, company_id) DO NOTHING;
|
|
|
|
-- ============================================================
|
|
-- STEP 8: Verify migration results
|
|
-- ============================================================
|
|
|
|
-- Log migration statistics (view in PostgreSQL logs)
|
|
DO $$
|
|
DECLARE
|
|
admin_count INTEGER;
|
|
office_mgr_count INTEGER;
|
|
manager_count INTEGER;
|
|
employee_count INTEGER;
|
|
member_count INTEGER;
|
|
unaffiliated_count INTEGER;
|
|
permissions_count INTEGER;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO admin_count FROM users WHERE role = 'ADMIN';
|
|
SELECT COUNT(*) INTO office_mgr_count FROM users WHERE role = 'OFFICE_MANAGER';
|
|
SELECT COUNT(*) INTO manager_count FROM users WHERE role = 'MANAGER';
|
|
SELECT COUNT(*) INTO employee_count FROM users WHERE role = 'EMPLOYEE';
|
|
SELECT COUNT(*) INTO member_count FROM users WHERE role = 'MEMBER';
|
|
SELECT COUNT(*) INTO unaffiliated_count FROM users WHERE role = 'UNAFFILIATED';
|
|
SELECT COUNT(*) INTO permissions_count FROM user_company_permissions;
|
|
|
|
RAISE NOTICE 'Role migration complete:';
|
|
RAISE NOTICE ' ADMIN: %', admin_count;
|
|
RAISE NOTICE ' OFFICE_MANAGER: %', office_mgr_count;
|
|
RAISE NOTICE ' MANAGER: %', manager_count;
|
|
RAISE NOTICE ' EMPLOYEE: %', employee_count;
|
|
RAISE NOTICE ' MEMBER: %', member_count;
|
|
RAISE NOTICE ' UNAFFILIATED: %', unaffiliated_count;
|
|
RAISE NOTICE ' Delegated permissions records: %', permissions_count;
|
|
END $$;
|
|
|
|
-- ============================================================
|
|
-- VERIFICATION QUERIES (run manually to verify)
|
|
-- ============================================================
|
|
|
|
-- Check for any users with NULL role (should be 0)
|
|
-- SELECT COUNT(*) FROM users WHERE role IS NULL;
|
|
|
|
-- View role distribution
|
|
-- SELECT role, COUNT(*) as count FROM users GROUP BY role ORDER BY count DESC;
|
|
|
|
-- View users with company but no company_role
|
|
-- SELECT id, email, company_id, role, company_role
|
|
-- FROM users
|
|
-- WHERE company_id IS NOT NULL AND company_role = 'NONE';
|
|
|
|
-- Check KRS-linked managers
|
|
-- SELECT u.id, u.email, u.role, u.company_role, c.name as company_name
|
|
-- FROM users u
|
|
-- JOIN companies c ON u.company_id = c.id
|
|
-- WHERE u.role = 'MANAGER'
|
|
-- LIMIT 10;
|