-- ============================================================================ -- Bug Reporting System -- ============================================================================ -- Access rules: -- ALL authenticated users can INSERT (submit bug reports) -- Only ADMIN and SUPER_ADMIN can SELECT, UPDATE (view/manage) -- SALES_USER and DEVELOPER cannot view bug_reports after submission -- ============================================================================ CREATE TABLE IF NOT EXISTS bug_reports ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), reported_by UUID NOT NULL REFERENCES users(id), title VARCHAR(255) NOT NULL, description TEXT NOT NULL, severity VARCHAR(20) NOT NULL DEFAULT 'medium' CHECK (severity IN ('low', 'medium', 'high', 'critical')), page_url TEXT, screenshot_url TEXT, status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'in_progress', 'resolved', 'closed')), assigned_to UUID REFERENCES users(id), resolution_notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_bug_reports_reported_by ON bug_reports(reported_by); CREATE INDEX IF NOT EXISTS idx_bug_reports_status ON bug_reports(status); CREATE INDEX IF NOT EXISTS idx_bug_reports_severity ON bug_reports(severity); CREATE INDEX IF NOT EXISTS idx_bug_reports_assigned ON bug_reports(assigned_to); CREATE INDEX IF NOT EXISTS idx_bug_reports_created ON bug_reports(created_at DESC); -- RLS: Allow INSERT for all authenticated users -- Allow SELECT/UPDATE only for ADMIN and SUPER_ADMIN ALTER TABLE bug_reports ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS bug_reports_insert ON bug_reports; CREATE POLICY bug_reports_insert ON bug_reports FOR INSERT WITH CHECK ( current_user_id() IS NOT NULL AND reported_by = current_user_id() ); DROP POLICY IF EXISTS bug_reports_select ON bug_reports; CREATE POLICY bug_reports_select ON bug_reports FOR SELECT USING ( current_user_hierarchy_level() IS NOT NULL AND current_user_hierarchy_level() <= 2 ); DROP POLICY IF EXISTS bug_reports_update ON bug_reports; CREATE POLICY bug_reports_update ON bug_reports FOR UPDATE USING ( current_user_hierarchy_level() IS NOT NULL AND current_user_hierarchy_level() <= 2 ); -- Audit trigger for bug report actions CREATE OR REPLACE FUNCTION audit_bug_report_action() RETURNS TRIGGER AS $$ BEGIN IF TG_OP = 'INSERT' THEN INSERT INTO audit_logs (table_name, record_id, action, new_data, changed_by) VALUES ( 'bug_reports', NEW.id, 'BUG_CREATED', jsonb_build_object( 'title', NEW.title, 'severity', NEW.severity, 'page_url', NEW.page_url, 'status', NEW.status ), NEW.reported_by ); ELSIF TG_OP = 'UPDATE' THEN IF OLD.status IS DISTINCT FROM NEW.status THEN INSERT INTO audit_logs (table_name, record_id, action, new_data, changed_by) VALUES ( 'bug_reports', NEW.id, CASE NEW.status WHEN 'resolved' THEN 'BUG_RESOLVED' ELSE 'BUG_UPDATED' END, jsonb_build_object( 'old_status', OLD.status, 'new_status', NEW.status, 'assigned_to', NEW.assigned_to, 'resolution_notes', NEW.resolution_notes ), NULLIF(current_setting('app.current_user_id', true), '') ); END IF; IF OLD.assigned_to IS DISTINCT FROM NEW.assigned_to AND NEW.assigned_to IS NOT NULL THEN INSERT INTO audit_logs (table_name, record_id, action, new_data, changed_by) VALUES ( 'bug_reports', NEW.id, 'BUG_ASSIGNED', jsonb_build_object( 'assigned_to', NEW.assigned_to, 'previous_assignee', OLD.assigned_to, 'status', NEW.status ), NULLIF(current_setting('app.current_user_id', true), '') ); END IF; END IF; RETURN COALESCE(NEW, OLD); END; $$ LANGUAGE plpgsql SECURITY DEFINER; DROP TRIGGER IF EXISTS trg_audit_bug_report ON bug_reports; CREATE TRIGGER trg_audit_bug_report AFTER INSERT OR UPDATE ON bug_reports FOR EACH ROW EXECUTE FUNCTION audit_bug_report_action(); -- Widen audit action constraint to include bug report events ALTER TABLE audit_logs DROP CONSTRAINT IF EXISTS chk_audit_action; ALTER TABLE audit_logs ADD CONSTRAINT chk_audit_action CHECK (action::text = ANY (ARRAY[ 'CREATE', 'UPDATE', 'DELETE', 'BUG_CREATED', 'BUG_UPDATED', 'BUG_ASSIGNED', 'BUG_RESOLVED', 'LOGIN', 'LOGOUT' ]::text[])); -- Prevent SALES_USER and DEVELOPER from reading bug_reports directly -- via RLS bypass attempts (additional safety via trigger) CREATE OR REPLACE FUNCTION prevent_bug_report_read_bypass() RETURNS TRIGGER AS $$ DECLARE user_level INT; BEGIN user_level := current_user_hierarchy_level(); IF user_level IS NULL OR user_level > 2 THEN RAISE EXCEPTION 'ACCESS DENIED: Only ADMIN and SUPER_ADMIN can view bug reports.'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER;