Current state
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
-- ============================================================================
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user