mirror of
https://git.coastit.co.za/caitlin/CRM_ENVR.git
synced 2026-07-10 03:05:43 +02:00
146 lines
5.4 KiB
PL/PgSQL
146 lines
5.4 KiB
PL/PgSQL
-- ============================================================================
|
|
-- 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;
|