Database
1 min
-- CommandIT Database Schema (Finalized)
-- Dialect: PostgreSQL
-- Notes: Simplified comments for brevity. Assumes necessary extensions and trigger functions exist.
-- =============================================
-- Core Platform & Organization
-- =============================================
CREATE TABLE OrganizationTypes (
org_type_id SERIAL PRIMARY KEY,
type_name VARCHAR(50) NOT NULL UNIQUE, -- e.g., 'Client', 'Vendor', 'ServiceProvider'
description TEXT
);
CREATE TABLE Contacts (
contact_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
site_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
email_primary VARCHAR(255) UNIQUE,
is_email_verified BOOLEAN DEFAULT false,
phone_office VARCHAR(50),
phone_mobile VARCHAR(50),
is_mobile_verified BOOLEAN DEFAULT false,
job_title VARCHAR(100),
is_primary BOOLEAN DEFAULT false,
is_vip BOOLEAN DEFAULT false,
is_authorized_caller BOOLEAN DEFAULT true,
reports_to_contact_id UUID NULL REFERENCES Contacts(contact_id) ON DELETE SET NULL,
security_pin_hash VARCHAR(255),
security_question TEXT,
security_answer_hash VARCHAR(255),
sms_consent_given BOOLEAN NOT NULL DEFAULT false,
allow_email_notifications BOOLEAN NOT NULL DEFAULT true,
allow_sms_notifications BOOLEAN NOT NULL DEFAULT false,
external_contact_id VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT contact_cannot_report_to_self CHECK (contact_id != reports_to_contact_id)
);
CREATE TABLE VerificationCodes (
code_hash VARCHAR(64) PRIMARY KEY, -- Hash of the code sent
contact_id UUID NULL REFERENCES Contacts(contact_id) ON DELETE CASCADE, -- Link if verifying a contact
user_id UUID NULL REFERENCES Users(user_id) ON DELETE CASCADE, -- Link if verifying a user (e.g., password reset)
delivery_method VARCHAR(10) NOT NULL CHECK (delivery_method IN ('SMS', 'Email', 'AppPush')), -- How code was sent
purpose VARCHAR(50) DEFAULT 'CallerVerification', -- What is this code for? e.g., 'CallerVerification', 'PasswordReset', 'MFASetup'
expires_at TIMESTAMPTZ NOT NULL, -- Expiry time for the code
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
used_at TIMESTAMPTZ NULL, -- Timestamp when the code was successfully used
CHECK (contact_id IS NOT NULL OR user_id IS NOT NULL) -- Must be linked to a person/account
);
CREATE TABLE Organizations (
org_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
parent_msp_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE SET NULL, -- Added: Link to managing MSP Org if this is a Client Org
name VARCHAR(255) NOT NULL UNIQUE,
logo_url TEXT,
data_residency_region VARCHAR(50),
supported_country_codes TEXT[] NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
default_currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD',
default_business_hours_id UUID REFERENCES BusinessHours(business_hours_id) ON DELETE SET NULL,
default_site_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
default_primary_contact_id UUID REFERENCES Contacts(contact_id) ON DELETE SET NULL,
default_timezone VARCHAR(100) NULL,
pricing_rule_set_id UUID NULL REFERENCES PricingRuleSets(rule_set_id) ON DELETE SET NULL,
account_manager_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
primary_tech_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
alert_processing_policy_id UUID NULL REFERENCES AlertProcessingPolicies(policy_id) ON DELETE SET NULL;
custom_fields JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON COLUMN Organizations.parent_msp_org_id IS 'If this organization is a client managed by an MSP, this links to the MSP''s org_id.';
COMMENT ON COLUMN Organizations.pricing_rule_set_id IS 'Optional: Link to a specific PricingRuleSet that overrides the MSP default for this client organization.';
COMMENT ON COLUMN Organizations.default_currency_code IS 'Default ISO 4217 currency code for financial transactions related to this organization (e.g., CAD, USD).';
COMMENT ON COLUMN Organizations.default_timezone IS 'Default IANA timezone name (e.g., ''America/Vancouver'') for the organization.';
COMMENT ON COLUMN Organizations.supported_country_codes IS 'For Vendor org types, lists ISO 3166-1 alpha-2 country codes where they operate/distribute.';
COMMENT ON COLUMN Organizations.alert_processing_policy_id IS 'Optional: Link to a specific AlertProcessingPolicy that overrides inherited policies for alerts concerning this organization.';
CREATE TABLE OrganizationTypeAssignments (
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
org_type_id INTEGER NOT NULL REFERENCES OrganizationTypes(org_type_id) ON DELETE RESTRICT,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (org_id, org_type_id)
);
CREATE TABLE Locations (
location_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
location_type VARCHAR(20) NOT NULL CHECK (location_type IN ('Office', 'ClientSite', 'DataCenter', 'HomeBase', 'Warehouse', 'Virtual', 'Remote', 'Other')),
is_system_defined BOOLEAN NOT NULL DEFAULT false,
parent_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
street_address VARCHAR(255),
city VARCHAR(100),
province_state VARCHAR(100),
postal_code VARCHAR(20),
country_code VARCHAR(2),
latitude NUMERIC(9, 6),
longitude NUMERIC(9, 6),
time_zone VARCHAR(100),
business_hours_id UUID NULL REFERENCES BusinessHours(business_hours_id) ON DELETE SET NULL,
zone_id UUID REFERENCES ServiceZones(zone_id) ON DELETE SET NULL,
pricing_rule_set_id UUID NULL REFERENCES PricingRuleSets(rule_set_id) ON DELETE SET NULL, -- Pricing rules specific to this Location
is_primary_office BOOLEAN NOT NULL DEFAULT false,
override_rate_sheet_id UUID REFERENCES RateSheets(rate_sheet_id) ON DELETE SET NULL,
parking_instructions TEXT,
entry_instructions TEXT,
external_site_id VARCHAR(50),
alert_processing_policy_id UUID NULL REFERENCES AlertProcessingPolicies(policy_id) ON DELETE SET NULL;
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT locations_no_self_parent CHECK (location_id != parent_location_id),
CONSTRAINT locations_org_name_unique UNIQUE(org_id, name)
);
COMMENT ON COLUMN Locations.pricing_rule_set_id IS 'Optional: Link to a specific PricingRuleSet that overrides Org/MSP defaults for this location.';
COMMENT ON COLUMN Locations.time_zone IS 'IANA timezone name (e.g., ''America/Vancouver''). Used for local time calculations if Business Hours type is LocationLocalTime.';
COMMENT ON COLUMN Locations.business_hours_id IS 'Optional: Business hours specific to this location. If NULL, inherits default business hours from the parent Organization.';
COMMENT ON COLUMN Locations.alert_processing_policy_id IS 'Optional: Link to a specific AlertProcessingPolicy that overrides inherited policies for alerts concerning this location.';
CREATE TABLE Users (
user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
contact_id UUID NULL UNIQUE REFERENCES Contacts(contact_id) ON DELETE SET NULL,
email VARCHAR(255) NOT NULL UNIQUE,
hashed_password VARCHAR(255),
first_name VARCHAR(100),
last_name VARCHAR(100),
employment_type VARCHAR(20) DEFAULT 'FullTime' CHECK (employment_type IN ('FullTime', 'PartTime', 'Contractor', 'VendorUser')),
avatar_url TEXT,
home_base_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
assigned_office_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
preferred_language VARCHAR(10) NOT NULL DEFAULT 'en',
is_active BOOLEAN NOT NULL DEFAULT true,
is_portal_access_allowed BOOLEAN NOT NULL DEFAULT true,
is_anonymized BOOLEAN NOT NULL DEFAULT false,
is_ai BOOLEAN NOT NULL DEFAULT false,
sp_ai_override_id UUID REFERENCES SpAiAgentOverrides(override_id) ON DELETE SET NULL,
default_work_type_id UUID REFERENCES WorkTypes(work_type_id) ON DELETE SET NULL,
job_title VARCHAR(255),
phone_number VARCHAR(50),
is_mobile_verified_for_login BOOLEAN DEFAULT false,
default_start_address_type VARCHAR(10) DEFAULT 'Office' CHECK (default_start_address_type IN ('Office', 'HomeBase', 'LastKnown')),
current_status_enum VARCHAR(20) CHECK (current_status_enum IN ('Available', 'TravelingToSite', 'OnSite', 'Lunch', 'Meeting', 'OffDuty', 'Vacation', 'Away', 'Idle')), -- Manual or inferred status
current_latitude NUMERIC(9, 6),
current_longitude NUMERIC(9, 6),
last_location_update_timestamp TIMESTAMPTZ,
current_schedule_entry_id BIGINT REFERENCES TechnicianSchedules(schedule_entry_id) ON DELETE SET NULL,
current_eta_timestamp TIMESTAMPTZ,
last_status_change_timestamp TIMESTAMPTZ,
last_system_activity_timestamp TIMESTAMPTZ NULL, -- Last interaction with any CommandIT interface
current_session_start_timestamp TIMESTAMPTZ NULL, -- When the current login session began
current_session_source VARCHAR(20) NULL CHECK (current_session_source IN ('Web', 'Agent', 'Mobile', 'API')), -- How user is connected
location_tracking_enabled BOOLEAN DEFAULT true,
mobile_device_push_token TEXT,
external_auth_provider VARCHAR(50),
external_auth_user_id VARCHAR(255),
latest_nps_score INTEGER CHECK (latest_nps_score >= 0 AND latest_nps_score <= 10),
latest_nps_submitted_at TIMESTAMPTZ,
last_login TIMESTAMPTZ, -- Still useful for last successful login overall
custom_fields JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
CHECK ((is_ai = false AND sp_ai_override_id IS NULL) OR (is_ai = true)),
CONSTRAINT users_external_auth_unique UNIQUE (external_auth_provider, external_auth_user_id)
);
COMMENT ON COLUMN Users.last_system_activity_timestamp IS 'Timestamp of the last detected user interaction with any CommandIT system component (Web, Agent, Mobile, API). Used to infer idle status.';
COMMENT ON COLUMN Users.current_session_start_timestamp IS 'Timestamp when the user''s current login session started. NULL if logged out.';
COMMENT ON COLUMN Users.current_session_source IS 'Indicates the primary interface used to establish the current login session.';
CREATE TABLE UserSessions (
user_session_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Unique ID for the session
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
session_start_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the session was initiated
session_end_time TIMESTAMPTZ NULL, -- When the session ended (NULL if currently active)
source_type VARCHAR(20) NOT NULL CHECK (source_type IN ('Web', 'Agent', 'Mobile', 'API', 'Unknown')), -- How the session was initiated
source_ip_address INET NULL, -- IP address at the start of the session
source_geo_location JSONB NULL, -- GeoIP lookup for the starting IP
user_agent TEXT NULL, -- User agent string from the client
last_activity_timestamp TIMESTAMPTZ NULL, -- Timestamp of the last known activity within THIS session
logout_reason VARCHAR(30) NULL CHECK (logout_reason IN ('UserLogout', 'Timeout', 'ForcedAdmin', 'SystemShutdown')), -- Why the session ended
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
-- Add index on user_id, session_start_time
-- Add index on user_id where session_end_time IS NULL (for finding active sessions)
);
COMMENT ON TABLE UserSessions IS 'Tracks user login sessions, their duration, source, and last activity within the session.';
COMMENT ON COLUMN UserSessions.session_end_time IS 'Timestamp when the session ended. NULL indicates the session is currently active.';
COMMENT ON COLUMN UserSessions.last_activity_timestamp IS 'Timestamp of the last detected user interaction associated with this specific session.';
COMMENT ON COLUMN UserSessions.logout_reason IS 'Reason why the session ended (user action, inactivity timeout, admin action, etc.).';
CREATE TABLE Roles (
role_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE, -- NULL for system roles
name VARCHAR(100) NOT NULL,
description TEXT,
is_system BOOLEAN NOT NULL DEFAULT false, -- Non-editable system role
is_portal_role BOOLEAN NOT NULL DEFAULT false, -- Is this role intended for portal users?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT roles_org_name_unique UNIQUE NULLS NOT DISTINCT (org_id, name)
);
CREATE TABLE UserRoleAssignments (
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES Roles(role_id) ON DELETE CASCADE,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, role_id)
);
CREATE TABLE RolePermissions (
permission_id BIGSERIAL PRIMARY KEY,
role_id UUID NOT NULL REFERENCES Roles(role_id) ON DELETE CASCADE,
permission_key VARCHAR(255) NOT NULL,
portal_link_url VARCHAR(512),
portal_link_title VARCHAR(100),
UNIQUE(role_id, permission_key)
);
CREATE TABLE Teams (
team_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE(org_id, name)
);
CREATE TABLE UserTeamAssignments (
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
team_id UUID NOT NULL REFERENCES Teams(team_id) ON DELETE CASCADE,
is_primary_team BOOLEAN DEFAULT false,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, team_id)
);
CREATE TABLE Tags (
tag_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
tag_key VARCHAR(100) NOT NULL,
tag_value VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT tags_org_key_value_unique UNIQUE(org_id, tag_key, tag_value)
);
CREATE TABLE UserTags (
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
PRIMARY KEY (user_id, tag_id)
);
CREATE TABLE UserEmails (
user_email_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
email_address VARCHAR(255) NOT NULL,
source VARCHAR(50) NOT NULL,
is_monitored BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT useremails_user_email_unique UNIQUE(user_id, email_address)
);
-- Maps internet domains to Organizations
CREATE TABLE OrganizationDomains (
org_domain_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
domain_name VARCHAR(255) NOT NULL, -- e.g., 'example.com'
is_verified BOOLEAN NOT NULL DEFAULT false,
verification_method VARCHAR(20) NULL,
verified_at TIMESTAMPTZ NULL,
notes TEXT NULL,
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
added_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL
);
CREATE INDEX idx_orgdomains_domain_name ON OrganizationDomains(domain_name);
CREATE INDEX idx_orgdomains_org_id ON OrganizationDomains(org_id);
ALTER TABLE OrganizationDomains ADD CONSTRAINT orgdomains_org_domain_unique UNIQUE (org_id, domain_name);
COMMENT ON TABLE OrganizationDomains IS 'Maps internet domain names to specific Organizations within CommandIT.';
-- =============================================
-- Configuration Management (CMDB)
-- =============================================
CREATE TABLE Manufacturers (
manufacturer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
-- Main table for devices managed by CommandIT
CREATE TABLE Devices (
device_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
smbios_uuid UUID NULL,
device_type VARCHAR(100) NOT NULL, -- e.g., 'Server', 'Workstation', 'Laptop', 'CloudServer', 'NetworkDevice', 'Mobile', 'PBX', 'VoIPSystem', 'UPS', 'PDU', 'HVAC', 'EnvironmentalSensor'
is_virtual BOOLEAN NOT NULL DEFAULT false,
status VARCHAR(50) NOT NULL DEFAULT 'Active' CHECK (status IN ('Active', 'Inactive', 'InRepair', 'Disposed', 'Archived', 'Lost', 'Stolen')),
location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
primary_contact_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
manufacturer_id UUID REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL,
model_number VARCHAR(255),
mfg_part_number VARCHAR(255),
serial_number VARCHAR(255),
asset_tag VARCHAR(100), -- Note: Server-side set, but field exists for manual entry/import
imei VARCHAR(20),
meid VARCHAR(18),
iccid VARCHAR(22),
cpu_architecture VARCHAR(50),
agent_version VARCHAR(50) NULL,
agent_service_version VARCHAR(50) NULL,
agent_ui_version VARCHAR(50) NULL,
agent_probe_version VARCHAR(50) NULL,
agent_updater_version VARCHAR(50) NULL,
agent_last_full_sync TIMESTAMPTZ NULL,
agent_health_status VARCHAR(20) NULL,
agent_last_health_check TIMESTAMPTZ NULL,
agent_health_details TEXT NULL,
is_probe BOOLEAN DEFAULT false,
last_agent_checkin TIMESTAMPTZ NULL,
inventory_detail_level VARCHAR(20) NOT NULL DEFAULT 'Normal',
enhanced_mode_expires_at TIMESTAMPTZ,
install_date TIMESTAMPTZ, -- OS Install Date
installed_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
vendor_org_id UUID REFERENCES Organizations(org_id) ON DELETE SET NULL,
purchase_date DATE,
warranty_expiry_date DATE,
warranty_provider VARCHAR(255),
warranty_status VARCHAR(100),
last_warranty_check_timestamp TIMESTAMPTZ,
warranty_details_url TEXT,
custom_sla_id UUID REFERENCES SLAs(sla_id) ON DELETE SET NULL,
manual_replacement_date DATE,
ip_address INET,
external_ip_address INET,
default_gateway INET,
os_type VARCHAR(50),
os_version VARCHAR(100),
os_name VARCHAR(255),
is_reboot_pending BOOLEAN NULL, -- Added: Flag indicating if the OS reports a reboot is pending
rack_id UUID NULL REFERENCES Racks(rack_id) ON DELETE SET NULL,
rack_position_u INTEGER NULL, -- Starting U position (bottom U of the device)
rack_units_consumed INTEGER NULL DEFAULT 1 CHECK (rack_units_consumed IS NULL OR rack_units_consumed >= 1), -- How many U's the device occupies
btu_output_standard INTEGER NULL, -- Standard/Typical Thermal Output in BTU/hour
btu_output_peak INTEGER NULL, -- Peak Thermal Output in BTU/hour
power_consumption_watts_standard INTEGER NULL, -- Standard/Typical Power Consumption in Watts
power_consumption_watts_peak INTEGER NULL, -- Peak Power Consumption in Watts
power_draw_amps_peak NUMERIC(6, 2) NULL, -- Peak Amperage Draw
last_logged_in_user VARCHAR(255),
last_reported_latitude NUMERIC(9, 6) NULL,
last_reported_longitude NUMERIC(9, 6) NULL,
last_location_accuracy_meters INTEGER NULL, -- Added: Accuracy radius in meters
last_location_source VARCHAR(30) NULL CHECK (last_location_source IN ('OS_GPS', 'OS_WiFi', 'OS_Network', 'IP_Geolocation', 'Unknown')), -- Added: Source method
last_location_report_timestamp TIMESTAMPTZ NULL,
windows11_compatibility_status VARCHAR(20) CHECK (windows11_compatibility_status IN ('Compatible', 'NotCompatible', 'Unknown', 'CheckFailed')), -- Added
windows11_incompatibility_reasons TEXT[] NULL, -- Added
windows11_last_check_time TIMESTAMPTZ NULL, -- Added
configuration JSONB, -- Store TPM, UEFI status, Cloud VM details, PBX details, etc. here
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
-- Constraints
CONSTRAINT devices_org_serial_unique UNIQUE NULLS NOT DISTINCT (org_id, serial_number),
CONSTRAINT devices_org_asset_tag_unique UNIQUE NULLS NOT DISTINCT (org_id, asset_tag)
);
COMMENT ON COLUMN Devices.install_date IS 'Operating System installation date, used for aging reports. Ensure agent populates this reliably.';
COMMENT ON COLUMN Devices.is_reboot_pending IS 'Flag indicating if the OS reports a reboot is pending (e.g., after updates). Collected by agent.';
COMMENT ON COLUMN Devices.last_reported_latitude IS 'Last reported latitude from OS location services.';
COMMENT ON COLUMN Devices.last_reported_longitude IS 'Last reported longitude from OS location services.';
COMMENT ON COLUMN Devices.last_location_accuracy_meters IS 'Estimated accuracy radius in meters of the last location report.';
COMMENT ON COLUMN Devices.last_location_source IS 'Method used to obtain the last reported location (OS API, IP Geolocation).';
COMMENT ON COLUMN Devices.last_location_report_timestamp IS 'Timestamp when the last location was successfully reported by the agent or derived server-side.';
COMMENT ON COLUMN Devices.windows11_compatibility_status IS 'Assessed compatibility status with Windows 11 (calculated server-side).';
COMMENT ON COLUMN Devices.windows11_incompatibility_reasons IS 'Array listing reasons for Windows 11 incompatibility (calculated server-side).';
COMMENT ON COLUMN Devices.windows11_last_check_time IS 'Timestamp when Windows 11 compatibility was last assessed by the server.';
COMMENT ON COLUMN Devices.configuration IS 'JSONB field storing device-specific config (TPM/UEFI status, Cloud VM Details, PBX Details, etc.) and potentially less critical OS state flags.';
COMMENT ON COLUMN Devices.rack_id IS 'FK linking the device to the specific rack it is installed in.';
COMMENT ON COLUMN Devices.rack_position_u IS 'The bottom-most U position occupied by this device within the rack.';
COMMENT ON COLUMN Devices.rack_units_consumed IS 'The number of U units this device occupies in the rack (defaults to 1 if not specified).';
COMMENT ON COLUMN Devices.btu_output_standard IS 'Rated or typical thermal output specification in BTU/hour.';
COMMENT ON COLUMN Devices.btu_output_peak IS 'Maximum rated thermal output specification in BTU/hour.';
COMMENT ON COLUMN Devices.power_consumption_watts_standard IS 'Rated or typical operational power consumption specification in Watts.';
COMMENT ON COLUMN Devices.power_consumption_watts_peak IS 'Maximum rated power consumption specification in Watts.';
COMMENT ON COLUMN Devices.power_draw_amps_peak IS 'Maximum rated amperage draw specification.';
COMMENT ON COLUMN Devices.updated_at IS 'Timestamp of the last update to this record, typically updated automatically by agent check-ins or manual modifications.';
CREATE INDEX idx_devices_org_id ON Devices(org_id);
CREATE INDEX idx_devices_location_id ON Devices(location_id);
CREATE INDEX idx_devices_os_name ON Devices(os_name);
CREATE INDEX idx_devices_device_type ON Devices(device_type);
CREATE INDEX idx_devices_last_checkin ON Devices(last_agent_checkin);
CREATE TABLE DeviceTags (
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
PRIMARY KEY (device_id, tag_id)
);
CREATE TABLE DeviceHardware (
device_id UUID PRIMARY KEY REFERENCES Devices(device_id) ON DELETE CASCADE,
cpu_details JSONB NULL, -- Incl. Model, Speed, Cores, Logical, Arch, Virt flags etc.
ram_total_mb INTEGER CHECK (ram_total_mb >= 0),
ram_slots_used INTEGER NULL,
ram_slots_total INTEGER NULL,
ram_details JSONB NULL, -- Array: Slot, Capacity, Type, Speed, Mfg, Part#, Serial#
bios_info JSONB NULL, -- Incl. Manufacturer, Version, Release Date, Secure Boot Status etc.
motherboard_info JSONB NULL, -- Incl. Manufacturer, Product, Serial#
chassis_info JSONB NULL, -- Incl. Manufacturer, Type, Serial#
video_controllers JSONB NULL, -- Array: Name, Mfg, RAM, Driver Version, WDDM/DirectX Level
sound_devices JSONB NULL, -- Array: Name, Mfg
usb_controllers JSONB NULL, -- Added: Array storing details about detected USB controllers.
tpm_details JSONB NULL, -- Added: Structured TPM details (Presence, Enabled, Version, Manufacturer ID/Version)
last_updated_at TIMESTAMPTZ -- Timestamp when this hardware inventory data was last collected/updated
);
COMMENT ON TABLE DeviceHardware IS 'Stores detailed hardware component information (CPU, RAM, BIOS, Motherboard, Chassis, Graphics, Sound, USB Controllers, TPM) for a device.';
COMMENT ON COLUMN DeviceHardware.tpm_details IS 'JSONB storing structured TPM details (Presence, Enabled, SpecVersion, ManufacturerVersion, ManufacturerId).';
COMMENT ON COLUMN DeviceHardware.cpu_details IS 'JSONB containing detailed CPU information (Model, Speed, Cores, Logical Processors, Architecture, Features).';
COMMENT ON COLUMN DeviceHardware.ram_details IS 'JSONB array storing details for each installed RAM module (Slot, Capacity, Type, Speed, Manufacturer, Part#, Serial#).';
COMMENT ON COLUMN DeviceHardware.bios_info IS 'JSONB containing BIOS/Firmware details (Manufacturer, Version, Release Date, Secure Boot Status).';
COMMENT ON COLUMN DeviceHardware.motherboard_info IS 'JSONB containing Motherboard details (Manufacturer, Product, Serial#).';
COMMENT ON COLUMN DeviceHardware.chassis_info IS 'JSONB containing Chassis details (Manufacturer, Type, Serial#).';
COMMENT ON COLUMN DeviceHardware.video_controllers IS 'JSONB array storing details for each video controller (Name, Manufacturer, RAM, Driver Version, WDDM/DirectX Info).';
COMMENT ON COLUMN DeviceHardware.sound_devices IS 'JSONB array storing details for each sound device (Name, Manufacturer).';
COMMENT ON COLUMN DeviceHardware.usb_controllers IS 'JSONB array storing details about detected USB controllers.';
CREATE TABLE Applications (
application_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- The primary Org associated with this application
name VARCHAR(255) NOT NULL, -- User-friendly name of the application
description TEXT,
application_type VARCHAR(50) NULL CHECK (application_type IN ('CommercialOffTheShelf', 'CustomDeveloped', 'SaaS', 'Internal', 'Other')),
business_owner_contact_id UUID NULL REFERENCES Contacts(contact_id) ON DELETE SET NULL, -- Primary business contact/owner
technical_owner_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- Primary technical contact/owner (MSP or Client User)
criticality VARCHAR(20) NULL CHECK (criticality IN ('VeryHigh', 'High', 'Medium', 'Low', 'Informational')), -- Business criticality
status VARCHAR(20) DEFAULT 'Production' CHECK (status IN ('Planning', 'Development', 'Testing', 'Production', 'Pilot', 'Retired')),
vendor_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE SET NULL, -- Vendor if COTS/SaaS
version VARCHAR(100) NULL, -- Current version if applicable
url TEXT NULL, -- Primary URL for accessing the application (if web-based)
notes TEXT, -- Use polymorphic Notes table instead? Keep simple notes here for now.
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
UNIQUE (org_id, name)
);
COMMENT ON TABLE Applications IS 'Represents logical business applications, potentially composed of multiple infrastructure CIs.';
COMMENT ON COLUMN Applications.criticality IS 'Business criticality level assigned to the application.';
CREATE TABLE Peripherals (
peripheral_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
peripheral_type VARCHAR(50) NOT NULL CHECK (peripheral_type IN ('Monitor', 'Keyboard', 'Mouse', 'DockingStation', 'Webcam', 'Other')),
manufacturer_id UUID REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL,
model VARCHAR(255),
serial_number VARCHAR(255),
asset_tag VARCHAR(100),
status VARCHAR(30) DEFAULT 'InUse' CHECK (status IN ('InStock', 'InUse', 'InRepair', 'Retired', 'Missing')),
purchase_date DATE,
warranty_expiry_date DATE,
location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
assigned_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT peripherals_org_serial_unique UNIQUE NULLS NOT DISTINCT (org_id, serial_number),
CONSTRAINT peripherals_org_asset_tag_unique UNIQUE NULLS NOT DISTINCT (org_id, asset_tag)
);
CREATE TABLE DevicePeripherals (
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
peripheral_id UUID NOT NULL REFERENCES Peripherals(peripheral_id) ON DELETE CASCADE,
connection_type VARCHAR(50),
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (device_id, peripheral_id)
);
CREATE TABLE PhysicalDisks (
physical_disk_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
manufacturer_id UUID REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL,
model VARCHAR(255),
serial_number VARCHAR(255) UNIQUE,
firmware_version VARCHAR(50),
interface_type VARCHAR(20) CHECK (interface_type IN ('SATA', 'SAS', 'NVMe', 'USB', 'SCSI', 'FC', 'IDE', 'Other')),
media_type VARCHAR(20) CHECK (media_type IN ('HDD', 'SSD', 'NVMe SSD', 'Removable', 'Unknown')),
capacity_bytes BIGINT CHECK (capacity_bytes >= 0),
status VARCHAR(30) DEFAULT 'Online' CHECK (status IN ('Online', 'Offline', 'Failed', 'PredictiveFailure', 'Rebuilding', 'Spare', 'Unconfigured', 'Missing', 'Unknown')), -- Includes SMART status mapping
physical_location VARCHAR(100), -- e.g., Disk 0, Slot 1
power_on_hours INTEGER NULL, -- Added Disk Power-On Hours
storage_controller_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
storage_array_id UUID REFERENCES StorageArrays(storage_array_id) ON DELETE SET NULL,
raid_role VARCHAR(20) CHECK (raid_role IN ('Member', 'HotSpare', 'GlobalSpare', 'Parity', 'Cache', 'Journal')),
last_updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE PhysicalDisks IS 'Stores details about physical disk drives in devices, including SMART status and power-on hours.';
COMMENT ON COLUMN PhysicalDisks.status IS 'Operational status, potentially including mapped SMART health status (e.g., Online, Failed, PredictiveFailure).';
COMMENT ON COLUMN PhysicalDisks.power_on_hours IS 'Total hours the disk has been powered on, typically retrieved from SMART data.';
CREATE INDEX idx_physicaldisks_device_id ON PhysicalDisks(device_id);
CREATE TABLE StorageArrays (
storage_array_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
controller_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
raid_level VARCHAR(50),
status VARCHAR(30) DEFAULT 'Optimal' CHECK (status IN ('Optimal', 'Degraded', 'Rebuilding', 'Failed', 'Offline')),
capacity_bytes BIGINT CHECK (capacity_bytes >= 0),
used_bytes BIGINT CHECK (used_bytes >= 0),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE(controller_device_id, name)
);
CREATE TABLE SoftwareProducts (
software_product_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
manufacturer_id UUID REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
version VARCHAR(100),
edition VARCHAR(100),
platform VARCHAR(50) DEFAULT 'CrossPlatform',
category VARCHAR(100),
end_of_life_date DATE,
end_of_support_date DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT softwareproducts_unique UNIQUE NULLS NOT DISTINCT (manufacturer_id, name, version, edition, platform)
);
CREATE TABLE SoftwareNormalizationRules (
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
description VARCHAR(255),
match_criteria JSONB NOT NULL,
target_software_product_id UUID NOT NULL REFERENCES SoftwareProducts(software_product_id) ON DELETE CASCADE,
priority INTEGER NOT NULL DEFAULT 100,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE DeviceSoftware (
device_software_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
display_name VARCHAR(255) NOT NULL,
version VARCHAR(100),
publisher VARCHAR(255),
install_date DATE,
install_path TEXT,
detected_software_product_id UUID REFERENCES SoftwareProducts(software_product_id) ON DELETE SET NULL, -- Normalized product
last_detected_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
source VARCHAR(50), -- How was this record created/updated (Agent, Manual, Import)
-- Agent-Detected Licensing Info --
detected_license_key TEXT NULL, -- License key found on the device by agent (may be generic KMS/MAK, OEM marker, etc.)
detected_activation_status VARCHAR(30) NULL CHECK (detected_activation_status IN ('Activated', 'GracePeriod', 'Notification', 'Unlicensed', 'OOBGrace', 'OOTGrace', 'NonGenuineGrace', 'Unknown')), -- Activation status reported by the agent
CONSTRAINT devicesoftware_unique UNIQUE NULLS NOT DISTINCT (device_id, display_name, version, publisher)
);
COMMENT ON TABLE DeviceSoftware IS 'Tracks software installations detected on devices.';
COMMENT ON COLUMN DeviceSoftware.detected_license_key IS 'Raw license key detected on the device for this software installation (informational, may not be entitlement key).';
COMMENT ON COLUMN DeviceSoftware.detected_activation_status IS 'Activation status reported by the agent for this installation (e.g., WMI query result).';
CREATE TABLE DeviceLogicalDisks (
disk_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
drive_letter_or_mount VARCHAR(255) NOT NULL,
volume_name VARCHAR(255),
file_system VARCHAR(50),
size_bytes BIGINT CHECK (size_bytes >= 0),
free_space_bytes BIGINT CHECK (free_space_bytes >= 0),
is_os_drive BOOLEAN NULL, -- Added: Flag indicating if this volume holds the OS
parent_type VARCHAR(20) NULL CHECK (parent_type IN ('PhysicalDisk', 'StorageArray', 'SoftwareRAID', 'Unknown')), -- Added: Type of underlying storage
parent_identifiers TEXT[] NULL, -- Added: Array of identifiers for the underlying storage
last_updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(device_id, drive_letter_or_mount)
);
COMMENT ON TABLE DeviceLogicalDisks IS 'Stores information about logical disks (volumes/partitions) on a device, including OS drive flag and link to underlying physical storage.';
COMMENT ON COLUMN DeviceLogicalDisks.is_os_drive IS 'Flag indicating if this logical disk contains the primary operating system.';
COMMENT ON COLUMN DeviceLogicalDisks.parent_type IS 'Type of underlying storage (PhysicalDisk, StorageArray, SoftwareRAID).';
COMMENT ON COLUMN DeviceLogicalDisks.parent_identifiers IS 'Array of identifiers (e.g., Physical Disk UUIDs/Serials, Storage Array UUID) for the underlying parent storage.';
CREATE INDEX idx_devicelogicaldisks_device_id ON DeviceLogicalDisks(device_id);
CREATE INDEX idx_devicelogicaldisks_osdrive ON DeviceLogicalDisks(device_id, is_os_drive) WHERE is_os_drive = true;
CREATE TABLE DeviceServices (
service_entry_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
service_name VARCHAR(255) NOT NULL,
display_name VARCHAR(255),
status VARCHAR(50),
start_type VARCHAR(50),
path_name TEXT,
log_on_as VARCHAR(255),
last_updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(device_id, service_name)
);
CREATE TABLE DeviceProcesses (
device_process_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
snapshot_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
process_name VARCHAR(255) NOT NULL,
pid INTEGER NOT NULL,
cpu_usage_percent NUMERIC(5,2),
memory_usage_bytes BIGINT,
user_name VARCHAR(255),
path TEXT,
command_line TEXT,
start_time TIMESTAMPTZ
);
CREATE TABLE EquipmentTypes (
equipment_type_id SERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_individually_tracked BOOLEAN NOT NULL DEFAULT false,
UNIQUE (org_id, name)
);
CREATE TABLE EquipmentAssets (
equipment_asset_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
equipment_type_id INTEGER NOT NULL REFERENCES EquipmentTypes(equipment_type_id) ON DELETE RESTRICT,
asset_tag VARCHAR(100) UNIQUE,
serial_number VARCHAR(255) UNIQUE NULLS NOT DISTINCT,
name VARCHAR(255),
description TEXT,
manufacturer_id UUID REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL,
model VARCHAR(100),
purchase_date DATE,
warranty_expiry_date DATE,
current_status VARCHAR(20) DEFAULT 'Available' CHECK (current_status IN ('Available', 'Assigned', 'InRepair', 'Retired', 'Missing')),
current_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
assigned_technician_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
last_seen_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE SerializedAssetInstances (
serial_instance_id BIGINT PRIMARY KEY REFERENCES ReceivedItemSerials(serial_instance_id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE RESTRICT,
serial_number VARCHAR(255) NOT NULL UNIQUE,
current_status VARCHAR(30) NOT NULL DEFAULT 'InStock' CHECK (current_status IN ('InStock', 'Allocated', 'Deployed', 'InRepair', 'Disposed', 'ReturnedToVendor', 'Missing')),
current_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
assigned_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
assigned_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
warranty_expiry_date DATE,
purchase_cost NUMERIC(19,4),
last_status_update TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE ConfigurationItemRelationships (
relationship_id BIGSERIAL PRIMARY KEY,
parent_ci_type VARCHAR(50) NOT NULL,
parent_ci_id VARCHAR(36) NOT NULL,
child_ci_type VARCHAR(50) NOT NULL,
child_ci_id VARCHAR(36) NOT NULL,
relationship_type VARCHAR(50) NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (parent_ci_type, parent_ci_id, child_ci_type, child_ci_id, relationship_type)
);
CREATE TABLE Attachments (
attachment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
file_name VARCHAR(255) NOT NULL,
mime_type VARCHAR(100) NOT NULL,
file_size_bytes BIGINT NOT NULL,
storage_provider VARCHAR(50) NOT NULL DEFAULT 'LocalStorage',
storage_path TEXT NOT NULL UNIQUE,
uploaded_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE LocationAttachments (
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
description TEXT,
is_primary_image BOOLEAN NOT NULL DEFAULT false,
attached_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (location_id, attachment_id)
);
CREATE TABLE DeviceAttachments (
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
description TEXT,
is_primary_image BOOLEAN NOT NULL DEFAULT false,
attached_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (device_id, attachment_id)
);
-- =============================================
-- Network Infrastructure & Topology
-- =============================================
CREATE TABLE NetworkDevicePorts (
port_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
port_index INTEGER,
port_name VARCHAR(255) NOT NULL,
port_description VARCHAR(512),
port_type VARCHAR(50) NOT NULL, -- e.g., 'Ethernet', 'FastEthernet', 'GigabitEthernet', 'Serial', 'Loopback'
status VARCHAR(50), -- Operational status (e.g., 'Up', 'Down', 'Testing')
admin_status VARCHAR(50), -- Administrative status (e.g., 'Up', 'Down')
native_vlan_record_id UUID NULL REFERENCES Vlans(vlan_record_id) ON DELETE SET NULL, -- FK to Vlans record for the native/untagged VLAN
allowed_vlan_ids INTEGER[], -- Array of NUMERIC VLAN IDs allowed if this is a trunk port (1-4094)
is_trunk BOOLEAN,
duplex VARCHAR(10), -- 'Full', 'Half', 'Auto'
speed_mbps BIGINT,
is_poe_enabled BOOLEAN,
is_manually_added BOOLEAN NOT NULL DEFAULT false,
last_status_change TIMESTAMPTZ,
last_updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT networkdeviceports_device_port_name_unique UNIQUE(device_id, port_name)
);
COMMENT ON TABLE NetworkDevicePorts IS 'Represents physical or logical ports on network devices (switches, routers).';
COMMENT ON COLUMN NetworkDevicePorts.native_vlan_record_id IS 'FK to the Vlans table representing the native (untagged) VLAN for this port.';
COMMENT ON COLUMN NetworkDevicePorts.allowed_vlan_ids IS 'Array of integer VLAN IDs (1-4094) permitted on this port if it is configured as a trunk.';
CREATE TABLE DeviceNetworkAdapters (
adapter_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description VARCHAR(255),
mac_address MACADDR,
manufacturer VARCHAR(255) NULL, -- Added Manufacturer
status VARCHAR(50), -- e.g., 'Up', 'Down', 'Disconnected'
is_physical BOOLEAN,
is_virtual BOOLEAN,
vlan_record_id UUID NULL REFERENCES Vlans(vlan_record_id) ON DELETE SET NULL,
ip_addresses INET[],
subnets CIDR[],
gateways INET[],
dns_servers INET[],
dhcp_enabled BOOLEAN,
dhcp_server INET,
link_speed_mbps BIGINT,
last_updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(device_id, name),
UNIQUE(device_id, mac_address) WHERE mac_address IS NOT NULL
);
COMMENT ON TABLE DeviceNetworkAdapters IS 'Represents network interface controllers (NICs) on endpoint devices.';
COMMENT ON COLUMN DeviceNetworkAdapters.manufacturer IS 'Manufacturer of the network adapter hardware.';
CREATE INDEX idx_devicenetworkadapters_device_id ON DeviceNetworkAdapters(device_id);
CREATE INDEX idx_devicenetworkadapters_mac_address ON DeviceNetworkAdapters(mac_address) WHERE mac_address IS NOT NULL;
CREATE TABLE NetworkConnections (
connection_id BIGSERIAL PRIMARY KEY,
local_port_id UUID NOT NULL REFERENCES NetworkDevicePorts(port_id) ON DELETE CASCADE,
remote_device_name VARCHAR(255),
remote_port_name VARCHAR(255),
remote_mac_address MACADDR,
remote_ip_address INET,
remote_platform VARCHAR(255),
remote_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
remote_network_adapter_id UUID REFERENCES DeviceNetworkAdapters(adapter_id) ON DELETE SET NULL,
remote_port_id UUID REFERENCES NetworkDevicePorts(port_id) ON DELETE SET NULL,
connection_type VARCHAR(20) NOT NULL,
discovery_protocol VARCHAR(20),
status VARCHAR(20) DEFAULT 'Active',
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
first_seen_at TIMESTAMPTZ
-- UNIQUE constraint on (local_port_id, remote_mac_address, remote_port_name) handled by index if needed
);
CREATE TABLE WirelessNetworks (
wireless_network_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
ssid_name VARCHAR(255) NOT NULL,
security_type VARCHAR(50),
authentication_type VARCHAR(50),
encryption_type VARCHAR(50),
is_guest_network BOOLEAN DEFAULT false,
configuration JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, ssid_name)
);
CREATE TABLE SnmpMonitoredDevices (
device_id UUID PRIMARY KEY REFERENCES Devices(device_id) ON DELETE CASCADE,
snmp_version VARCHAR(3) NOT NULL CHECK (snmp_version IN ('v1', 'v2c', 'v3')),
port INTEGER NOT NULL DEFAULT 161,
community_string TEXT, -- Encrypt or vault reference
v3_security_name VARCHAR(100),
v3_security_level VARCHAR(20) CHECK (v3_security_level IN ('noAuthNoPriv', 'authNoPriv', 'authPriv')),
v3_auth_protocol VARCHAR(10) CHECK (v3_auth_protocol IN ('MD5', 'SHA', 'SHA256', 'SHA384', 'SHA512')),
v3_auth_passphrase TEXT, -- Encrypt or vault reference
v3_priv_protocol VARCHAR(10) CHECK (v3_priv_protocol IN ('DES', 'AES', 'AES192', 'AES256')),
v3_priv_passphrase TEXT, -- Encrypt or vault reference
timeout_ms INTEGER NOT NULL DEFAULT 5000,
retries INTEGER NOT NULL DEFAULT 2,
last_successful_poll TIMESTAMPTZ,
last_error TEXT,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE ServiceZones (
zone_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Owning Org (MSP)
name VARCHAR(100) NOT NULL,
geo_boundary GEOGRAPHY(POLYGON, 4326), -- Requires PostGIS extension
description TEXT,
UNIQUE (org_id, name)
);
CREATE TABLE TechnicianServiceZones (
technician_user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
zone_id UUID NOT NULL REFERENCES ServiceZones(zone_id) ON DELETE CASCADE,
PRIMARY KEY (technician_user_id, zone_id)
);
CREATE TABLE Vlans (
vlan_record_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- New stable primary key
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org defining/using this VLAN
vlan_id INTEGER NOT NULL CHECK (vlan_id >= 1 AND vlan_id <= 4094), -- The actual VLAN ID (1-4094)
name VARCHAR(100) NULL, -- Optional descriptive name (e.g., 'Servers', 'Workstations', 'Voice')
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT vlans_org_vlan_id_unique UNIQUE (org_id, vlan_id) -- VLAN ID must be unique within an Organization
);
COMMENT ON TABLE Vlans IS 'Represents Virtual Local Area Networks (VLANs). Uses a surrogate UUID key (vlan_record_id) for stable FK references.';
COMMENT ON COLUMN Vlans.vlan_record_id IS 'Internal unique identifier for the VLAN record.';
COMMENT ON COLUMN Vlans.vlan_id IS 'The actual 802.1Q VLAN Tag ID (1-4094), unique within the scope of the owning Organization.';
COMMENT ON TABLE Vlans IS 'Represents Virtual Local Area Networks (VLANs).';
CREATE TABLE Subnets (
subnet_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Owning Org
subnet_cidr CIDR NOT NULL, -- Subnet definition (e.g., '192.168.1.0/24')
name VARCHAR(100), -- User-friendly name (e.g., 'Main Office LAN', 'Server Subnet DC1')
description TEXT,
location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Physical location association
vlan_record_id UUID NULL REFERENCES Vlans(vlan_record_id) ON DELETE SET NULL, -- Associated VLAN record (FK)
gateway_ip INET NULL, -- Default gateway for this subnet
dhcp_server_ip INET NULL, -- Primary DHCP server address
dns_server_ips INET[] NULL, -- Array of DNS server IPs
-- Cloud Network Links (Optional, if subnet represents a cloud VPC/Subnet)
aws_vpc_id VARCHAR(50) NULL, -- AWS VPC ID if applicable
aws_subnet_id VARCHAR(50) NULL, -- AWS Subnet ID if applicable
azure_vnet_name VARCHAR(255) NULL, -- Azure VNet Name if applicable
azure_subnet_name VARCHAR(255) NULL, -- Azure Subnet Name if applicable
gcp_network_name VARCHAR(255) NULL, -- GCP Network Name if applicable
gcp_subnetwork_name VARCHAR(255) NULL, -- GCP Subnetwork Name if applicable
is_routable BOOLEAN DEFAULT true, -- Is this subnet expected to be routable?
is_active BOOLEAN DEFAULT true,
last_scan_time TIMESTAMPTZ, -- When IPs in this subnet were last actively scanned
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (org_id, subnet_cidr)
);
COMMENT ON TABLE Subnets IS 'Defines network subnets, their properties, and associations.';
COMMENT ON COLUMN Subnets.vlan_record_id IS 'FK to the Vlans table, linking this subnet to a specific VLAN definition.';
COMMENT ON COLUMN Subnets.location_id IS 'Primary physical location associated with this subnet.';
CREATE TABLE IpAddresses (
ip_address_id BIGSERIAL PRIMARY KEY,
subnet_id UUID NOT NULL REFERENCES Subnets(subnet_id) ON DELETE CASCADE,
ip_address INET NOT NULL, -- The actual IP Address
status VARCHAR(20) NOT NULL CHECK (status IN ('Static', 'DHCP', 'Reserved', 'Unused', 'Conflict', 'Transient')),
usage_type VARCHAR(30) NULL CHECK (usage_type IN ('Server', 'Workstation', 'Printer', 'NetworkDevice', 'VoipDevice', 'Gateway', 'DhcpServer', 'DnsServer', 'Management', 'VM', 'Container', 'Other')), -- What kind of device uses this IP?
assigned_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Link to the device currently using this IP (if known/static)
assigned_adapter_id UUID NULL REFERENCES DeviceNetworkAdapters(adapter_id) ON DELETE SET NULL, -- Link to specific NIC (more transient for DHCP)
hostname VARCHAR(255) NULL, -- Last known hostname associated with this IP (via DNS/scan)
mac_address MACADDR NULL, -- Last known MAC address associated with this IP
last_seen_timestamp TIMESTAMPTZ NULL, -- When this IP was last detected as active/assigned
first_seen_timestamp TIMESTAMPTZ NULL,
notes TEXT, -- Use polymorphic Notes table instead
is_critical BOOLEAN DEFAULT false, -- Flag critical IPs like gateways, servers
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (subnet_id, ip_address) -- An IP can only exist once within a given subnet definition
-- Add index on ip_address for fast lookups across subnets if needed
-- Add index on assigned_device_id
);
COMMENT ON TABLE IpAddresses IS 'Tracks individual IP addresses within defined subnets, their status, and associations.';
COMMENT ON COLUMN IpAddresses.status IS 'Current assignment status (Static, DHCP, Reserved, Unused, Conflict, Transient).';
COMMENT ON COLUMN IpAddresses.usage_type IS 'Intended or detected purpose of the device using this IP.';
-- Modify NetworkDevicePorts and DeviceNetworkAdapters to reference VLANs
ALTER TABLE NetworkDevicePorts ADD COLUMN native_vlan_id INTEGER NULL; -- No FK if Vlans PK isn't globally unique
-- ALTER TABLE NetworkDevicePorts ADD COLUMN allowed_vlan_ids INTEGER[] NULL; -- Already exists
ALTER TABLE DeviceNetworkAdapters ADD COLUMN vlan_id INTEGER NULL; -- No FK if Vlans PK isn't globally unique
-- =============================================
-- Policies & Configuration Management
-- =============================================
CREATE TABLE Scripts (
script_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
script_language VARCHAR(50) NOT NULL,
script_content TEXT NOT NULL,
execution_timeout_seconds INTEGER NOT NULL DEFAULT 300,
is_system_defined BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT scripts_org_name_unique UNIQUE(org_id, name)
);
CREATE TABLE ComplianceFrameworks (
framework_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
version VARCHAR(50),
description TEXT,
url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE ComplianceFrameworkControls (
control_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
framework_id UUID NOT NULL REFERENCES ComplianceFrameworks(framework_id) ON DELETE CASCADE,
control_identifier VARCHAR(100) NOT NULL,
control_name VARCHAR(512),
control_description TEXT,
url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE(framework_id, control_identifier)
);
CREATE TABLE CweDetails ( -- Common Weakness Enumeration lookup
cwe_id VARCHAR(20) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
url TEXT,
last_synced_at TIMESTAMPTZ
);
CREATE TABLE ComplianceRules (
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
definer_org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
platform_os_type VARCHAR(50) NOT NULL DEFAULT 'Any',
check_logic_type VARCHAR(50) NOT NULL,
check_logic_data JSONB NOT NULL,
default_check_interval_seconds INTEGER NOT NULL CHECK (default_check_interval_seconds > 0),
default_remediation_type VARCHAR(50) NOT NULL DEFAULT 'None',
default_remediation_script_id UUID REFERENCES Scripts(script_id) ON DELETE SET NULL,
default_remediation_ticket_template_id UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
default_remediation_variables JSONB,
is_system_defined BOOLEAN NOT NULL DEFAULT false,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT compliancerules_org_name_unique UNIQUE NULLS NOT DISTINCT (definer_org_id, name)
);
CREATE TABLE ComplianceRuleFrameworkMappings (
rule_id UUID NOT NULL REFERENCES ComplianceRules(rule_id) ON DELETE CASCADE,
control_id UUID NOT NULL REFERENCES ComplianceFrameworkControls(control_id) ON DELETE CASCADE,
PRIMARY KEY (rule_id, control_id)
);
CREATE TABLE ComplianceRuleCwes (
rule_id UUID NOT NULL REFERENCES ComplianceRules(rule_id) ON DELETE CASCADE,
cwe_id VARCHAR(20) NOT NULL REFERENCES CweDetails(cwe_id) ON DELETE CASCADE,
PRIMARY KEY (rule_id, cwe_id)
);
CREATE TABLE CompliancePolicies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
definer_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
policy_data JSONB NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT true,
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization',
scope_id UUID,
parent_policy_id UUID REFERENCES CompliancePolicies(policy_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT compliancepolicies_org_name_scope_unique UNIQUE(definer_org_id, name, scope_type, scope_id)
);
CREATE TABLE TagCompliancePolicyAssignments (
assignment_id BIGSERIAL PRIMARY KEY,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
compliance_policy_id UUID NOT NULL REFERENCES CompliancePolicies(policy_id) ON DELETE CASCADE,
is_enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 0,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE PatchPolicies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
definer_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
policy_data JSONB NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT true,
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization',
scope_id UUID,
parent_policy_id UUID REFERENCES PatchPolicies(policy_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT patchpolicies_org_name_scope_unique UNIQUE(definer_org_id, name, scope_type, scope_id)
);
CREATE TABLE TagPatchPolicyAssignments (
assignment_id BIGSERIAL PRIMARY KEY,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
patch_policy_id UUID NOT NULL REFERENCES PatchPolicies(policy_id) ON DELETE CASCADE,
is_enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 0,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE PatchSources (
patch_source_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, -- e.g., 'Microsoft', 'Apple', 'Chocolatey', 'VendorName', 'Internal'
description TEXT
);
COMMENT ON TABLE PatchSources IS 'Lookup table for sources/vendors of patches.';
-- Seed basic sources if desired:
-- INSERT INTO PatchSources (name, description) VALUES ('Microsoft', 'Microsoft Security Updates (KB Articles)');
-- INSERT INTO PatchSources (name, description) VALUES ('Apple', 'Apple Security Updates (APPLE-SA)');
-- INSERT INTO PatchSources (name, description) VALUES ('Chocolatey', 'Chocolatey Community Package Repository');
CREATE TABLE PatchDefinitions (
patch_definition_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
patch_source_id INTEGER NOT NULL REFERENCES PatchSources(patch_source_id) ON DELETE RESTRICT,
identifier VARCHAR(255) NOT NULL, -- Unique identifier within the source (e.g., 'KB5001234', 'APPLE-SA-2024-03-15-1', 'googlechrome', 'VendorPatchID-XYZ')
title VARCHAR(512) NOT NULL, -- Human-readable title/name
description TEXT NULL,
severity VARCHAR(20) NULL CHECK (severity IN ('Info', 'Low', 'Medium', 'High', 'Critical', 'Unknown')),
classification VARCHAR(100) NULL, -- e.g., 'Security Update', 'Critical Update', 'Feature Pack', 'Driver', 'Application Update'
release_date DATE NULL,
kb_article_url TEXT NULL, -- Link to Microsoft KB article
security_bulletin_id VARCHAR(100) NULL, -- e.g., MS bulletin ID
vendor_url TEXT NULL, -- Link to vendor advisory/patch page
cve_ids TEXT[] NULL, -- Array of associated CVE identifiers (e.g., ['CVE-2024-1234', 'CVE-2024-5678'])
cvss_base_score NUMERIC(3, 1) NULL,
supersedes_identifiers TEXT[] NULL, -- Optional: Array of identifiers this patch supersedes
is_superseded_by UUID NULL REFERENCES PatchDefinitions(patch_definition_id) ON DELETE SET NULL, -- Optional: Link to the patch that supersedes this one
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger or sync process
CONSTRAINT patchdefinitions_source_identifier_unique UNIQUE (patch_source_id, identifier)
);
COMMENT ON TABLE PatchDefinitions IS 'Catalog of known patches/updates from various sources (Microsoft, Apple, Chocolatey, etc.).';
COMMENT ON COLUMN PatchDefinitions.identifier IS 'Unique identifier for the patch within its source (e.g., KB number, Apple SA ID, Chocolatey package ID).';
COMMENT ON COLUMN PatchDefinitions.cve_ids IS 'Array of Common Vulnerabilities and Exposures (CVE) IDs associated with this patch.';
COMMENT ON COLUMN PatchDefinitions.is_superseded_by IS 'Link to the patch_definition_id of the patch that replaces this one, if known.';
CREATE TABLE PatchProductApplicability (
patch_applicability_id BIGSERIAL PRIMARY KEY,
patch_definition_id UUID NOT NULL REFERENCES PatchDefinitions(patch_definition_id) ON DELETE CASCADE,
software_product_id UUID NOT NULL REFERENCES SoftwareProducts(software_product_id) ON DELETE CASCADE, -- Link to the specific software product/version this patch applies to
notes TEXT NULL, -- Any specific applicability notes
UNIQUE (patch_definition_id, software_product_id)
);
COMMENT ON TABLE PatchProductApplicability IS 'Maps patches from PatchDefinitions to the specific SoftwareProducts they apply to.';
CREATE TABLE MonitoringPolicies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
definer_org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE, -- NULL for Platform defaults, OrgID for SP/Client policies
name VARCHAR(255) NOT NULL, -- Name of the monitoring policy/template
description TEXT,
category VARCHAR(100), -- For UI grouping (e.g., 'Windows Servers', 'Network Devices', 'SQL Monitoring')
policy_data JSONB NOT NULL, -- Defines which MonitoringRules are included and potential overrides
-- Example policy_data structure:
-- {
-- "rules": [
-- {
-- "monitoring_rule_id": "uuid-of-cpu-rule",
-- "is_enabled": true,
-- "override_check_interval_seconds": 300,
-- "override_check_parameters": { "warning_threshold": 85, "critical_threshold": 95 },
-- "override_alert_severity_critical": "Critical",
-- "override_ticket_template_id_critical": "uuid-of-critical-cpu-template"
-- },
-- { "monitoring_rule_id": "uuid-of-disk-rule", "is_enabled": true },
-- { "monitoring_rule_id": "uuid-of-service-rule", "is_enabled": false }
-- ]
-- }
is_enabled BOOLEAN NOT NULL DEFAULT true, -- Is this policy definition active?
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization' CHECK (scope_type IN ('Global', 'Organization', 'Location', 'Device')), -- Scope where this *specific definition/override* applies
scope_id UUID NULL, -- Links to Org, Location, or Device based on scope_type (NULL if Global)
parent_policy_id UUID REFERENCES MonitoringPolicies(policy_id) ON DELETE SET NULL, -- Optional link for hierarchical overrides
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT monitoringpolicies_org_name_scope_unique UNIQUE NULLS NOT DISTINCT (definer_org_id, name, scope_type, scope_id)
);
CREATE TABLE MonitoringRules (
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
definer_org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
is_enabled BOOLEAN NOT NULL DEFAULT true,
target_platform_os_type TEXT[],
target_cpu_architectures TEXT[],
check_type VARCHAR(30) NOT NULL,
check_parameters JSONB NOT NULL,
check_interval_seconds INTEGER NOT NULL CHECK (check_interval_seconds >= 60),
execution_location VARCHAR(10) NOT NULL DEFAULT 'Agent' CHECK (execution_location IN ('Agent', 'Probe', 'Cloud')),
assigned_probe_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
warning_threshold_enabled BOOLEAN NOT NULL DEFAULT true,
critical_threshold_enabled BOOLEAN NOT NULL DEFAULT true,
alert_if_unavailable BOOLEAN NOT NULL DEFAULT false,
alert_delay_seconds INTEGER NOT NULL DEFAULT 0,
alert_clear_delay_seconds INTEGER NOT NULL DEFAULT 0,
alert_severity_warning VARCHAR(20) DEFAULT 'Warning' CHECK (alert_severity_warning IN ('Info', 'Warning', 'Critical', 'None')),
alert_severity_critical VARCHAR(20) DEFAULT 'Critical' CHECK (alert_severity_critical IN ('Info', 'Warning', 'Critical', 'None')),
notification_profile_id UUID NULL REFERENCES NotificationProfiles(profile_id) ON DELETE SET NULL, -- Link to detailed notification/escalation profile
-- Basic actions below might be ignored if notification_profile_id is set, depending on app logic:
auto_create_ticket_on VARCHAR(10) NOT NULL DEFAULT 'Critical' CHECK (auto_create_ticket_on IN ('None', 'Warning', 'Critical', 'Both')),
ticket_template_id_warning UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
ticket_template_id_critical UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
auto_resolve_ticket BOOLEAN NOT NULL DEFAULT true,
auto_run_script_on VARCHAR(10) NOT NULL DEFAULT 'None' CHECK (auto_run_script_on IN ('None', 'Warning', 'Critical', 'Both')),
remediation_script_id UUID REFERENCES Scripts(script_id) ON DELETE SET NULL,
remediation_delay_seconds INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT monitoringrules_org_name_unique UNIQUE NULLS NOT DISTINCT (definer_org_id, name)
);
CREATE TABLE TagMonitoringPolicyAssignments (
assignment_id BIGSERIAL PRIMARY KEY,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
monitoring_policy_id UUID NOT NULL REFERENCES MonitoringPolicies(policy_id) ON DELETE CASCADE,
is_enabled BOOLEAN NOT NULL DEFAULT true, -- Enable/disable this specific tag-to-policy link
priority INTEGER NOT NULL DEFAULT 0, -- Order for resolving conflicts if a device has multiple tags with competing policies (lower number = higher priority)
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
-- Consider adding a UNIQUE constraint on (tag_id, monitoring_policy_id) if needed
);
CREATE TABLE SentNotifications (
sent_notification_id BIGSERIAL PRIMARY KEY,
org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE SET NULL, -- Org context, if applicable
triggering_event_source VARCHAR(50) NULL, -- Source type: e.g., 'MonitoringRule', 'TicketStatusChange', 'ApprovalRequest', 'Survey', 'ManualBroadcast'
triggering_event_id VARCHAR(50) NULL, -- ID of the source entity (Alert ID, Ticket ID, Approval Request ID etc.)
notification_profile_id UUID NULL REFERENCES NotificationProfiles(profile_id) ON DELETE SET NULL, -- Profile used, if applicable
escalation_step_id UUID NULL REFERENCES NotificationProfileEscalationSteps(step_id) ON DELETE SET NULL, -- Specific step in the profile, if applicable
notification_template_id UUID NULL REFERENCES NotificationTemplates(template_id) ON DELETE SET NULL, -- Template used, if applicable
delivery_method VARCHAR(20) NOT NULL CHECK (delivery_method IN ('Email', 'SMS', 'System', 'Webhook', 'Push')), -- How it was sent
status VARCHAR(30) NOT NULL DEFAULT 'Pending' CHECK (status IN ('Pending', 'Sent', 'Delivered', 'Failed', 'Opened', 'Clicked', 'Undeliverable', 'Queued')), -- Delivery status
recipient_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- Target CommandIT User, if applicable
recipient_contact_id UUID NULL REFERENCES Contacts(contact_id) ON DELETE SET NULL, -- Target CommandIT Contact, if applicable
recipient_group_id UUID NULL REFERENCES DistributionGroups(distribution_group_id) ON DELETE SET NULL, -- Target Group, if applicable
recipient_address TEXT NOT NULL, -- The actual address/identifier used (email, phone number, user ID for system/push)
subject TEXT NULL, -- Snapshot of the subject line (for Email)
body_summary TEXT NULL, -- Snapshot or summary of the notification body
sent_at TIMESTAMPTZ NULL, -- Timestamp when the send attempt was initiated
processed_at TIMESTAMPTZ NULL, -- Timestamp when the delivery provider acknowledged/processed it
status_updated_at TIMESTAMPTZ NULL, -- Timestamp of the last status update (e.g., Delivered, Failed)
error_message TEXT NULL, -- Details if the status is 'Failed' or 'Undeliverable'
external_message_id VARCHAR(255) NULL, -- Optional: ID from the external delivery service (e.g., SendGrid, Twilio)
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -- When the notification record was created internally
);
COMMENT ON TABLE SentNotifications IS 'Logs individual notifications sent by the system for auditing and status tracking.';
COMMENT ON COLUMN SentNotifications.triggering_event_source IS 'Indicates what type of event triggered this notification (e.g., MonitoringRule, TicketStatusChange).';
COMMENT ON COLUMN SentNotifications.triggering_event_id IS 'The ID of the specific entity that triggered the notification.';
COMMENT ON COLUMN SentNotifications.delivery_method IS 'The channel used for sending the notification (Email, SMS, System, etc.).';
COMMENT ON COLUMN SentNotifications.status IS 'The current delivery status of the notification.';
COMMENT ON COLUMN SentNotifications.recipient_address IS 'The actual destination address used (email address, phone number, user ID).';
COMMENT ON COLUMN SentNotifications.external_message_id IS 'Identifier provided by the external delivery service (e.g., Mailgun, Twilio ID).';
-- Add indexes for common query patterns
CREATE INDEX idx_sentnotifications_status ON SentNotifications(status);
CREATE INDEX idx_sentnotifications_recipient_user_id ON SentNotifications(recipient_user_id);
CREATE INDEX idx_sentnotifications_recipient_contact_id ON SentNotifications(recipient_contact_id);
CREATE INDEX idx_sentnotifications_recipient_address ON SentNotifications(recipient_address);
CREATE INDEX idx_sentnotifications_triggering_event ON SentNotifications(triggering_event_source, triggering_event_id);
CREATE INDEX idx_sentnotifications_created_at ON SentNotifications(created_at);
-- =============================================
-- Event Log Monitoring
-- =============================================
-- Stores metadata about known OS/Application events to populate the UI library
-- when creating Event Log Monitoring Policies/Rules. Defines what agents should look for.
CREATE TABLE EventLogDefinitions (
definition_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- NULL for system-provided, OrgID for MSP-defined additions
os_type VARCHAR(20) NOT NULL CHECK (os_type IN ('Windows', 'macOS', 'Linux', 'Any')), -- Target OS
log_name VARCHAR(255) NOT NULL, -- e.g., 'Security', 'System', 'Application', 'auth.log', 'system.log'
channel_path TEXT NULL, -- Added: Specific Windows Event Channel Path (e.g., 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational')
source_name VARCHAR(255) NULL, -- Optional: Specific event source name (e.g., 'Microsoft-Windows-Security-Auditing', 'sshd')
event_id VARCHAR(50) NOT NULL, -- Event ID (Integer for Windows, potentially string for others)
query TEXT NULL, -- Added: Optional XML/XPath query for filtering (Defaults to * if NULL)
level VARCHAR(30) NULL, -- Typical level (e.g., 'Information', 'Warning', 'Error', 'AuditSuccess', 'AuditFailure'), can be overridden in policy
name VARCHAR(255) NOT NULL, -- User-friendly name/title for the event
description TEXT NULL, -- Explanation of what this event typically signifies (Matches requirement)
recommended_severity VARCHAR(20) NULL CHECK (recommended_severity IN ('Info', 'Warning', 'Critical')), -- Suggested alert severity if monitored
mitre_attck_technique_ids TEXT[] NULL, -- Optional: Array of relevant MITRE ATT&CK Technique IDs (e.g., ['T1078', 'T1021.001'])
is_system_defined BOOLEAN GENERATED ALWAYS AS (owner_org_id IS NULL) STORED, -- Is this provided by CommandIT?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE NULLS NOT DISTINCT (owner_org_id, os_type, name), -- Name unique per OS within its scope (System or specific Org)
UNIQUE NULLS NOT DISTINCT (owner_org_id, os_type, log_name, channel_path, source_name, event_id) -- Technical uniqueness including channel path
);
COMMENT ON TABLE EventLogDefinitions IS 'Stores metadata about known OS/Application events to define capture rules for agents. Includes standard logs and specific channel paths.';
COMMENT ON COLUMN EventLogDefinitions.owner_org_id IS 'NULL for definitions provided by CommandIT, OrgID for custom definitions added by an MSP.';
COMMENT ON COLUMN EventLogDefinitions.log_name IS 'Standard log name (Application, Security, System) or primary log file name (syslog, auth.log).';
COMMENT ON COLUMN EventLogDefinitions.channel_path IS 'Optional, specific Windows Event Log Channel path (e.g., Application, System, Security, or deeper like Microsoft-Windows-TaskScheduler/Operational).';
COMMENT ON COLUMN EventLogDefinitions.event_id IS 'Event Identifier (numeric for Windows, may be string for others).';
COMMENT ON COLUMN EventLogDefinitions.query IS 'Optional: Advanced filtering query (e.g., XPath for Windows Event Log XML) applied by the agent. Defaults to match all events matching other criteria if NULL.';
COMMENT ON COLUMN EventLogDefinitions.description IS 'Explanation of what this event typically signifies.';
CREATE INDEX idx_eventlogdefinitions_owner ON EventLogDefinitions(owner_org_id);
CREATE INDEX idx_eventlogdefinitions_ostype ON EventLogDefinitions(os_type);
CREATE INDEX idx_eventlogdefinitions_log_channel ON EventLogDefinitions(log_name, channel_path);
CREATE TABLE EventLogMonitoringPolicies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
definer_org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE, -- NULL for Platform defaults, OrgID for SP/Client policies
name VARCHAR(255) NOT NULL, -- e.g., "Default Windows Security Events", "Critical Linux Auth Events", "Custom App XYZ Logs"
description TEXT,
target_os_types TEXT[], -- Optional: Limit policy to ['Windows'], ['Linux'], ['macOS']
policy_data JSONB NOT NULL, -- Defines events & settings
-- Example policy_data structure:
-- {
-- "event_poll_frequency_seconds": 300, -- Fallback polling interval if subscription fails/not possible
-- "windows_events": [
-- { "log_name": "Security", "event_id": 4720, "level": ["AuditSuccess"], "keywords_include": null, "keywords_exclude": null, "is_enabled": true, "description": "User account created" },
-- { "log_name": "Security", "event_id": 4740, "level": ["AuditFailure"], "is_enabled": true, "description": "User account locked out" }
-- { "log_name": "System", "event_id": 7036, "source_name": null, "level": ["Information"], "keywords_include": ["Defensive Service Name"], "is_enabled": true, "description": "A defensive service was stopped." }
-- ],
-- "macos_events": [
-- { "log_process": "sshd", "keywords_include": ["Accepted password for"], "is_enabled": true, "description": "SSH Connection Success" }
-- ],
-- "linux_events": [
-- { "log_facility": "auth", "severity": ["err", "crit"], "keywords_include": ["authentication failure"], "is_enabled": true, "description": "User Authentication Failure"}
-- ]
-- }
is_enabled BOOLEAN NOT NULL DEFAULT true, -- Is this policy definition active?
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization' CHECK (scope_type IN ('Global', 'Organization', 'Location', 'Device')), -- Scope of application
scope_id UUID NULL,
parent_policy_id UUID REFERENCES EventLogMonitoringPolicies(policy_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
CONSTRAINT eventlogpolicies_org_name_scope_unique UNIQUE NULLS NOT DISTINCT (definer_org_id, name, scope_type, scope_id)
);
COMMENT ON TABLE EventLogMonitoringPolicies IS 'Defines policies for monitoring specific OS Event Logs.';
COMMENT ON COLUMN EventLogMonitoringPolicies.policy_data IS 'JSONB defining events to capture (by log name, source, ID, level, keywords) and settings like privacy masking.';
CREATE TABLE TagEventLogMonitoringPolicyAssignments (
assignment_id BIGSERIAL PRIMARY KEY,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
event_log_policy_id UUID NOT NULL REFERENCES EventLogMonitoringPolicies(policy_id) ON DELETE CASCADE,
is_enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 0, -- Conflict resolution priority
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (tag_id, event_log_policy_id)
);
COMMENT ON TABLE TagEventLogMonitoringPolicyAssignments IS 'Applies Event Log Monitoring Policies to devices/orgs via Tags.';
CREATE TABLE NotificationProfiles (
profile_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Owning Org (MSP)
name VARCHAR(150) NOT NULL, -- e.g., "Critical Server Alerts - Tiered", "SLA Breach Warning - Client Mgr"
description TEXT,
is_default_for_org BOOLEAN NOT NULL DEFAULT false, -- Is this the default profile for the Org? (App logic enforces only one)
is_active BOOLEAN NOT NULL DEFAULT true, -- Can this profile be used?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name)
);
CREATE TABLE NotificationProfileTimeRules (
time_rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
profile_id UUID NOT NULL REFERENCES NotificationProfiles(profile_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL, -- e.g., "Business Hours", "After Hours", "Weekends", "Catch All"
priority INTEGER NOT NULL DEFAULT 0, -- Order of evaluation (e.g., 0=highest, process first match)
business_hours_id UUID REFERENCES BusinessHours(business_hours_id) ON DELETE SET NULL, -- Optional link to Business Hours definition
-- Or define specific days/times if not using BusinessHours:
days_of_week INTEGER[], -- Optional: [0-6] (Sun-Sat) - Applies if business_hours_id is NULL
start_time_local TIME NULL, -- Optional: Local start time - Applies if business_hours_id is NULL
end_time_local TIME NULL, -- Optional: Local end time - Applies if business_hours_id is NULL
is_default_rule BOOLEAN NOT NULL DEFAULT false, -- Catch-all if no other time rules match for the profile?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (profile_id, name),
UNIQUE (profile_id, priority), -- Enforce unique priority within a profile
CHECK (is_default_rule = true OR business_hours_id IS NOT NULL OR (days_of_week IS NOT NULL AND start_time_local IS NOT NULL AND end_time_local IS NOT NULL)) -- Ensure time rule is defined unless default
);
CREATE TABLE NotificationProfileEscalationSteps (
step_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
time_rule_id UUID NOT NULL REFERENCES NotificationProfileTimeRules(time_rule_id) ON DELETE CASCADE,
escalation_level INTEGER NOT NULL DEFAULT 0, -- 0=Initial Notification, 1=First Escalation, etc.
delay_after_previous_level_seconds INTEGER NOT NULL DEFAULT 0, -- Delay *after prior level triggers* before this one starts (0 for level 0)
repeat_interval_seconds INTEGER NULL, -- How often to re-notify *at this level* if unacknowledged (NULL = no repeats at this level)
max_repeats_at_level INTEGER NULL, -- Max number of repeat notifications for this specific level (NULL = repeat indefinitely or until resolved/acked)
notify_method_email BOOLEAN NOT NULL DEFAULT true, -- Send standard email notifications?
notify_method_sms BOOLEAN NOT NULL DEFAULT false, -- Send SMS notifications? (Requires consent check)
notify_method_system BOOLEAN NOT NULL DEFAULT true, -- Create internal CommandIT `Notifications` record?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (time_rule_id, escalation_level) -- Only one definition per level within a time rule
);
CREATE TABLE NotificationProfileStepRecipients (
step_recipient_id BIGSERIAL PRIMARY KEY,
step_id UUID NOT NULL REFERENCES NotificationProfileEscalationSteps(step_id) ON DELETE CASCADE,
user_id UUID NULL REFERENCES Users(user_id) ON DELETE CASCADE, -- Notify specific user
distribution_group_id UUID NULL REFERENCES DistributionGroups(distribution_group_id) ON DELETE CASCADE, -- Notify a group
recipient_type VARCHAR(10) GENERATED ALWAYS AS (CASE WHEN user_id IS NOT NULL THEN 'User' WHEN distribution_group_id IS NOT NULL THEN 'Group' ELSE NULL END) STORED, -- Calculated type
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (user_id IS NOT NULL OR distribution_group_id IS NOT NULL), -- Must specify a recipient
UNIQUE (step_id, user_id) WHERE user_id IS NOT NULL,
UNIQUE (step_id, distribution_group_id) WHERE distribution_group_id IS NOT NULL
);
CREATE TABLE ExecutionControlPolicies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
is_enabled BOOLEAN NOT NULL DEFAULT true,
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization',
scope_id UUID,
parent_policy_id UUID REFERENCES ExecutionControlPolicies(policy_id) ON DELETE SET NULL,
default_execution_action VARCHAR(10) NOT NULL DEFAULT 'Deny' CHECK (default_execution_action IN ('Allow', 'Deny')),
default_elevation_action VARCHAR(20) NOT NULL DEFAULT 'Deny' CHECK (default_elevation_action IN ('Allow', 'Deny', 'StandardUser')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT execcontrolpolicies_org_name_scope_unique UNIQUE NULLS NOT DISTINCT (org_id, name, scope_type, scope_id)
);
CREATE TABLE ExecutionControlRules (
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
policy_id UUID NOT NULL REFERENCES ExecutionControlPolicies(policy_id) ON DELETE CASCADE,
rule_order INTEGER NOT NULL DEFAULT 0,
action VARCHAR(20) NOT NULL CHECK (action IN ('Allow', 'Deny', 'Elevate', 'ElevateWithApproval')),
match_criteria JSONB NOT NULL,
elevation_justification_required BOOLEAN DEFAULT false,
elevation_approval_workflow_id UUID REFERENCES ApprovalWorkflowDefinitions(definition_id) ON DELETE SET NULL,
description TEXT,
is_enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE TagExecutionControlPolicyAssignments (
assignment_id BIGSERIAL PRIMARY KEY,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
execution_control_policy_id UUID NOT NULL REFERENCES ExecutionControlPolicies(policy_id) ON DELETE CASCADE,
is_enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 0,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE StorageControlPolicies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
is_enabled BOOLEAN NOT NULL DEFAULT true,
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization',
scope_id UUID,
parent_policy_id UUID REFERENCES StorageControlPolicies(policy_id) ON DELETE SET NULL,
default_action VARCHAR(10) NOT NULL DEFAULT 'Deny' CHECK (default_action IN ('Allow', 'Deny', 'ReadOnly')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT storagecontrolpolicies_org_name_scope_unique UNIQUE NULLS NOT DISTINCT (org_id, name, scope_type, scope_id)
);
CREATE TABLE StorageControlRules (
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
policy_id UUID NOT NULL REFERENCES StorageControlPolicies(policy_id) ON DELETE CASCADE,
rule_order INTEGER NOT NULL DEFAULT 0,
action VARCHAR(10) NOT NULL CHECK (action IN ('Allow', 'Deny', 'ReadOnly')),
match_criteria JSONB NOT NULL,
description TEXT,
is_enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE TagStorageControlPolicyAssignments (
assignment_id BIGSERIAL PRIMARY KEY,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
storage_control_policy_id UUID NOT NULL REFERENCES StorageControlPolicies(policy_id) ON DELETE CASCADE,
is_enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 0,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE PolicyAttestationAssignments (
assignment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
policy_document_id UUID NOT NULL REFERENCES Documents(document_id) ON DELETE RESTRICT,
required_revision_id UUID REFERENCES DocumentRevisions(revision_id) ON DELETE SET NULL,
assigned_to_user_id UUID REFERENCES Users(user_id) ON DELETE CASCADE,
assigned_to_role_id UUID REFERENCES Roles(role_id) ON DELETE CASCADE,
assigned_to_org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
assigned_to_tag_id UUID REFERENCES Tags(tag_id) ON DELETE CASCADE,
assigning_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
due_date DATE,
is_recurring BOOLEAN NOT NULL DEFAULT false,
recurrence_interval INTERVAL,
last_recurrence_at DATE,
is_active BOOLEAN NOT NULL DEFAULT true,
CHECK (assigned_to_user_id IS NOT NULL OR assigned_to_role_id IS NOT NULL OR assigned_to_org_id IS NOT NULL OR assigned_to_tag_id IS NOT NULL)
);
CREATE TABLE UserPolicyAttestations (
attestation_id BIGSERIAL PRIMARY KEY,
assignment_id UUID NOT NULL REFERENCES PolicyAttestationAssignments(assignment_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
attested_document_id UUID NOT NULL REFERENCES Documents(document_id) ON DELETE RESTRICT,
attested_revision_id UUID NOT NULL REFERENCES DocumentRevisions(revision_id) ON DELETE RESTRICT,
attested_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
ip_address INET,
user_agent TEXT,
UNIQUE (assignment_id, user_id)
);
-- =============================================
-- IT Service Management (ITSM)
-- =============================================
CREATE TABLE NotificationTemplates (
template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Defining Org (MSP)
name VARCHAR(255) NOT NULL, -- Unique name for the template within Org
description TEXT,
notification_type VARCHAR(20) NOT NULL CHECK (notification_type IN ('Email', 'System', 'SMS')),
usage_context VARCHAR(50), -- Optional: e.g., 'TicketStatusChange', 'ApprovalRequest'
subject_template TEXT NULL, -- Subject line (primarily for Email type), supports variables
body_template TEXT NOT NULL, -- Template body (HTML, Markdown, or Plain Text), supports variables
body_content_type VARCHAR(10) NOT NULL DEFAULT 'Html' CHECK (body_content_type IN ('Html', 'Markdown', 'Text')),
is_system BOOLEAN NOT NULL DEFAULT false, -- Is this a non-editable system default?
is_active BOOLEAN NOT NULL DEFAULT true, -- Can this template be selected/used?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT notificationtemplates_org_name_unique UNIQUE(org_id, name)
);
CREATE TABLE Alerts (
alert_id BIGSERIAL PRIMARY KEY, -- Referenced as BIGINT by Tickets
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Organization experiencing the alert
device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Source device, if applicable
monitoring_rule_id UUID NULL REFERENCES MonitoringRules(rule_id) ON DELETE SET NULL, -- Link to the rule that triggered this
compliance_rule_id UUID NULL REFERENCES ComplianceRules(rule_id) ON DELETE SET NULL, -- Link if triggered by compliance
alert_source VARCHAR(100), -- Name/Identifier of the source (e.g., Monitoring Rule Name, 'EmailTriage', 'EDR Integration')
alert_signature TEXT NULL, -- Unique signature identifying the specific alert condition (e.g., Device+Check+Severity). Used for state tracking.
alert_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the condition was initially detected/alert record created
severity VARCHAR(20) NOT NULL CHECK (severity IN ('Info', 'Warning', 'Critical', 'Ok')), -- Severity when created/updated
title VARCHAR(512) NOT NULL, -- Short summary of the alert condition
details JSONB, -- Structured details, metrics, event data, etc.
threat_details JSONB, -- Specific fields for EDR/Security threats
status VARCHAR(25) NOT NULL DEFAULT 'New' CHECK (status IN (
'New', -- Initial state, thresholds potentially not met
'Acknowledged', -- Human acknowledged, investigation may be ongoing
'TicketCreated', -- Ticket automatically created by rule
'ActionAttempted', -- Automated action (Script, etc.) initiated
'ActionFailed', -- Automated action failed
'ActionSucceeded', -- Automated action completed successfully
'Suppressed_Maint', -- Alert occurred but was suppressed due to Maintenance Window
'Resolved', -- Clearing condition met or manually resolved
'Closed', -- Final state after resolution/acknowledgement
'Unknown' -- Error state or indeterminate
)),
first_occurred_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this signature first triggered this active alert instance
last_occurred_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Timestamp condition last observed for this active instance
occurrence_count INTEGER NOT NULL DEFAULT 1, -- How many times re-triggered while active
action_taken_flag BOOLEAN NOT NULL DEFAULT false, -- Set true once a threshold action (CreateTicket, RunScript, etc.) executed for this instance
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Link if a ticket was created/linked
acknowledged_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
acknowledged_at TIMESTAMPTZ NULL,
resolved_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- User who manually resolved/cleared
resolved_at TIMESTAMPTZ NULL, -- When alert transitioned to Resolved state
closed_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
closed_at TIMESTAMPTZ NULL,
resolution_notes TEXT -- Optional notes on how it was resolved or why closed/suppressed
);
-- Index for finding active alerts by signature
CREATE INDEX idx_alerts_signature_status ON Alerts(alert_signature, status);
CREATE INDEX idx_alerts_last_occurred ON Alerts(last_occurred_at); -- Useful for cleanup/reporting
CREATE INDEX idx_alerts_org_status ON Alerts(org_id, status);
COMMENT ON COLUMN Alerts.alert_signature IS 'Unique signature identifying the specific alert condition (e.g., Device+Check+Severity). Used for state tracking and thresholds.';
COMMENT ON COLUMN Alerts.status IS 'Current lifecycle state of the alert instance.';
COMMENT ON COLUMN Alerts.first_occurred_at IS 'Timestamp when this specific alert signature first became active (entered ''New'' state).';
COMMENT ON COLUMN Alerts.last_occurred_at IS 'Timestamp when this specific alert signature was last seen while in an active state.';
COMMENT ON COLUMN Alerts.occurrence_count IS 'Number of times this specific alert signature was received while the alert instance was active.';
COMMENT ON COLUMN Alerts.action_taken_flag IS 'Set to true once a threshold-based action (like CreateTicket or RunScript) has been executed for this active alert instance to prevent duplicates.';
CREATE TABLE AlertActionsLog (
alert_action_log_id BIGSERIAL PRIMARY KEY,
alert_id BIGINT NOT NULL REFERENCES Alerts(alert_id) ON DELETE CASCADE, -- Link to the triggering alert
action_type VARCHAR(20) NOT NULL CHECK (action_type IN ('CreateTicket', 'RunScript')), -- Type of automated action attempted
monitoring_rule_id UUID NULL REFERENCES MonitoringRules(rule_id) ON DELETE SET NULL, -- Denormalized: Rule that defined the action
status VARCHAR(20) NOT NULL CHECK (status IN ('Attempted', 'Success', 'Failure')), -- Outcome of the action execution
execution_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the action was executed/attempted
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Link to the ticket created (if action_type='CreateTicket')
related_command_queue_id BIGINT NULL REFERENCES AgentCommandQueue(command_queue_id) ON DELETE SET NULL, -- Link to the command sent (if action_type='RunScript')
error_message TEXT NULL -- Details if status is 'Failure'
);
COMMENT ON TABLE AlertActionsLog IS 'Logs the execution and outcome of automated actions (Create Ticket, Run Script) triggered by Alerts based on Monitoring Rule definitions.';
CREATE TABLE BusinessHours (
business_hours_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL CHECK (type IN ('FixedTimeZone', 'LocationLocalTime')),
fixed_time_zone VARCHAR(100),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT businesshours_org_name_unique UNIQUE(org_id, name)
);
CREATE TABLE BusinessHoursSlots (
slot_id BIGSERIAL PRIMARY KEY,
business_hours_id UUID NOT NULL REFERENCES BusinessHours(business_hours_id) ON DELETE CASCADE,
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 0 AND day_of_week <= 6),
start_time_local TIME NOT NULL,
end_time_local TIME NOT NULL,
CHECK (end_time_local > start_time_local)
);
CREATE TABLE ServiceBoards (
board_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
default_assigned_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
default_ticket_template_id UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
enable_ai_triage BOOLEAN NOT NULL DEFAULT false,
inbound_email_address VARCHAR(255) UNIQUE,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT serviceboards_org_name_unique UNIQUE(org_id, name)
);
CREATE TABLE TicketStatuses (
status_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES ServiceBoards(board_id) ON DELETE CASCADE,
internal_name VARCHAR(100) NOT NULL,
external_name VARCHAR(100) NOT NULL,
is_resolved_flag BOOLEAN NOT NULL DEFAULT false,
is_closed_flag BOOLEAN NOT NULL DEFAULT false,
is_cancelled_flag BOOLEAN NOT NULL DEFAULT false,
pauses_sla_clock BOOLEAN NOT NULL DEFAULT false, -- Does this status pause SLA timers?
allow_time_entry BOOLEAN NOT NULL DEFAULT true,
is_default_status BOOLEAN NOT NULL DEFAULT false,
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT true,
email_notify_on_entry BOOLEAN NOT NULL DEFAULT false,
email_notify_template_id UUID REFERENCES NotificationTemplates(template_id) ON DELETE SET NULL,
internal_notify_on_entry BOOLEAN NOT NULL DEFAULT false,
internal_notify_template_id UUID REFERENCES NotificationTemplates(template_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT ticketstatuses_board_name_unique UNIQUE(board_id, internal_name)
);
CREATE TABLE TicketCategories (
category_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES ServiceBoards(board_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT ticketcategories_board_name_unique UNIQUE(board_id, name)
);
CREATE TABLE TicketTypes (
type_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES ServiceBoards(board_id) ON DELETE CASCADE,
category_id UUID REFERENCES TicketCategories(category_id) ON DELETE SET NULL,
name VARCHAR(100) NOT NULL,
ticket_nature VARCHAR(20) NOT NULL DEFAULT 'Incident' CHECK (ticket_nature IN ('Incident', 'ServiceRequest', 'Change', 'Problem', 'Other')),
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT tickettypes_board_name_unique UNIQUE(board_id, name)
);
CREATE TABLE TicketSubTypes (
sub_type_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type_id UUID NOT NULL REFERENCES TicketTypes(type_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT ticketsubtypes_type_name_unique UNIQUE(type_id, name)
);
CREATE TABLE TicketItems (
item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sub_type_id UUID NOT NULL REFERENCES TicketSubTypes(sub_type_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT ticketitems_subtype_name_unique UNIQUE(sub_type_id, name)
);
CREATE TABLE TicketImpact (
impact_id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
description TEXT
);
CREATE TABLE TicketUrgency (
urgency_id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
description TEXT
);
CREATE TABLE TicketPriority (
priority_id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
description TEXT,
is_default BOOLEAN NOT NULL DEFAULT false,
sort_order INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE TicketPriorityMatrix (
impact_id INTEGER NOT NULL REFERENCES TicketImpact(impact_id) ON DELETE CASCADE,
urgency_id INTEGER NOT NULL REFERENCES TicketUrgency(urgency_id) ON DELETE CASCADE,
priority_id INTEGER NOT NULL REFERENCES TicketPriority(priority_id) ON DELETE RESTRICT,
PRIMARY KEY (impact_id, urgency_id)
);
CREATE TABLE TicketTemplates (
template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization',
scope_id UUID,
parent_template_id UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
is_enabled BOOLEAN NOT NULL DEFAULT true,
available_to VARCHAR(10) NOT NULL DEFAULT 'TechOnly' CHECK (available_to IN ('TechOnly', 'UserOnly', 'All')),
required_role_ids JSONB,
target_board_id UUID NOT NULL REFERENCES ServiceBoards(board_id) ON DELETE RESTRICT,
default_ticket_data JSONB NOT NULL,
form_questions JSONB, -- For initial data gathering
default_tasks_data JSONB,
default_required_equipment_type_ids UUID[],
default_required_certification_ids UUID[],
initiation_chatbot_flow_id UUID REFERENCES ChatbotFlows(flow_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT tickettemplates_org_name_scope_unique UNIQUE NULLS NOT DISTINCT (org_id, name, scope_type, scope_id)
);
CREATE TABLE Skills (
skill_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT
);
CREATE TABLE UserSkills (
user_skill_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
skill_id UUID NOT NULL REFERENCES Skills(skill_id) ON DELETE CASCADE, -- Changed to FK
skill_level INTEGER CHECK (skill_level >= 1 AND skill_level <= 5),
UNIQUE (user_id, skill_id) -- Kept unique constraint
);
CREATE TABLE Tickets (
ticket_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
ticket_number VARCHAR(20) NOT NULL UNIQUE,
requester_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
requester_email VARCHAR(255),
callback_phone_number VARCHAR(50),
subject VARCHAR(255) NOT NULL,
description TEXT,
board_id UUID NOT NULL REFERENCES ServiceBoards(board_id) ON DELETE RESTRICT,
status_id UUID NOT NULL REFERENCES TicketStatuses(status_id) ON DELETE RESTRICT,
priority_id INTEGER REFERENCES TicketPriority(priority_id) ON DELETE SET NULL,
impact_id INTEGER REFERENCES TicketImpact(impact_id) ON DELETE SET NULL,
urgency_id INTEGER REFERENCES TicketUrgency(urgency_id) ON DELETE SET NULL,
category_id UUID REFERENCES TicketCategories(category_id) ON DELETE SET NULL,
type_id UUID NOT NULL REFERENCES TicketTypes(type_id) ON DELETE RESTRICT,
sub_type_id UUID REFERENCES TicketSubTypes(sub_type_id) ON DELETE SET NULL,
item_id UUID REFERENCES TicketItems(item_id) ON DELETE SET NULL,
assigned_to_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
primary_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
site_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
source VARCHAR(50) DEFAULT 'Agent',
budget_hours NUMERIC(10,2),
actual_hours NUMERIC(10,2) DEFAULT 0.00,
has_pending_field_request BOOLEAN DEFAULT false,
due_date TIMESTAMPTZ, -- Optional manual overall due date
calculated_response_due_utc TIMESTAMPTZ NULL, -- SLA Engine calculated response deadline
calculated_resolution_due_utc TIMESTAMPTZ NULL, -- SLA Engine calculated resolution deadline
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
first_response_at TIMESTAMPTZ,
resolved_at TIMESTAMPTZ,
closed_at TIMESTAMPTZ,
sla_timer_paused BOOLEAN NOT NULL DEFAULT false,
sla_paused_timestamp TIMESTAMPTZ,
response_sla_status VARCHAR(20) DEFAULT 'NA' CHECK (response_sla_status IN ('Met', 'Breached', 'AtRisk', 'Paused', 'NA')),
resolution_sla_status VARCHAR(20) DEFAULT 'NA' CHECK (resolution_sla_status IN ('Met', 'Breached', 'AtRisk', 'Paused', 'NA')),
change_request_id UUID REFERENCES ChangeRequests(change_request_id) ON DELETE SET NULL,
problem_id UUID REFERENCES Problems(problem_id) ON DELETE SET NULL,
service_catalog_item_id UUID REFERENCES ServiceCatalogItems(service_catalog_item_id) ON DELETE SET NULL,
originating_alert_id BIGINT REFERENCES Alerts(alert_id) ON DELETE SET NULL,
originating_sales_order_id UUID REFERENCES SalesOrders(portal_order_id) ON DELETE SET NULL,
related_project_id UUID REFERENCES Projects(project_id) ON DELETE SET NULL,
scheduled_start_time_utc TIMESTAMPTZ,
scheduled_end_time_utc TIMESTAMPTZ,
is_major_incident BOOLEAN NOT NULL DEFAULT false,
custom_fields JSONB,
ai_summary TEXT,
form_answers JSONB
);
CREATE TABLE RelatedTickets (
ticket_id_a BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
ticket_id_b BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
relationship_type VARCHAR(20) NOT NULL CHECK (relationship_type IN ('Duplicate', 'Related', 'CausedBy', 'ParentOf', 'ChildOf')),
linked_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
linked_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
PRIMARY KEY (ticket_id_a, ticket_id_b),
CHECK (ticket_id_a < ticket_id_b) -- Ensure canonical ordering to prevent duplicate pairs
);
CREATE TABLE TicketStatusHistory (
status_history_id BIGSERIAL PRIMARY KEY,
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
old_status_id UUID REFERENCES TicketStatuses(status_id) ON DELETE SET NULL,
new_status_id UUID NOT NULL REFERENCES TicketStatuses(status_id) ON DELETE RESTRICT,
changed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
changed_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL
);
CREATE TABLE TicketUpdates (
update_id BIGSERIAL PRIMARY KEY,
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- User or AI creating the update
timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the update occurred
body TEXT NOT NULL,
is_internal_note BOOLEAN NOT NULL DEFAULT false,
note_type VARCHAR(30) NULL CHECK (note_type IN ('Standard', 'Internal', 'EmailLog', 'SystemEvent', 'AiSummary')), -- Categorizes the update
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -- When the record was created
-- updated_at can be added if edits are allowed
);
COMMENT ON TABLE TicketUpdates IS 'Stores notes, replies, and logs associated with a ticket.';
COMMENT ON COLUMN TicketUpdates.note_type IS 'Categorizes the type of update (Standard user note, Internal tech note, Log of processed Email, Automated system event, AI generated summary, etc.). Helps distinguish AI-logged emails.';
CREATE INDEX idx_ticketupdates_note_type ON TicketUpdates(note_type);
CREATE TABLE TicketUpdateAttachments (
ticket_update_attachment_id BIGSERIAL PRIMARY KEY,
ticket_update_id BIGINT NOT NULL REFERENCES TicketUpdates(update_id) ON DELETE CASCADE, -- Link to the specific note/update
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE, -- Link to the file attachment
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (ticket_update_id, attachment_id) -- Prevent linking same file twice to same note
);
COMMENT ON TABLE TicketUpdateAttachments IS 'Links file attachments to specific TicketUpdates (e.g., attachments from a chat transcript saved as a note).';
CREATE TABLE TicketTasks (
ticket_task_id BIGSERIAL PRIMARY KEY,
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
template_task_id VARCHAR(100), -- Optional identifier linking back to task definition in TicketTemplate
sequence_order INTEGER NOT NULL DEFAULT 0, -- Display/execution order within the ticket
depends_on_ticket_task_id BIGINT NULL REFERENCES TicketTasks(ticket_task_id) ON DELETE SET NULL, -- Prerequisite task within the same ticket
description TEXT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'Pending' CHECK (status IN ('Pending', 'InProgress', 'AwaitingApproval', 'Complete', 'Skipped', 'Failed')),
requires_approval BOOLEAN NOT NULL DEFAULT false, -- Does this task trigger an approval?
approval_request_id UUID REFERENCES ApprovalRequests(approval_request_id) ON DELETE SET NULL, -- Link to the triggered ApprovalRequest
assigned_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- Specific user assigned (if manual)
due_date TIMESTAMPTZ,
is_checklist_item BOOLEAN NOT NULL DEFAULT false, -- Simple check-off vs. substantive work?
execution_type VARCHAR(20) NOT NULL DEFAULT 'Manual' CHECK (execution_type IN ('Manual', 'Automation')),
automation_script_id UUID REFERENCES Scripts(script_id) ON DELETE SET NULL, -- Link if execution_type='Automation' (simple script)
automation_workflow_id UUID REFERENCES Automations(automation_id) ON DELETE SET NULL, -- Link if execution_type='Automation' (complex workflow)
automation_trigger VARCHAR(50), -- Condition triggering automation ('OnTicketCreation', 'OnTaskStatusChange', 'Manual')
input_variables JSONB, -- Variables passed to automation script/workflow
requirements_definition JSONB, -- Prerequisites for automated tasks
estimated_duration_seconds INTEGER, -- Estimated tech effort
completion_notes TEXT,
completed_at TIMESTAMPTZ,
created_by_ai BOOLEAN NOT NULL DEFAULT false, -- Was this task suggested/created by AI?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated by trigger
);
CREATE TABLE TicketAffectedCIs (
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
ci_type VARCHAR(50) NOT NULL,
ci_id VARCHAR(36) NOT NULL,
relationship_notes TEXT,
PRIMARY KEY (ticket_id, ci_type, ci_id)
);
CREATE TABLE TicketRequiredSkills (
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
skill_id UUID NOT NULL REFERENCES Skills(skill_id) ON DELETE CASCADE,
PRIMARY KEY (ticket_id, skill_id)
);
CREATE TABLE TicketProductsUsed (
ticket_product_id BIGSERIAL PRIMARY KEY,
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE RESTRICT,
quantity NUMERIC(19,4) NOT NULL,
serial_number VARCHAR(255), -- If product is serialized
unit_price NUMERIC(19,4), -- Price used for this instance (may override product default)
is_billable BOOLEAN DEFAULT true,
inventory_transaction_id BIGINT REFERENCES InventoryTransactions(transaction_id) ON DELETE SET NULL, -- Link to stock deduction
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
added_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL
);
CREATE TABLE FieldServiceRequests (
field_service_request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE RESTRICT,
requesting_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
request_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
reason TEXT,
requested_start_datetime TIMESTAMPTZ,
requested_end_datetime TIMESTAMPTZ,
requested_time_preference VARCHAR(50),
estimated_duration_seconds INTEGER,
urgency_level INTEGER,
notes_for_dispatcher TEXT,
intended_assignee_type VARCHAR(20) DEFAULT 'InternalUser' CHECK (intended_assignee_type IN ('InternalUser', 'ExternalVendor')),
intended_vendor_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE SET NULL,
status VARCHAR(20) DEFAULT 'Pending' CHECK (status IN ('Pending', 'Scheduled', 'Denied', 'Cancelled', 'PendingReview')),
dispatcher_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
handling_timestamp TIMESTAMPTZ,
resulting_schedule_entry_id BIGINT REFERENCES TechnicianSchedules(schedule_entry_id) ON DELETE SET NULL,
denial_reason TEXT
);
CREATE TABLE FieldServiceRequestRequiredEquipment (
field_service_request_id UUID NOT NULL REFERENCES FieldServiceRequests(field_service_request_id) ON DELETE CASCADE,
equipment_type_id INTEGER NOT NULL REFERENCES EquipmentTypes(equipment_type_id) ON DELETE CASCADE,
notes TEXT, -- e.g., "Need 8ft Ladder"
PRIMARY KEY (field_service_request_id, equipment_type_id)
);
CREATE TABLE LocationRequiredEquipment (
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE,
equipment_type_id INTEGER NOT NULL REFERENCES EquipmentTypes(equipment_type_id) ON DELETE CASCADE,
notes TEXT,
PRIMARY KEY (location_id, equipment_type_id)
);
CREATE TABLE FieldServiceRequestRequiredCertifications (
field_service_request_id UUID NOT NULL REFERENCES FieldServiceRequests(field_service_request_id) ON DELETE CASCADE,
certification_id UUID NOT NULL REFERENCES Certifications(certification_id) ON DELETE CASCADE,
PRIMARY KEY (field_service_request_id, certification_id)
);
CREATE TABLE TravelBillingPolicies (
travel_billing_policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP defining the policy
name VARCHAR(150) NOT NULL, -- e.g., "Standard Client Travel", "Project Onsite Travel (No First/Last)"
is_default_for_org BOOLEAN NOT NULL DEFAULT false, -- Only one default policy per Org allowed
billing_method VARCHAR(20) NOT NULL CHECK (billing_method IN ('Time', 'Mileage', 'FlatRate', 'TimeOrMileageGreater', 'TimeAndMileage', 'None')),
travel_work_type_id UUID REFERENCES WorkTypes(work_type_id) ON DELETE SET NULL, -- Work Type for billing travel TIME
mileage_product_id UUID REFERENCES Products(product_id) ON DELETE SET NULL, -- Product for billing MILEAGE
flat_rate_product_id UUID REFERENCES Products(product_id) ON DELETE SET NULL, -- Product for billing FLAT RATE
flat_rate_amount_override NUMERIC(19,4), -- Optional override if flat_rate_product_id is NULL
first_trip_rule VARCHAR(25) DEFAULT 'Billable' CHECK (first_trip_rule IN ('Billable', 'NonBillable', 'BillableAfterThreshold', 'DifferentRate')),
first_trip_threshold_km INTEGER,
first_trip_threshold_minutes INTEGER,
first_trip_rate_modifier VARCHAR(50),
last_trip_rule VARCHAR(25) DEFAULT 'Billable' CHECK (last_trip_rule IN ('Billable', 'NonBillable', 'BillableAfterThreshold', 'DifferentRate')),
last_trip_threshold_km INTEGER,
last_trip_threshold_minutes INTEGER,
last_trip_rate_modifier VARCHAR(50),
inter_site_travel_rule VARCHAR(25) DEFAULT 'BillToDestination' CHECK (inter_site_travel_rule IN ('BillToDestination', 'BillToOrigin', 'SplitEqually', 'NonBillable')),
distance_unit VARCHAR(2) DEFAULT 'km' CHECK (distance_unit IN ('km', 'mi')),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name)
);
CREATE TABLE Problems (
problem_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org experiencing the problem
problem_number VARCHAR(20) NOT NULL UNIQUE, -- Human-readable sequence
title VARCHAR(255) NOT NULL, -- Summary of the underlying problem
description TEXT, -- Detailed description of symptoms, scope, impact, history
status VARCHAR(50) NOT NULL DEFAULT 'Open' CHECK (status IN ('Open', 'Investigating', 'RootCauseIdentified', 'WorkaroundAvailable', 'SolutionPendingChange', 'Resolved', 'Closed')),
priority_id INTEGER NULL REFERENCES TicketPriority(priority_id) ON DELETE SET NULL, -- Problem priority
urgency_id INTEGER NULL REFERENCES TicketUrgency(urgency_id) ON DELETE SET NULL, -- Problem urgency
impact_id INTEGER NULL REFERENCES TicketImpact(impact_id) ON DELETE SET NULL, -- Problem impact
reported_date TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When problem record created
assigned_to_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- Problem manager/investigator
root_cause_summary TEXT, -- Summary of the identified root cause
workaround_details TEXT, -- Description of any temporary workaround
permanent_solution_details TEXT, -- Description of the permanent solution
related_change_request_id UUID REFERENCES ChangeRequests(change_request_id) ON DELETE SET NULL, -- Link to RFC implementing solution
inbound_email_address VARCHAR(255) UNIQUE NULL;
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
resolved_at TIMESTAMPTZ, -- When the permanent solution was confirmed implemented
closed_at TIMESTAMPTZ -- When the problem record was formally closed
);
CREATE TABLE ProblemIncidents ( -- M2M linking Problems to related incident Tickets
problem_id UUID NOT NULL REFERENCES Problems(problem_id) ON DELETE CASCADE,
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
linked_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
linked_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
PRIMARY KEY (problem_id, ticket_id)
);
CREATE TABLE ProblemUpdates (
problem_update_id BIGSERIAL PRIMARY KEY,
problem_id UUID NOT NULL REFERENCES Problems(problem_id) ON DELETE CASCADE,
user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- User or AI creating the update
timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
body TEXT NOT NULL, -- Content of the note or processed email body
note_type VARCHAR(30) NULL CHECK (note_type IN ('Standard', 'Internal', 'EmailLog', 'SystemEvent')), -- Categorizes the update
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_problemupdates_problem_id ON ProblemUpdates(problem_id, timestamp DESC);
COMMENT ON TABLE ProblemUpdates IS 'Stores notes, logs of emails received via problem email address, and other updates related to a problem record.';
CREATE TABLE ProblemUpdateAttachments (
problem_update_attachment_id BIGSERIAL PRIMARY KEY,
problem_update_id BIGINT NOT NULL REFERENCES ProblemUpdates(problem_update_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (problem_update_id, attachment_id)
);
COMMENT ON TABLE ProblemUpdateAttachments IS 'Links file attachments (stored in Attachments table) to specific ProblemUpdates.';
CREATE TABLE ChangeRequests (
change_request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
request_number VARCHAR(20) NOT NULL UNIQUE,
title VARCHAR(255) NOT NULL,
description TEXT, -- Detailed description of the change
justification TEXT, -- Reason for the change
implementation_plan TEXT NULL, -- Steps to implement
test_plan TEXT NULL, -- How success will be tested
backout_plan TEXT, -- Steps to revert if needed
requester_user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE RESTRICT,
change_type VARCHAR(20) NOT NULL DEFAULT 'Normal' CHECK (change_type IN ('Standard', 'Normal', 'Emergency')),
priority_id INTEGER NULL REFERENCES TicketPriority(priority_id) ON DELETE SET NULL,
risk_level VARCHAR(20) CHECK (risk_level IN ('Low', 'Medium', 'High', 'Critical')),
impact_level VARCHAR(20) CHECK (impact_level IN ('Low', 'Medium', 'High', 'Critical')),
status VARCHAR(20) NOT NULL DEFAULT 'Pending' CHECK (status IN ('Pending', 'Assessing', 'PendingApproval', 'Approved', 'Rejected', 'Scheduled', 'Implementing', 'Completed', 'Failed', 'Cancelled')),
approval_request_id UUID REFERENCES ApprovalRequests(approval_request_id) ON DELETE SET NULL,
project_id UUID NULL REFERENCES Projects(project_id) ON DELETE SET NULL,
scheduled_start_date TIMESTAMPTZ,
scheduled_end_date TIMESTAMPTZ,
actual_start_date TIMESTAMPTZ,
actual_end_date TIMESTAMPTZ,
inbound_email_address VARCHAR(255) UNIQUE NULL;
review_notes TEXT, -- For Post-Implementation Review (PIR)
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
COMMENT ON COLUMN ChangeRequests.inbound_email_address IS 'Unique email address ({guid}@commandit.net) for sending updates directly to this change request record.';
CREATE TABLE ChangeRequestUpdates (
change_update_id BIGSERIAL PRIMARY KEY,
change_request_id UUID NOT NULL REFERENCES ChangeRequests(change_request_id) ON DELETE CASCADE,
user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- User or AI creating the update
timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
body TEXT NOT NULL, -- Content of the note or processed email body
note_type VARCHAR(30) NULL CHECK (note_type IN ('Standard', 'Internal', 'EmailLog', 'SystemEvent')), -- Categorizes the update
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_changeupdates_change_id ON ChangeRequestUpdates(change_request_id, timestamp DESC);
COMMENT ON TABLE ChangeRequestUpdates IS 'Stores notes, logs of emails received via change request email address, and other updates related to a change request record.';
CREATE TABLE ChangeRequestUpdateAttachments (
change_update_attachment_id BIGSERIAL PRIMARY KEY,
change_update_id BIGINT NOT NULL REFERENCES ChangeRequestUpdates(change_update_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (change_update_id, attachment_id)
);
COMMENT ON TABLE ChangeRequestUpdateAttachments IS 'Links file attachments (stored in Attachments table) to specific ChangeRequestUpdates.';
CREATE TABLE BlackoutPeriods (
blackout_period_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org defining the blackout
name VARCHAR(150) NOT NULL, -- e.g., "End of Month Freeze", "Holiday Change Freeze"
reason TEXT,
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NOT NULL,
affects_all_cis BOOLEAN NOT NULL DEFAULT true, -- Does it apply globally or to specific items?
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CHECK (end_time_utc > start_time_utc)
-- Consider M2M tables to link BlackoutPeriods to specific Orgs, Locations, Device Types, or CIs if affects_all_cis is false.
);
CREATE TABLE ChangeRequestAffectedCIs (
affected_ci_id BIGSERIAL PRIMARY KEY,
change_request_id UUID NOT NULL REFERENCES ChangeRequests(change_request_id) ON DELETE CASCADE,
ci_type VARCHAR(50) NOT NULL,
ci_id VARCHAR(36) NOT NULL,
relationship_notes TEXT,
UNIQUE(change_request_id, ci_type, ci_id)
);
CREATE TABLE RecurringTicketSchedules (
schedule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
definer_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
ticket_template_id UUID NOT NULL REFERENCES TicketTemplates(template_id) ON DELETE RESTRICT,
template_variable_values JSONB,
target_client_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
schedule_type VARCHAR(20) NOT NULL CHECK (schedule_type IN ('Monthly', 'Weekly', 'Daily')),
interval INTEGER NOT NULL DEFAULT 1 CHECK (interval > 0),
days_of_week INTEGER[],
monthly_occurrence_type VARCHAR(30),
monthly_day_of_month INTEGER,
monthly_ordinal INTEGER,
monthly_day_specifier VARCHAR(20),
scheduled_time_utc TIME NOT NULL,
next_run_at_utc TIMESTAMPTZ,
last_run_at_utc TIMESTAMPTZ,
last_generated_ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
error_message TEXT,
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization',
scope_id UUID,
parent_schedule_id UUID REFERENCES RecurringTicketSchedules(schedule_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CHECK ( /* Ensure valid schedule combinations */ ),
UNIQUE(definer_org_id, name, scope_type, scope_id)
);
CREATE TABLE RecurringScheduleDeviceTargets (
schedule_id UUID NOT NULL REFERENCES RecurringTicketSchedules(schedule_id) ON DELETE CASCADE,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (schedule_id, device_id)
);
CREATE TABLE RecurringScheduleTagTargets (
schedule_id UUID NOT NULL REFERENCES RecurringTicketSchedules(schedule_id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (schedule_id, tag_id)
);
CREATE TABLE ServiceCatalogItems (
service_catalog_item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
icon_url TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
display_price NUMERIC(19,4),
estimated_delivery_time VARCHAR(50),
fulfillment_ticket_template_id UUID NOT NULL REFERENCES TicketTemplates(template_id) ON DELETE RESTRICT,
requires_approval BOOLEAN NOT NULL DEFAULT false,
approval_workflow_definition_id UUID REFERENCES ApprovalWorkflowDefinitions(definition_id) ON DELETE SET NULL,
initiation_chatbot_flow_id UUID REFERENCES ChatbotFlows(flow_id) ON DELETE SET NULL,
available_to_all_orgs BOOLEAN NOT NULL DEFAULT true,
available_to_org_ids UUID[],
requires_specific_roles BOOLEAN NOT NULL DEFAULT false,
available_to_role_ids UUID[],
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT servicecatalogitems_org_name_unique UNIQUE NULLS NOT DISTINCT (org_id, name)
);
CREATE TABLE ServiceCatalogItemKBDocs (
service_catalog_item_id UUID NOT NULL REFERENCES ServiceCatalogItems(service_catalog_item_id) ON DELETE CASCADE,
document_id UUID NOT NULL REFERENCES Documents(document_id) ON DELETE CASCADE,
relationship_type VARCHAR(50) DEFAULT 'RelatedInfo',
PRIMARY KEY (service_catalog_item_id, document_id)
);
CREATE TABLE DeviceIntake (
intake_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE RESTRICT,
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE RESTRICT,
device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
received_item_description TEXT NOT NULL,
customer_reported_issue TEXT,
arrival_method VARCHAR(20) NOT NULL CHECK (arrival_method IN ('MailInCourier', 'CustomerDropOff', 'FieldDropOff', 'NewStock', 'Other')),
received_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
received_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
inbound_courier_name VARCHAR(100),
inbound_tracking_number VARCHAR(100),
sender_details JSONB,
dropped_off_by_name VARCHAR(255),
dropped_off_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
status VARCHAR(30) NOT NULL DEFAULT 'Received' CHECK (status IN ('Received', 'IntakeComplete', 'OnBench', 'AwaitingParts', 'ReadyForReturn', 'Returned', 'Disposed')),
notes TEXT,
return_method VARCHAR(20) CHECK (return_method IN ('MailOutCourier', 'CustomerPickup', 'FieldPickup', 'Disposed', 'Other')),
returned_at TIMESTAMPTZ,
returned_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
outbound_courier_name VARCHAR(100),
outbound_tracking_number VARCHAR(100),
shipping_address_details JSONB,
picked_up_by_name VARCHAR(255),
picked_up_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE WorkBenches (
bench_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
specialized_skills TEXT[], -- Match TechnicianSkills? Or EquipmentTypes? Prefer EquipmentTypes
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE(location_id, name)
);
CREATE TABLE BenchAssignments (
bench_assignment_id BIGSERIAL PRIMARY KEY,
bench_id UUID NOT NULL REFERENCES WorkBenches(bench_id) ON DELETE RESTRICT,
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
originating_intake_id UUID REFERENCES DeviceIntake(intake_id) ON DELETE SET NULL,
scheduled_start_time TIMESTAMPTZ NOT NULL,
scheduled_end_time TIMESTAMPTZ NOT NULL,
estimated_bench_duration_seconds INTEGER CHECK (estimated_bench_duration_seconds >= 0),
actual_start_time TIMESTAMPTZ,
actual_end_time TIMESTAMPTZ,
status VARCHAR(30) NOT NULL DEFAULT 'Scheduled' CHECK (status IN ('Scheduled', 'InProgress', 'OnHoldAwaitingParts', 'Completed', 'Cancelled')),
assigned_technician_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CHECK (scheduled_end_time > scheduled_start_time)
-- Constraint prevent_bench_overlaps EXCLUDE USING GIST (...) -- Requires GIST index setup
);
CREATE TABLE DistributionGroups (
distribution_group_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name)
);
CREATE TABLE DistributionGroupMembers (
distribution_group_member_id BIGSERIAL PRIMARY KEY,
distribution_group_id UUID NOT NULL REFERENCES DistributionGroups(distribution_group_id) ON DELETE CASCADE,
user_id UUID REFERENCES Users(user_id) ON DELETE CASCADE,
contact_id UUID REFERENCES Contacts(contact_id) ON DELETE CASCADE,
email_address VARCHAR(255),
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (user_id IS NOT NULL OR contact_id IS NOT NULL OR email_address IS NOT NULL),
UNIQUE (distribution_group_id, user_id) WHERE user_id IS NOT NULL,
UNIQUE (distribution_group_id, contact_id) WHERE contact_id IS NOT NULL,
UNIQUE (distribution_group_id, email_address) WHERE email_address IS NOT NULL
);
CREATE TABLE TicketFollowers (
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
followed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
notification_level VARCHAR(20) DEFAULT 'AllUpdates',
PRIMARY KEY (ticket_id, user_id)
);
CREATE TABLE FieldServiceRequestRequiredEquipment (
field_service_request_id UUID NOT NULL REFERENCES FieldServiceRequests(field_service_request_id) ON DELETE CASCADE,
equipment_type_id INTEGER NOT NULL REFERENCES EquipmentTypes(equipment_type_id) ON DELETE CASCADE,
notes TEXT, -- Specific notes, e.g., "Need 8ft Ladder"
PRIMARY KEY (field_service_request_id, equipment_type_id)
);
CREATE TABLE Certifications ( -- New table for Certifications
certification_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(150) NOT NULL UNIQUE,
description TEXT,
issuing_body VARCHAR(100),
validity_period_months INTEGER -- NULL if no expiry
);
CREATE TABLE UserCertifications ( -- New M2M table
user_certification_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
certification_id UUID NOT NULL REFERENCES Certifications(certification_id) ON DELETE CASCADE,
issue_date DATE,
expiry_date DATE, -- Automatically calculated or entered
certification_number VARCHAR(100),
verified_at TIMESTAMPTZ,
verified_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
UNIQUE (user_id, certification_id)
);
CREATE TABLE FieldServiceRequestRequiredCertifications (
field_service_request_id UUID NOT NULL REFERENCES FieldServiceRequests(field_service_request_id) ON DELETE CASCADE,
certification_id UUID NOT NULL REFERENCES Certifications(certification_id) ON DELETE CASCADE,
PRIMARY KEY (field_service_request_id, certification_id)
);
CREATE TABLE LocationRequiredCertifications (
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE,
certification_id UUID NOT NULL REFERENCES Certifications(certification_id) ON DELETE CASCADE,
PRIMARY KEY (location_id, certification_id)
);
CREATE TABLE FormTemplates (
form_template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Defining Org (NULL for system)
name VARCHAR(255) NOT NULL,
description TEXT,
structure_definition JSONB NOT NULL, -- JSON defining form fields, types, options, validation, potentially mapping to controls
usage_context TEXT[], -- Optional tags indicating where form is typically used (e.g., ['SiteSurvey', 'HIPAA_Assessment', 'OnboardingChecklist'])
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE NULLS NOT DISTINCT (org_id, name)
);
CREATE TABLE FormInstances (
form_instance_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
form_template_id UUID NOT NULL REFERENCES FormTemplates(form_template_id) ON DELETE RESTRICT,
ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Link to Ticket
schedule_entry_id BIGINT REFERENCES TechnicianSchedules(schedule_entry_id) ON DELETE SET NULL, -- Link to Schedule Entry
org_id UUID REFERENCES Organizations(org_id) ON DELETE SET NULL, -- Context Org (e.g., Client for assessment)
location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL, -- Context Location
device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL, -- Context Device
technician_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- User who filled it out
submission_timestamp TIMESTAMPTZ NULL, -- When form was marked as submitted/completed
status VARCHAR(20) DEFAULT 'Pending' CHECK (status IN ('Pending', 'InProgress', 'Completed', 'RequiresReview')),
form_data JSONB, -- JSON storing the actual filled values { "field_id": "value", ... }
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
-- =============================================
-- Inventory & Procurement
-- =============================================
-- Products defined in Billing section
CREATE TABLE Warehouses (
warehouse_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Owning Org (MSP)
name VARCHAR(100) NOT NULL, -- e.g., 'Main Warehouse', 'Tech Van - John Doe'
location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL, -- Physical location if applicable
warehouse_type VARCHAR(20) NOT NULL CHECK (warehouse_type IN ('Main', 'Van', 'SiteCache', 'Virtual')),
technician_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- Link if Van type
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name),
UNIQUE (technician_user_id) WHERE warehouse_type = 'Van' AND technician_user_id IS NOT NULL -- Only one van per tech
);
CREATE TABLE InventoryStockLevels (
stock_level_id BIGSERIAL PRIMARY KEY,
product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE CASCADE,
warehouse_id UUID NOT NULL REFERENCES Warehouses(warehouse_id) ON DELETE CASCADE, -- Changed from location_id
quantity_on_hand NUMERIC(19,4) NOT NULL DEFAULT 0.00,
quantity_allocated NUMERIC(19,4) NOT NULL DEFAULT 0.00,
quantity_on_order NUMERIC(19,4) NOT NULL DEFAULT 0.00,
reorder_point NUMERIC(19,4),
minimum_level NUMERIC(19,4),
maximum_level NUMERIC(19,4),
last_updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (product_id, warehouse_id)
);
CREATE TABLE InventoryTransactions (
transaction_id BIGSERIAL PRIMARY KEY,
product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE RESTRICT,
warehouse_id UUID NOT NULL REFERENCES Warehouses(warehouse_id) ON DELETE RESTRICT, -- Source/Dest Warehouse
ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Optional link to ticket where used
schedule_entry_id BIGINT REFERENCES TechnicianSchedules(schedule_entry_id) ON DELETE SET NULL, -- Optional link to schedule entry
technician_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- User performing transaction
quantity_changed NUMERIC(19,4) NOT NULL, -- Negative for usage/out, Positive for receive/in
serial_number VARCHAR(255), -- If serialized item
transaction_type VARCHAR(20) NOT NULL CHECK (transaction_type IN ('UsedOnTicket', 'Received', 'TransferOut', 'TransferIn', 'AdjustUp', 'AdjustDown', 'InitialStock')),
related_transaction_id BIGINT REFERENCES InventoryTransactions(transaction_id) ON DELETE SET NULL, -- Link for Transfers (e.g., TransferOut links to TransferIn)
destination_warehouse_id UUID NULL REFERENCES Warehouses(warehouse_id) ON DELETE RESTRICT, -- For TransferOut/In
timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
notes TEXT
);
CREATE TABLE ItemReceipts (
receipt_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE RESTRICT,
receipt_number VARCHAR(50) NOT NULL UNIQUE,
receipt_date DATE NOT NULL DEFAULT CURRENT_DATE,
purchase_order_id UUID REFERENCES PurchaseOrders(purchase_order_id) ON DELETE SET NULL,
vendor_org_id UUID REFERENCES Organizations(org_id) ON DELETE SET NULL,
packing_slip_number VARCHAR(100),
notes TEXT,
received_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE ItemReceiptLines (
receipt_line_id BIGSERIAL PRIMARY KEY,
receipt_id UUID NOT NULL REFERENCES ItemReceipts(receipt_id) ON DELETE CASCADE,
po_line_item_id BIGINT REFERENCES PurchaseOrderLineItems(po_line_item_id) ON DELETE SET NULL,
product_id UUID REFERENCES Products(product_id) ON DELETE RESTRICT,
description TEXT NOT NULL,
quantity_received NUMERIC(19,4) NOT NULL,
unit_cost NUMERIC(19,4) NULL, -- Actual cost of received item (nullable)
currency_code VARCHAR(3) NULL, -- Currency of the unit_cost (nullable)
is_serialized BOOLEAN NOT NULL DEFAULT false,
notes TEXT,
sequence INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE ReceivedItemSerials (
serial_instance_id BIGSERIAL PRIMARY KEY,
receipt_line_id BIGINT NOT NULL REFERENCES ItemReceiptLines(receipt_line_id) ON DELETE CASCADE,
serial_number VARCHAR(255) NOT NULL UNIQUE, -- The actual serial number received
received_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE PurchaseOrders (
purchase_order_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP Org placing order
vendor_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE RESTRICT, -- Vendor being ordered from
po_number VARCHAR(50) NOT NULL UNIQUE, -- Generated sequence
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
expected_delivery_date DATE,
shipping_address_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL, -- Where items ship to
billing_address_details JSONB, -- MSP billing address info
status VARCHAR(20) DEFAULT 'Draft' CHECK (status IN ('Draft', 'Ordered', 'PartialReceipt', 'Received', 'Cancelled', 'Closed')),
currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD', -- Currency for this PO
subtotal_amount NUMERIC(19,4) DEFAULT 0.00,
tax_amount NUMERIC(19,4) DEFAULT 0.00,
shipping_cost NUMERIC(19,4) DEFAULT 0.00,
total_amount NUMERIC(19,4) DEFAULT 0.00,
notes TEXT,
created_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE PurchaseOrderLineItems (
po_line_item_id BIGSERIAL PRIMARY KEY,
purchase_order_id UUID NOT NULL REFERENCES PurchaseOrders(purchase_order_id) ON DELETE CASCADE,
product_id UUID REFERENCES Products(product_id) ON DELETE RESTRICT,
description TEXT NOT NULL,
quantity_ordered NUMERIC(19,4) NOT NULL,
unit_cost NUMERIC(19,4) NOT NULL,
line_total NUMERIC(19,4) NOT NULL, -- quantity_ordered * unit_cost
quantity_received NUMERIC(19,4) DEFAULT 0.00,
notes TEXT,
sequence INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE SalesOrders (
portal_order_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org placing the order
managing_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP Org fulfilling the order
order_number VARCHAR(50) NOT NULL UNIQUE,
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
status VARCHAR(30) DEFAULT 'PendingApproval' CHECK (status IN ('Draft', 'PendingApproval', 'Approved', 'PendingProcurement', 'PartialShipment', 'Shipped', 'Invoiced', 'Completed', 'Cancelled')),
agreement_id UUID REFERENCES Agreements(agreement_id) ON DELETE SET NULL,
quote_id UUID NULL, -- FK constraint added below, links to the originating quote
requester_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
shipping_address_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
billing_address_details JSONB,
currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD', -- Currency for all monetary amounts on this order
subtotal_amount NUMERIC(19,4) DEFAULT 0.00,
tax_amount NUMERIC(19,4) DEFAULT 0.00,
shipping_amount NUMERIC(19,4) DEFAULT 0.00,
total_amount NUMERIC(19,4) DEFAULT 0.00,
notes TEXT, -- Notes visible to client
internal_notes TEXT, -- Internal fulfillment notes
approval_request_id UUID REFERENCES ApprovalRequests(approval_request_id) ON DELETE SET NULL,
related_project_id UUID REFERENCES Projects(project_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT fk_salesorders_quote_id
FOREIGN KEY (quote_id)
REFERENCES Quotes(quote_id)
ON DELETE SET NULL
);
COMMENT ON COLUMN SalesOrders.quote_id IS 'Link to the originating quote that this sales order was converted from, if applicable.';
CREATE TABLE SalesOrderLineItems (
order_line_item_id BIGSERIAL PRIMARY KEY,
portal_order_id UUID NOT NULL REFERENCES SalesOrders(portal_order_id) ON DELETE CASCADE,
product_id UUID REFERENCES Products(product_id) ON DELETE RESTRICT,
description TEXT NOT NULL,
quantity NUMERIC(19,4) NOT NULL,
unit_price NUMERIC(19,4) NOT NULL, -- Price for this specific line
currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD', -- Currency of unit_price and line_total
line_total NUMERIC(19,4) NOT NULL, -- quantity * unit_price
parent_line_item_id BIGINT REFERENCES SalesOrderLineItems(order_line_item_id) ON DELETE CASCADE, -- For bundle components
is_bundle_component BOOLEAN NOT NULL DEFAULT false,
procurement_status VARCHAR(30) DEFAULT 'NotNeeded' CHECK (procurement_status IN ('NotNeeded', 'Required', 'Ordered', 'Received', 'Allocated')),
quantity_procured NUMERIC(19,4) DEFAULT 0.00,
quantity_shipped NUMERIC(19,4) DEFAULT 0.00,
notes TEXT,
sequence INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE SalesOrderBundleComponentStatus ( -- Tracks procurement status for bundle components
order_line_item_id BIGINT NOT NULL REFERENCES SalesOrderLineItems(order_line_item_id) ON DELETE CASCADE, -- The *parent* bundle line item
component_product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE RESTRICT, -- The specific component product
quantity_required NUMERIC(19,4) NOT NULL, -- Quantity of this component needed *per unit* of the parent bundle
quantity_procured NUMERIC(19,4) DEFAULT 0.00, -- Total quantity procured specifically for this bundle line's needs
quantity_received NUMERIC(19,4) DEFAULT 0.00, -- Total quantity received for this bundle line
status VARCHAR(30) DEFAULT 'Required', -- Status for this specific component for this bundle line
PRIMARY KEY (order_line_item_id, component_product_id)
);
CREATE TABLE SalesOrderProcurementLinks ( -- M2M linking SO Lines to PO Lines
order_line_item_id BIGINT NOT NULL REFERENCES SalesOrderLineItems(order_line_item_id) ON DELETE CASCADE,
po_line_item_id BIGINT NOT NULL REFERENCES PurchaseOrderLineItems(po_line_item_id) ON DELETE CASCADE,
quantity_linked NUMERIC(19,4) NOT NULL, -- How much of PO line quantity is allocated to this SO line
linked_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (order_line_item_id, po_line_item_id)
);
CREATE TABLE Shipments (
shipment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sales_order_id UUID NOT NULL REFERENCES SalesOrders(portal_order_id) ON DELETE CASCADE,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org receiving the shipment
managing_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP Org sending the shipment
shipment_date DATE NOT NULL DEFAULT CURRENT_DATE,
carrier VARCHAR(100) NULL,
tracking_number VARCHAR(100) NULL,
status VARCHAR(30) NOT NULL DEFAULT 'Processing' CHECK (status IN ('Processing', 'Packed', 'Shipped', 'Delivered', 'Cancelled', 'Exception')),
shipped_from_warehouse_id UUID NULL REFERENCES Warehouses(warehouse_id) ON DELETE SET NULL, -- Warehouse stock was pulled from
shipping_method VARCHAR(100) NULL, -- e.g., 'Ground', 'Overnight'
shipping_cost_charged NUMERIC(19,4) NULL, -- Actual cost charged to customer for this shipment
internal_shipping_cost NUMERIC(19,4) NULL, -- Internal cost incurred for this shipment
currency_code VARCHAR(3) NULL, -- Currency of cost fields
shipping_address_details JSONB NULL, -- Snapshot of the shipping address used
delivery_notes TEXT NULL, -- Notes for delivery driver or recipient
notes TEXT NULL, -- Internal notes
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated by trigger
);
COMMENT ON TABLE Shipments IS 'Tracks outbound shipments related to Sales Orders.';
COMMENT ON COLUMN Shipments.status IS 'The current status of the shipment process.';
CREATE TABLE ShipmentLineItems (
shipment_line_id BIGSERIAL PRIMARY KEY,
shipment_id UUID NOT NULL REFERENCES Shipments(shipment_id) ON DELETE CASCADE,
sales_order_line_item_id BIGINT NOT NULL REFERENCES SalesOrderLineItems(order_line_item_id) ON DELETE RESTRICT, -- Link to the SO line being shipped
product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE RESTRICT, -- Denormalized for easier access
quantity_shipped NUMERIC(19,4) NOT NULL CHECK (quantity_shipped > 0), -- Quantity of this product in this shipment for this SO line
serial_numbers TEXT[] NULL, -- Array of serial numbers shipped for this line item in this shipment
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE ShipmentLineItems IS 'Details which products/quantities from a Sales Order Line were included in a specific Shipment.';
COMMENT ON COLUMN ShipmentLineItems.serial_numbers IS 'Array storing serial numbers included in this specific shipment line.';
-- =============================================
-- Quotes & Quoting
-- =============================================
-- Main table for Quote headers
CREATE TABLE Quotes (
quote_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE RESTRICT, -- Client Organization the quote is for
managing_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE RESTRICT, -- MSP Organization issuing the quote
quote_number VARCHAR(50) NOT NULL, -- User-friendly quote number, unique within the managing_org_id
quote_date DATE NOT NULL DEFAULT CURRENT_DATE,
expiry_date DATE, -- Optional: When the quote expires
status VARCHAR(30) NOT NULL DEFAULT 'Draft' CHECK (status IN ('Draft', 'Presented', 'NeedsApproval', 'Approved', 'Rejected', 'Expired', 'Converted', 'Cancelled')),
agreement_id UUID NULL REFERENCES Agreements(agreement_id) ON DELETE SET NULL, -- Optional: Link to a specific client agreement
requester_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- CommandIT user who initiated/created the quote
sales_rep_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- Assigned sales representative
primary_contact_id UUID NULL REFERENCES Contacts(contact_id) ON DELETE SET NULL, -- Primary client contact for this quote
shipping_address_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Optional: Specified shipping location
billing_address_details JSONB, -- Optional: Snapshot or specific billing address details
currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD', -- ISO 4217 currency code for this quote
subtotal_amount NUMERIC(19,4) NOT NULL DEFAULT 0.00 CHECK (subtotal_amount >= 0),
discount_amount NUMERIC(19,4) NOT NULL DEFAULT 0.00 CHECK (discount_amount >= 0), -- Total discount applied at quote level (if any)
tax_amount NUMERIC(19,4) NOT NULL DEFAULT 0.00 CHECK (tax_amount >= 0),
shipping_amount NUMERIC(19,4) NOT NULL DEFAULT 0.00 CHECK (shipping_amount >= 0),
total_amount NUMERIC(19,4) NOT NULL DEFAULT 0.00 CHECK (total_amount >= 0), -- Calculated: subtotal - discount + tax + shipping
notes TEXT, -- Notes visible to the client
internal_notes TEXT, -- Internal notes for MSP staff
approval_request_id UUID NULL REFERENCES ApprovalRequests(approval_request_id) ON DELETE SET NULL, -- Link to approval workflow if needed
converted_sales_order_id UUID NULL REFERENCES SalesOrders(portal_order_id) ON DELETE SET NULL, -- Link to the Sales Order if converted
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT quotes_managing_org_quote_number_unique UNIQUE (managing_org_id, quote_number)
);
COMMENT ON TABLE Quotes IS 'Stores header information for sales quotes provided to clients.';
COMMENT ON COLUMN Quotes.org_id IS 'Client Organization the quote is for.';
COMMENT ON COLUMN Quotes.managing_org_id IS 'MSP Organization issuing the quote.';
COMMENT ON COLUMN Quotes.quote_number IS 'User-friendly quote number, unique within the managing MSP organization.';
COMMENT ON COLUMN Quotes.status IS 'Lifecycle status of the quote (Draft, Presented, Approved, Rejected, Expired, Converted, Cancelled).';
COMMENT ON COLUMN Quotes.agreement_id IS 'Optional link to a specific client agreement that might influence pricing or terms.';
COMMENT ON COLUMN Quotes.currency_code IS 'ISO 4217 currency code for all monetary amounts on this quote.';
COMMENT ON COLUMN Quotes.total_amount IS 'The final calculated total amount for the quote.';
COMMENT ON COLUMN Quotes.approval_request_id IS 'Link to the approval request if this quote requires formal approval.';
COMMENT ON COLUMN Quotes.converted_sales_order_id IS 'Link to the Sales Order generated from this quote, if converted.';
-- Table for individual line items within a Quote
CREATE TABLE QuoteLineItems (
quote_line_item_id BIGSERIAL PRIMARY KEY,
quote_id UUID NOT NULL REFERENCES Quotes(quote_id) ON DELETE CASCADE, -- Link back to the parent quote
sequence INTEGER NOT NULL DEFAULT 0, -- Display order
product_id UUID NULL REFERENCES Products(product_id) ON DELETE RESTRICT, -- Link to catalog Product
description TEXT NOT NULL, -- Description (can override product)
quantity NUMERIC(19,4) NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(19,4) NOT NULL CHECK (unit_price >= 0),
unit_cost NUMERIC(19,4) NULL CHECK (unit_cost >= 0), -- Optional estimated cost
discount_percentage NUMERIC(5,2) DEFAULT 0.00 CHECK (discount_percentage >= 0 AND discount_percentage <= 100),
line_total NUMERIC(19,4) NOT NULL CHECK (line_total >= 0), -- Calculated: quantity * unit_price * (1 - discount_percentage / 100)
is_taxable BOOLEAN NOT NULL DEFAULT true,
tax_code_id INTEGER NULL REFERENCES TaxCodes(tax_code_id) ON DELETE SET NULL,
is_bundle_parent BOOLEAN NOT NULL DEFAULT false, -- Is this the main line item representing a bundle/configuration?
parent_line_item_id BIGINT NULL REFERENCES QuoteLineItems(quote_line_item_id) ON DELETE CASCADE, -- Link to parent bundle/config line if this is a component
bundle_price_display_mode VARCHAR(20) NULL CHECK (bundle_price_display_mode IN ('TotalPrice', 'ComponentPrices')), -- How to display price for this bundle instance on the quote (applies only if is_bundle_parent=true)
configuration_details JSONB NULL, -- Stores selected configurator options { "attribute_name": "selected_option_value", ... } (applies only if is_bundle_parent=true and originated from configurator)
notes TEXT -- Notes specific to this line item
);
COMMENT ON COLUMN QuoteLineItems.is_bundle_parent IS 'Flags this line item as the main entry for a bundle or configured product; child components link to this via parent_line_item_id.';
COMMENT ON COLUMN QuoteLineItems.parent_line_item_id IS 'If this line represents a component, links to the quote_line_item_id of the parent bundle/configured item.';
COMMENT ON COLUMN QuoteLineItems.bundle_price_display_mode IS 'Controls quote output: Show only parent total (TotalPrice) or list component prices (ComponentPrices). Applies to parent lines.';
COMMENT ON COLUMN QuoteLineItems.configuration_details IS 'Stores the specific options chosen via a configurator that resulted in this configured bundle being added to the quote.';
-- =============================================
-- Billing & Financials (Continued)
-- =============================================
CREATE TABLE CurrencyCodes (
currency_code CHAR(3) PRIMARY KEY, -- ISO 4217 Alpha-3 code (e.g., 'CAD', 'USD', 'EUR')
currency_name VARCHAR(100) NOT NULL, -- e.g., 'Canadian Dollar', 'US Dollar'
symbol VARCHAR(5) NULL, -- e.g., '$', '€'
decimal_places SMALLINT NOT NULL DEFAULT 2,
is_active BOOLEAN NOT NULL DEFAULT true
);
COMMENT ON TABLE CurrencyCodes IS 'Lookup table for ISO 4217 currency codes and related information.';
CREATE TABLE Products (
product_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
identifier VARCHAR(100) NOT NULL,
description TEXT NOT NULL,
product_type VARCHAR(30) NOT NULL CHECK (product_type IN ('Inventory', 'NonInventory', 'Service', 'Bundle', 'Labor', 'Expense', 'Configurable')),
category VARCHAR(100),
subcategory VARCHAR(100),
manufacturer_id UUID NULL REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL,
manufacturer_part_number VARCHAR(100),
unit_price NUMERIC(19,4) DEFAULT 0.00,
default_currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD' REFERENCES CurrencyCodes(currency_code),
unit_cost NUMERIC(19,4) NULL,
unit_of_measure VARCHAR(50),
default_work_type_id UUID REFERENCES WorkTypes(work_type_id) ON DELETE SET NULL,
is_serialized BOOLEAN NOT NULL DEFAULT false,
is_bundle BOOLEAN NOT NULL DEFAULT false,
is_configurable BOOLEAN NOT NULL DEFAULT false,
configurator_template_id UUID NULL REFERENCES ConfiguratorTemplates(template_id) ON DELETE SET NULL,
is_billable BOOLEAN NOT NULL DEFAULT true,
is_taxable BOOLEAN NOT NULL DEFAULT true,
is_inactive BOOLEAN NOT NULL DEFAULT false,
phase_out_date DATE NULL,
end_of_life_date DATE NULL,
end_of_support_date DATE NULL,
fulfillment_template_id UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
allow_backorder BOOLEAN NOT NULL DEFAULT true,
external_id VARCHAR(100),
applicable_entity_types TEXT[] NULL, -- Stores ['Device', 'User'] or specific type
applicable_device_types TEXT[] NULL, -- Stores ['Laptop', 'Server', ...] or NULL for all device types
comparison_attributes JSONB NULL, -- Stores features for comparison modal, e.g., {"Real-time Protection": true}
is_mandatory BOOLEAN NOT NULL DEFAULT false, -- Is service required if applicable?
is_service_management_enabled BOOLEAN NOT NULL DEFAULT false, -- Should this appear on the Service Management screen?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT products_org_identifier_unique UNIQUE NULLS NOT DISTINCT (org_id, identifier),
CHECK ((is_configurable = true AND configurator_template_id IS NOT NULL) OR (is_configurable = false))
);
-- Add comments for new/relevant columns
COMMENT ON COLUMN Products.applicable_entity_types IS 'Array indicating if product applies to ''Device'', ''User'', or both. NULL implies general applicability based on product_type.';
COMMENT ON COLUMN Products.applicable_device_types IS 'Array of specific device types (from Devices.device_type) this product applies to. NULL means applicable to all device types matching applicable_entity_types.';
COMMENT ON COLUMN Products.comparison_attributes IS 'JSONB object storing key features and their values (boolean/text) for display in comparison modals.';
COMMENT ON COLUMN Products.is_mandatory IS 'If true, this service MUST be assigned to applicable entities and cannot be set to ''None''.';
COMMENT ON COLUMN Products.is_service_management_enabled IS 'If true and product_type is ''Service'', this product will appear as an option on the Service Management assignment screen.';
-- Add indexes for performance on new/relevant columns
CREATE INDEX idx_products_category ON Products(category);
CREATE INDEX idx_products_product_type ON Products(product_type);
CREATE INDEX idx_products_svc_mgmt_enabled ON Products(is_service_management_enabled) WHERE is_service_management_enabled = true;
-- Consider GIN index if querying arrays frequently:
-- CREATE INDEX idx_products_applicable_entity_types ON Products USING GIN (applicable_entity_types);
-- CREATE INDEX idx_products_applicable_device_types ON Products USING GIN (applicable_device_types);
CREATE TABLE ProductVendorCosts (
product_vendor_cost_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE CASCADE,
vendor_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE RESTRICT, -- Link to the Vendor/Distributor Organization
vendor_sku VARCHAR(100) NULL, -- The SKU or part number used by this specific vendor
unit_cost NUMERIC(19,4) NOT NULL, -- Cost of the product from this vendor
currency_code VARCHAR(3) NOT NULL, -- ISO 4217 currency code for the unit_cost (e.g., 'CAD', 'USD')
minimum_order_quantity NUMERIC(19,4) DEFAULT 1.00,
unit_of_measure VARCHAR(50), -- Unit of measure for the cost (if different from product standard)
is_preferred_source BOOLEAN NOT NULL DEFAULT false, -- Is this the default vendor to source from?
last_checked_at TIMESTAMPTZ, -- When this cost was last verified/updated from the vendor feed/API
source_url TEXT NULL, -- Optional URL to the product page on the vendor's site
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT productvendorcosts_product_vendor_unique UNIQUE (product_id, vendor_org_id)
);
CREATE INDEX idx_productvendorcosts_product_id ON ProductVendorCosts(product_id);
CREATE INDEX idx_productvendorcosts_vendor_org_id ON ProductVendorCosts(vendor_org_id);
CREATE TABLE PricingRuleSets (
rule_set_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(150) NOT NULL,
description TEXT,
currency_code VARCHAR(3) NOT NULL,
is_default_for_org BOOLEAN NOT NULL DEFAULT false,
is_active BOOLEAN NOT NULL DEFAULT true,
rounding_rules_definition JSONB NULL, -- Structured definition for rounding rules
minimum_margin_percent NUMERIC(5,2) NULL CHECK (minimum_margin_percent >= 0 AND minimum_margin_percent < 100), -- Minimum margin to protect during rounding
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name)
);
COMMENT ON TABLE PricingRuleSets IS 'Defines named collections of specific pricing rules, including rounding and minimum margin settings.';
COMMENT ON COLUMN PricingRuleSets.rounding_rules_definition IS 'JSONB array defining tiered rounding rules. Example: [{"max_price_threshold": 100, "rounding_type": "Nearest", "rounding_multiple": 10, "apply_ending_adjustment": "Subtract0.01"}, {"rounding_type": "None"}]';
COMMENT ON COLUMN PricingRuleSets.minimum_margin_percent IS 'Optional minimum margin %. If rounding down violates this margin, rounding is adjusted/skipped.';
CREATE TABLE PricingRules (
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rule_set_id UUID NOT NULL REFERENCES PricingRuleSets(rule_set_id) ON DELETE CASCADE,
priority INTEGER NOT NULL DEFAULT 0, -- Execution order within set (lower runs first, first match typically wins)
name VARCHAR(255) NULL, -- Optional name for the specific rule
is_active BOOLEAN NOT NULL DEFAULT true, -- Is this rule currently active?
effective_start_date DATE NULL, -- Optional start date for rule validity
effective_end_date DATE NULL, -- Optional end date for rule validity
-- Filter Criteria (Rule applies if ALL non-null filters match)
filter_product_id UUID NULL REFERENCES Products(product_id) ON DELETE CASCADE, -- Specific product
filter_product_category VARCHAR(100) NULL, -- Specific product category
filter_product_manufacturer_id UUID NULL REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL, -- Specific manufacturer
filter_target_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Specific Client Org
filter_target_org_type_id INTEGER NULL REFERENCES OrganizationTypes(org_type_id) ON DELETE RESTRICT, -- Specific Org Type
filter_target_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE CASCADE, -- Specific Location
filter_min_quantity NUMERIC(19,4) NULL, -- Minimum quantity for this rule to apply (volume break)
-- Pricing Calculation Definition
calculation_method VARCHAR(30) NOT NULL CHECK (calculation_method IN ('FixedPrice', 'CostPlusPercent', 'CostPlusAmount', 'ListPriceMinusPercent')),
cost_basis VARCHAR(30) NULL CHECK (cost_basis IN ('StandardCost', 'PreferredVendorCost', 'SpecificVendorCost')), -- Required if method is CostPlus*
cost_basis_vendor_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE RESTRICT, -- Required if cost_basis='SpecificVendorCost'
price_or_markup_value NUMERIC(19,4) NOT NULL, -- The fixed price, markup %, markup amount, or discount %
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CHECK ((calculation_method LIKE 'CostPlus%' AND cost_basis IS NOT NULL) OR (calculation_method NOT LIKE 'CostPlus%')), -- Cost basis needed for CostPlus*
CHECK ((cost_basis = 'SpecificVendorCost' AND cost_basis_vendor_org_id IS NOT NULL) OR (cost_basis != 'SpecificVendorCost' OR cost_basis IS NULL)) -- Vendor needed for SpecificVendorCost
);
COMMENT ON TABLE PricingRules IS 'Defines individual pricing rules within a Rule Set, including filters and calculation methods.';
COMMENT ON COLUMN PricingRules.priority IS 'Determines rule evaluation order within a set (lower number evaluated first).';
COMMENT ON COLUMN PricingRules.filter_product_id IS 'Applies rule only if this specific product matches.';
COMMENT ON COLUMN PricingRules.filter_min_quantity IS 'Applies rule only if line item quantity meets or exceeds this minimum.';
COMMENT ON COLUMN PricingRules.calculation_method IS 'How the selling price is calculated (Fixed, Cost+, List-).';
COMMENT ON COLUMN PricingRules.cost_basis IS 'Which cost to use for CostPlus calculations (Standard Product Cost, Preferred Vendor Cost, Specific Vendor Cost).';
COMMENT ON COLUMN PricingRules.cost_basis_vendor_org_id IS 'The specific Vendor Org ID if cost_basis is SpecificVendorCost.';
COMMENT ON COLUMN PricingRules.price_or_markup_value IS 'The value used in the calculation (fixed price, percentage, or fixed amount).';
CREATE TABLE ProductBundleComponents (
bundle_component_id BIGSERIAL PRIMARY KEY,
parent_product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE CASCADE, -- The Product defined as the bundle
child_product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE CASCADE, -- The Product included in the bundle
quantity NUMERIC(19,4) NOT NULL DEFAULT 1 CHECK (quantity > 0), -- Quantity of the child item per bundle unit
is_optional BOOLEAN NOT NULL DEFAULT false, -- Can this component be optionally included/excluded by the user? (Mainly relevant for configurators/templates)
default_included BOOLEAN NOT NULL DEFAULT true, -- If optional, is it selected by default?
UNIQUE (parent_product_id, child_product_id)
);
COMMENT ON TABLE ProductBundleComponents IS 'Defines the component products included within a bundle product in the catalog.';
COMMENT ON COLUMN ProductBundleComponents.parent_product_id IS 'FK to the Products table identifying the main bundle item.';
COMMENT ON COLUMN ProductBundleComponents.child_product_id IS 'FK to the Products table identifying a component within the bundle.';
COMMENT ON COLUMN ProductBundleComponents.quantity IS 'Default quantity of the child product included for each unit of the parent bundle.';
CREATE TABLE ConfiguratorTemplates (
template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
base_product_id UUID NULL REFERENCES Products(product_id) ON DELETE SET NULL,
pricing_rule_set_id UUID NULL REFERENCES PricingRuleSets(rule_set_id) ON DELETE SET NULL, -- Specific pricing rules for components
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE(org_id, name)
);
COMMENT ON COLUMN ConfiguratorTemplates.pricing_rule_set_id IS 'Optional: Link to a PricingRuleSet used specifically to price components selected via this configurator, overriding standard pricing hierarchy.';
CREATE TABLE ConfiguratorAttributeGroups (
group_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id UUID NOT NULL REFERENCES ConfiguratorTemplates(template_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL, -- e.g., "Core Components", "Storage Options"
description TEXT,
image_url TEXT NULL, -- Optional URL for a group icon/image
sequence_order INTEGER NOT NULL DEFAULT 0,
UNIQUE (template_id, name)
);
CREATE TABLE ConfiguratorAttributes (
attribute_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id UUID NOT NULL REFERENCES ConfiguratorTemplates(template_id) ON DELETE CASCADE,
group_id UUID NULL REFERENCES ConfiguratorAttributeGroups(group_id) ON DELETE SET NULL,
name VARCHAR(150) NOT NULL, -- e.g., "Memory (RAM)", "Operating System"
description TEXT,
attribute_type VARCHAR(20) NOT NULL DEFAULT 'SingleSelect' CHECK (attribute_type IN ('SingleSelect', 'MultiSelect', 'Quantity')),
display_hint VARCHAR(20) NULL CHECK (display_hint IN ('Radio', 'Checkbox', 'Dropdown', 'Slider', 'NumberInput')),
image_url TEXT NULL, -- Optional URL for an attribute icon/image
is_required BOOLEAN NOT NULL DEFAULT true,
sequence_order INTEGER NOT NULL DEFAULT 0,
UNIQUE(template_id, name)
);
COMMENT ON COLUMN ConfiguratorAttributes.attribute_type IS 'Defines the logical selection rule: SingleSelect (choose one), MultiSelect (choose zero or more), Quantity (enter a number).';
COMMENT ON COLUMN ConfiguratorAttributes.display_hint IS 'Optional suggestion for UI rendering (e.g., Radio/Dropdown for SingleSelect, Checkbox for MultiSelect, NumberInput/Slider for Quantity).';
REATE TABLE ConfiguratorOptions (
option_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
attribute_id UUID NOT NULL REFERENCES ConfiguratorAttributes(attribute_id) ON DELETE CASCADE,
display_text VARCHAR(255) NOT NULL,
related_product_id UUID NULL REFERENCES Products(product_id) ON DELETE SET NULL, -- Link to the actual Product SKU
quantity_modifier NUMERIC(19,4) NOT NULL DEFAULT 1, -- Quantity of related_product_id added
image_url TEXT NULL,
is_default_option BOOLEAN NOT NULL DEFAULT false,
is_available BOOLEAN NOT NULL DEFAULT true,
sequence_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (attribute_id, display_text)
);
COMMENT ON COLUMN ConfiguratorOptions.related_product_id IS 'Links option to a Product. Price is determined by applying Pricing Rules to this product.';
COMMENT ON COLUMN ConfiguratorOptions.quantity_modifier IS 'Quantity of the related_product_id added when this option is selected.';
COMMENT ON COLUMN ConfiguratorOptions.price_adjustment IS 'Price difference relative to the default option for this attribute (can be positive or negative).';
CREATE TABLE ConfiguratorRules (
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_id UUID NOT NULL REFERENCES ConfiguratorTemplates(template_id) ON DELETE CASCADE, -- Rule applies to the whole template
name VARCHAR(255), -- Optional name for the rule
description TEXT, -- Explanation of the rule
rule_type VARCHAR(20) NOT NULL CHECK (rule_type IN ('Requirement', 'Exclusion', 'Recommendation', 'PriceAdjustment', 'QuantityConstraint')),
condition_logic JSONB NOT NULL, -- Defines trigger: e.g., { "operator": "AND", "conditions": [ {"attribute_id": "uuid", "selected_option_id": "uuid"}, ... ] }
action_logic JSONB NOT NULL, -- Defines action: e.g., { "action": "require", "option_id": "uuid" } or { "action": "exclude", "attribute_id": "uuid"} or { "action": "set_quantity", "option_id": "uuid", "quantity": 2}
is_active BOOLEAN NOT NULL DEFAULT true,
error_message TEXT NULL, -- Message to display to user if rule is violated (especially for exclusions)
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
COMMENT ON TABLE ConfiguratorRules IS 'Stores rules defining dependencies, exclusions, or price adjustments between selected configurator options.';
COMMENT ON COLUMN ConfiguratorRules.condition_logic IS 'JSON structure defining the conditions (selected options) that trigger this rule.';
COMMENT ON COLUMN ConfiguratorRules.action_logic IS 'JSON structure defining the action to take (require/exclude option/attribute, adjust price, set quantity) when conditions are met.';
CREATE TABLE MspCountryDistributorPrefs (
msp_country_dist_pref_id BIGSERIAL PRIMARY KEY,
msp_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- The MSP setting the preference
country_code VARCHAR(2) NOT NULL, -- ISO 3166-1 alpha-2 Country Code
preferred_vendor_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE RESTRICT, -- Link to the preferred Vendor Org
priority INTEGER NOT NULL DEFAULT 0, -- Lower number = higher preference
last_updated TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE (msp_org_id, country_code, preferred_vendor_org_id),
UNIQUE (msp_org_id, country_code, priority) -- Ensure unique priority per country for ordering
);
COMMENT ON TABLE MspCountryDistributorPrefs IS 'Allows MSPs to define preferred Vendors/Distributors per country, with priority.';
CREATE TABLE WorkTypes (
work_type_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP defining the work type
name VARCHAR(100) NOT NULL, -- e.g., 'Onsite Support', 'Remote Support', 'Project Labor', 'Travel Time'
description TEXT,
is_billable BOOLEAN NOT NULL DEFAULT true, -- Does time logged with this type typically get billed? (Rate sheet determines rate)
is_active BOOLEAN NOT NULL DEFAULT true, -- Can this work type be used?
external_id VARCHAR(100), -- ID from an external system (e.g., PSA integration)
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT worktypes_org_name_unique UNIQUE(org_id, name)
);
CREATE TABLE RateSheets (
rate_sheet_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
is_default BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT ratesheets_org_name_unique UNIQUE(org_id, name)
);
CREATE TABLE RateSheetLines (
rate_sheet_line_id BIGSERIAL PRIMARY KEY,
rate_sheet_id UUID NOT NULL REFERENCES RateSheets(rate_sheet_id) ON DELETE CASCADE,
work_type_id UUID NOT NULL REFERENCES WorkTypes(work_type_id) ON DELETE RESTRICT,
rate_modifier VARCHAR(50) DEFAULT 'Standard' CHECK (rate_modifier IN ('Standard', 'Overtime', 'AfterHours', 'Holiday', 'Weekend')),
billing_rate NUMERIC(19,4) NOT NULL,
minimum_charge_minutes INTEGER DEFAULT 0,
billing_increment_minutes INTEGER DEFAULT 1,
surcharge_amount NUMERIC(19,4) DEFAULT 0.00,
effective_start_date DATE DEFAULT CURRENT_DATE,
effective_end_date DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (rate_sheet_id, work_type_id, rate_modifier, effective_start_date)
);
CREATE TABLE HolidaySets (
holiday_set_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_default BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE(org_id, name)
);
CREATE TABLE HolidayDates (
holiday_date_id BIGSERIAL PRIMARY KEY,
holiday_set_id UUID NOT NULL REFERENCES HolidaySets(holiday_set_id) ON DELETE CASCADE,
holiday_date DATE NOT NULL, -- The actual date the holiday falls on (e.g., Jan 1st)
name VARCHAR(100) NOT NULL, -- e.g., "New Year's Day", "Canada Day"
observed_on_date DATE NULL, -- If observed on different date (e.g., the Monday after), store that date here. NULL if observed on holiday_date.
is_observed_as_closed BOOLEAN NOT NULL DEFAULT true, -- Does this observed date count as non-working for SLA/scheduling? Default Yes.
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Unique constraint might need adjustment depending on how duplicates are handled,
-- but generally should be unique per set for the date that matters (observed or actual)
UNIQUE (holiday_set_id, holiday_date) -- Base uniqueness on the statutory date per set
);
CREATE TABLE Agreements (
agreement_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
managing_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
start_date DATE NOT NULL,
end_date DATE,
agreement_type VARCHAR(50),
status VARCHAR(20) DEFAULT 'Active' CHECK (status IN ('Draft', 'Active', 'Expired', 'Cancelled')),
rate_sheet_id UUID REFERENCES RateSheets(rate_sheet_id) ON DELETE SET NULL,
business_hours_id UUID REFERENCES BusinessHours(business_hours_id) ON DELETE SET NULL,
holiday_set_id UUID REFERENCES HolidaySets(holiday_set_id) ON DELETE SET NULL,
travel_billing_policy_id UUID NULL REFERENCES TravelBillingPolicies(travel_billing_policy_id) ON DELETE SET NULL,
travel_billing_approval_workflow_id UUID NULL REFERENCES ApprovalWorkflowDefinitions(definition_id) ON DELETE SET NULL,
pricing_rule_set_id UUID NULL REFERENCES PricingRuleSets(rule_set_id) ON DELETE SET NULL, -- Pricing rules specific to non-addition items in this Agreement
allow_overage BOOLEAN DEFAULT true,
bill_travel_separately BOOLEAN DEFAULT true,
default_so_client_approval VARCHAR(20) DEFAULT 'None' CHECK (default_so_client_approval IN ('None', 'Required', 'AutoApproved')),
default_so_approval_threshold NUMERIC(19,4),
onsite_billing_ref_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
onsite_billing_distance_km INTEGER,
onsite_billing_distro_group_id UUID REFERENCES DistributionGroups(distribution_group_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name)
);
COMMENT ON COLUMN Agreements.pricing_rule_set_id IS 'Optional: Link to a PricingRuleSet applying to items quoted/ordered under this agreement (lower priority than AgreementAdditions pricing).';
COMMENT ON COLUMN Agreements.onsite_billing_distro_group_id IS 'Distribution group to get approval for onsite billing if distance check returns billable.';
CREATE TABLE AgreementAdditions (
addition_id BIGSERIAL PRIMARY KEY,
agreement_id UUID NOT NULL REFERENCES Agreements(agreement_id) ON DELETE CASCADE,
product_id UUID REFERENCES Products(product_id) ON DELETE SET NULL,
description TEXT NOT NULL,
quantity_included NUMERIC(19,4),
unit_of_measure VARCHAR(50),
unit_price_override NUMERIC(19,4),
recurring_cycle VARCHAR(20) CHECK (recurring_cycle IN ('Monthly', 'Quarterly', 'Annually', 'OneTime')),
recurring_fee NUMERIC(19,4) DEFAULT 0.00,
is_taxable BOOLEAN DEFAULT true,
apply_to_all_locations BOOLEAN DEFAULT true,
target_location_id UUID REFERENCES Locations(location_id) ON DELETE CASCADE,
-- Distance Threshold fields for this specific addition/location target
distance_threshold_km INTEGER NULL, -- Threshold (can be NULL if not applicable)
distance_surcharge_product_id UUID NULL REFERENCES Products(product_id) ON DELETE SET NULL, -- Product to bill if threshold exceeded
distance_surcharge_flat_amount NUMERIC(19,4) NULL, -- Flat amount if no product
distance_threshold_applies_to VARCHAR(20) NULL CHECK (distance_threshold_applies_to IN ('TravelToClientSite', 'TravelFromClientSite', 'Both')), -- Which leg?
measure_distance_from VARCHAR(30) NULL CHECK (measure_distance_from IN ('AgreementReferencePoints', 'PreviousSite', 'TechStartLocation')), -- How to measure?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE AgreementDistanceReferenceLocations (
agreement_id UUID NOT NULL REFERENCES Agreements(agreement_id) ON DELETE CASCADE,
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE,
PRIMARY KEY (agreement_id, location_id)
);
CREATE TABLE AgreementTemplates (
agreement_template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP defining the template
name VARCHAR(255) NOT NULL,
description TEXT,
agreement_type VARCHAR(50), -- Default type for agreements created from this template
rate_sheet_id UUID NULL REFERENCES RateSheets(rate_sheet_id) ON DELETE SET NULL,
business_hours_id UUID NULL REFERENCES BusinessHours(business_hours_id) ON DELETE SET NULL,
holiday_set_id UUID NULL REFERENCES HolidaySets(holiday_set_id) ON DELETE SET NULL,
travel_billing_policy_id UUID NULL REFERENCES TravelBillingPolicies(travel_billing_policy_id) ON DELETE SET NULL,
default_additions JSONB NULL, -- Simplified: Store default additions as JSON. { "additions": [ { "product_identifier": "SKU123", "description": "Managed Antivirus", "quantity": 10, "recurring_cycle": "Monthly", "recurring_fee": 5.00 }, ... ] }
default_sla_ids UUID[] NULL, -- Array of SLA IDs to apply by default
default_so_client_approval VARCHAR(20) DEFAULT 'None' CHECK (default_so_client_approval IN ('None', 'Required', 'AutoApproved')),
default_so_approval_threshold NUMERIC(19,4),
notes TEXT,
is_active BOOLEAN NOT NULL DEFAULT true, -- Can this template be used?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CONSTRAINT agreementtemplates_org_name_unique UNIQUE(org_id, name)
);
CREATE TABLE AgreementTemplateDistanceReferenceLocations (
agreement_template_id UUID NOT NULL REFERENCES AgreementTemplates(agreement_template_id) ON DELETE CASCADE,
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE,
PRIMARY KEY (agreement_template_id, location_id)
);
CREATE TABLE SLAs (
sla_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(150) NOT NULL,
description TEXT,
service_target VARCHAR(50) NOT NULL CHECK (service_target IN ('ResponseTime', 'ResolutionTime', 'UptimePercentage')),
metric VARCHAR(50) NOT NULL CHECK (metric IN ('Minutes', 'Hours', 'Percentage')),
target_value NUMERIC(10, 2) NOT NULL,
warning_threshold_percent INTEGER NOT NULL DEFAULT 80 CHECK (warning_threshold_percent > 0 AND warning_threshold_percent < 100), -- Percentage of time elapsed before 'AtRisk'
business_hours_id UUID NULL REFERENCES BusinessHours(business_hours_id) ON DELETE SET NULL,
applies_to_filter JSONB NULL, -- Filter criteria for tickets { "priority": ["Critical", "High"], "type": ["Incident"] }
is_default BOOLEAN NOT NULL DEFAULT false,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name)
);
CREATE TABLE AgreementSLAs (
agreement_sla_id BIGSERIAL PRIMARY KEY,
agreement_id UUID NOT NULL REFERENCES Agreements(agreement_id) ON DELETE CASCADE,
sla_id UUID NOT NULL REFERENCES SLAs(sla_id) ON DELETE CASCADE,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (agreement_id, sla_id)
);
CREATE TABLE SlaBreachLog (
breach_log_id BIGSERIAL PRIMARY KEY,
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Denormalized for easier reporting
sla_id UUID NOT NULL REFERENCES SLAs(sla_id) ON DELETE CASCADE, -- The SLA definition that was breached
breach_type VARCHAR(20) NOT NULL CHECK (breach_type IN ('Response', 'Resolution')), -- Which target was missed
sla_target_value_minutes INTEGER NULL, -- Target time in minutes (denormalized from SLA for context)
sla_business_hours_id UUID NULL REFERENCES BusinessHours(business_hours_id) ON DELETE SET NULL, -- Business hours applied (denormalized)
due_timestamp TIMESTAMPTZ NOT NULL, -- When the SLA target was due (UTC)
actual_timestamp TIMESTAMPTZ NULL, -- When the action (response/resolution) actually occurred (UTC)
breach_duration_seconds INTEGER NULL, -- Calculated duration of the breach in seconds (Actual - Due)
logged_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -- When this breach record was created
);
COMMENT ON TABLE SlaBreachLog IS 'Logs instances where ticket response or resolution times breached defined SLA targets.';
COMMENT ON COLUMN SlaBreachLog.breach_type IS 'Indicates whether the Response time or Resolution time target was breached.';
COMMENT ON COLUMN SlaBreachLog.breach_duration_seconds IS 'Calculated time difference between actual completion and the due time, in seconds.';
CREATE TABLE UserCostRates (
user_cost_rate_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
effective_date DATE NOT NULL DEFAULT CURRENT_DATE,
hourly_cost_rate NUMERIC(19,4) NOT NULL DEFAULT 0.00,
salary_amount NUMERIC(19,4),
salary_period VARCHAR(20) CHECK (salary_period IN ('Annual', 'Monthly', 'BiWeekly')),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (user_id, effective_date)
);
CREATE TABLE TimeEntries (
time_entry_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE RESTRICT,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org context
ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Link to the primary Ticket
project_task_id BIGINT NULL REFERENCES ProjectTasks(project_task_id) ON DELETE SET NULL, -- Link if for a project task
schedule_entry_id BIGINT NULL REFERENCES TechnicianSchedules(schedule_entry_id) ON DELETE SET NULL, -- Link to the scheduled block
related_chat_channel_id UUID NULL REFERENCES ChatChannels(channel_id) ON DELETE SET NULL, -- Link to chat channel if time tracked via chat
agreement_id UUID NULL REFERENCES Agreements(agreement_id) ON DELETE SET NULL, -- Agreement context for billing
work_type_id UUID NOT NULL REFERENCES WorkTypes(work_type_id) ON DELETE RESTRICT, -- Type of work performed
start_time_utc TIMESTAMPTZ NOT NULL, -- Timer start time
end_time_utc TIMESTAMPTZ NULL, -- Timer end time (NULL if running)
duration_seconds INTEGER NULL, -- Calculated duration (end - start) - can be calculated on update/query
notes TEXT NULL, -- Tech's notes about the work done during this entry
internal_notes TEXT NULL, -- Internal-only notes
is_billable BOOLEAN NOT NULL DEFAULT true, -- Should this entry appear on an invoice?
is_travel BOOLEAN NOT NULL DEFAULT false, -- Is this specifically travel time?
travel_distance_km NUMERIC(10,2) NULL,
travel_from_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL,
travel_to_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL,
calculated_cost NUMERIC(19,4) NULL, -- Calculated internal cost (duration * UserCostRate)
calculated_rate NUMERIC(19,4) NULL, -- Calculated billing rate (from RateSheet based on WorkType/Agreement)
calculated_bill_amount NUMERIC(19,4) NULL, -- Calculated bill amount (duration * Rate)
invoice_line_item_id BIGINT NULL REFERENCES InvoiceLineItems(invoice_line_item_id) ON DELETE SET NULL, -- Link after invoicing
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CHECK (end_time_utc IS NULL OR end_time_utc >= start_time_utc)
);
COMMENT ON COLUMN TimeEntries.related_chat_channel_id IS 'If this time entry was generated from activity within a specific chat channel, links to that channel.';
COMMENT ON COLUMN TimeEntries.end_time_utc IS 'Timer end time. NULL indicates the timer is currently running for this entry.';
COMMENT ON COLUMN TimeEntries.duration_seconds IS 'Calculated duration (end_time_utc - start_time_utc). May be NULL if timer is running.';
CREATE TABLE ExpenseEntries (
expense_entry_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE RESTRICT,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org
expense_date DATE NOT NULL,
ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
project_id UUID REFERENCES Projects(project_id) ON DELETE SET NULL,
schedule_entry_id BIGINT REFERENCES TechnicianSchedules(schedule_entry_id) ON DELETE SET NULL,
agreement_id UUID REFERENCES Agreements(agreement_id) ON DELETE SET NULL,
expense_type VARCHAR(100), -- e.g., 'Hotel', 'Flight', 'PerDiem', 'RentalCar', 'Meals'
description TEXT NOT NULL,
quantity NUMERIC(10, 2) NOT NULL DEFAULT 1.00, -- For items like 'days' of per diem
unit_amount NUMERIC(19,4), -- Cost per unit (e.g., per diem rate)
total_amount NUMERIC(19,4) NOT NULL,
currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD',
billing_option VARCHAR(15) NOT NULL DEFAULT 'Billable' CHECK (billing_option IN ('Billable', 'DoNotBill', 'NoCharge')),
payment_method VARCHAR(50), -- 'Company Card', 'Personal'
requires_reimbursement BOOLEAN NOT NULL DEFAULT false, -- Explicit flag, could be generated based on payment_method
markup_percentage NUMERIC(5, 2) NULL, -- Optional markup % for this line
billable_amount NUMERIC(19,4) NULL, -- Calculated amount to invoice (total_amount + markup)
invoice_line_item_id BIGINT REFERENCES InvoiceLineItems(invoice_line_item_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
-- Consider adding CHECK constraint: total_amount = quantity * unit_amount where unit_amount is not null
);
CREATE TABLE ExpenseEntryAttachments (
expense_entry_id BIGINT NOT NULL REFERENCES ExpenseEntries(expense_entry_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
attached_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (expense_entry_id, attachment_id)
);
CREATE TABLE TaxCodes (
tax_code_id SERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL,
rate_percent NUMERIC(5,3) NOT NULL,
is_compound BOOLEAN NOT NULL DEFAULT false,
tax_agency_name VARCHAR(100),
tax_number VARCHAR(50),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE(org_id, name)
);
CREATE TABLE Invoices (
invoice_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org being invoiced
managing_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP Org issuing the invoice
invoice_number VARCHAR(50) NOT NULL UNIQUE,
invoice_date DATE NOT NULL DEFAULT CURRENT_DATE,
due_date DATE,
agreement_id UUID REFERENCES Agreements(agreement_id) ON DELETE SET NULL,
status VARCHAR(20) DEFAULT 'Draft' CHECK (status IN ('Draft', 'Sent', 'Paid', 'Partial', 'Overdue', 'Void')),
currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD', -- Currency for all monetary amounts on this invoice
subtotal_amount NUMERIC(19,4) DEFAULT 0.00,
tax_amount NUMERIC(19,4) DEFAULT 0.00,
total_amount NUMERIC(19,4) DEFAULT 0.00,
amount_paid NUMERIC(19,4) DEFAULT 0.00,
balance_due NUMERIC(19,4) DEFAULT 0.00, -- Typically total_amount - amount_paid
notes TEXT, -- Notes visible to the client
internal_notes TEXT, -- Notes only visible internally
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated by trigger
);
CREATE TABLE InvoiceLineItems (
invoice_line_item_id BIGSERIAL PRIMARY KEY,
invoice_id UUID NOT NULL REFERENCES Invoices(invoice_id) ON DELETE CASCADE,
sequence INTEGER NOT NULL DEFAULT 0,
source_type VARCHAR(20) CHECK (source_type IN ('TimeEntry', 'ExpenseEntry', 'Product', 'AgreementAddition', 'Manual')),
source_id VARCHAR(50),
description TEXT NOT NULL,
quantity NUMERIC(19,4) NOT NULL,
unit_price NUMERIC(19,4) NOT NULL,
line_total NUMERIC(19,4) NOT NULL,
is_taxable BOOLEAN DEFAULT true,
tax_code_id INTEGER REFERENCES TaxCodes(tax_code_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE Payments (
payment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
invoice_id UUID REFERENCES Invoices(invoice_id) ON DELETE SET NULL,
payment_date DATE NOT NULL DEFAULT CURRENT_DATE,
amount NUMERIC(19,4) NOT NULL,
currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD',
payment_method VARCHAR(50),
reference_number VARCHAR(100),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE AssetFinancials (
asset_financial_id BIGSERIAL PRIMARY KEY,
target_entity_type VARCHAR(50) NOT NULL CHECK (target_entity_type IN ('Device', 'Peripheral', 'SoftwareLicense', 'EquipmentAsset')),
target_entity_id UUID NOT NULL,
purchase_order_id UUID REFERENCES PurchaseOrders(purchase_order_id) ON DELETE SET NULL,
purchase_date DATE,
purchase_cost NUMERIC(19,4),
currency_code VARCHAR(3),
depreciation_method VARCHAR(50),
depreciation_start_date DATE,
useful_life_years INTEGER,
salvage_value NUMERIC(19,4),
current_book_value NUMERIC(19,4),
last_depreciation_calc_date DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (target_entity_type, target_entity_id)
);
CREATE TABLE AssetLeases (
lease_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
target_entity_type VARCHAR(50) NOT NULL CHECK (target_entity_type IN ('Device', 'Peripheral', 'EquipmentAsset')),
target_entity_id UUID NOT NULL,
lessor_vendor_org_id UUID REFERENCES Organizations(org_id) ON DELETE SET NULL,
lease_start_date DATE NOT NULL,
lease_end_date DATE NOT NULL,
lease_term_months INTEGER,
monthly_payment NUMERIC(19,4),
buyout_option VARCHAR(50),
buyout_amount NUMERIC(19,4),
currency_code VARCHAR(3) NOT NULL DEFAULT 'CAD', -- Currency of payment/buyout amounts
lease_account_number VARCHAR(100),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
UNIQUE (target_entity_type, target_entity_id, lease_start_date)
);
CREATE TABLE AssetLeaseAttachments (
lease_id UUID NOT NULL REFERENCES AssetLeases(lease_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
description TEXT,
attached_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (lease_id, attachment_id)
);
-- =============================================
-- Scheduling & Availability
-- =============================================
CREATE TABLE TechnicianAvailabilityTemplates (
template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE TechnicianAvailabilitySlots (
slot_id BIGSERIAL PRIMARY KEY,
template_id UUID NOT NULL REFERENCES TechnicianAvailabilityTemplates(template_id) ON DELETE CASCADE,
day_of_week INTEGER NOT NULL CHECK (day_of_week >= 0 AND day_of_week <= 6),
start_time_local TIME NOT NULL,
end_time_local TIME NOT NULL,
CHECK (end_time_local > start_time_local)
);
CREATE TABLE UserAvailability (
user_id UUID PRIMARY KEY REFERENCES Users(user_id) ON DELETE CASCADE,
availability_template_id UUID REFERENCES TechnicianAvailabilityTemplates(template_id) ON DELETE SET NULL,
time_zone VARCHAR(100) NOT NULL,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE TechnicianSchedules (
schedule_entry_id BIGSERIAL PRIMARY KEY,
user_id UUID NULL REFERENCES Users(user_id) ON DELETE CASCADE, -- Made NULLABLE
assigned_vendor_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE SET NULL, -- Link to vendor org
schedule_type VARCHAR(30) NOT NULL CHECK (schedule_type IN ('WorkOrder', 'Appointment', 'TimeOff', 'Travel', 'Meeting', 'Other', 'ExternalDispatch')),
activity_type VARCHAR(100),
ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
project_task_id BIGINT REFERENCES ProjectTasks(project_task_id) ON DELETE SET NULL,
time_off_request_id UUID REFERENCES TimeOffRequests(request_id) ON DELETE SET NULL,
originating_field_request_id UUID REFERENCES FieldServiceRequests(field_service_request_id) ON DELETE SET NULL,
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NOT NULL,
actual_start_time_utc TIMESTAMPTZ,
actual_end_time_utc TIMESTAMPTZ,
title VARCHAR(255),
description TEXT,
location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
bench_id UUID REFERENCES WorkBenches(bench_id) ON DELETE SET NULL,
related_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
external_vendor_reference VARCHAR(100), -- Vendor's job #
is_all_day BOOLEAN NOT NULL DEFAULT false,
status VARCHAR(20) DEFAULT 'Scheduled' CHECK (status IN ('Scheduled', 'Confirmed', 'Traveling', 'InProgress', 'OnHold', 'Completed', 'Cancelled', 'Rescheduled', 'VendorAcknowledged', 'VendorComplete')),
reminder_time_minutes INTEGER,
is_shiftable BOOLEAN DEFAULT true,
optimized_sequence_order INTEGER,
estimated_duration_seconds INTEGER,
travel_time_before_seconds INTEGER,
travel_time_after_seconds INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
CHECK (end_time_utc > start_time_utc),
CONSTRAINT chk_schedule_assignee CHECK (user_id IS NOT NULL OR assigned_vendor_org_id IS NOT NULL) -- Must be assigned to user OR vendor
-- Constraint prevent_tech_overlaps EXCLUDE USING GIST (...) -- Needs GIST index
);
CREATE TABLE TravelLogEntries (
travel_log_id BIGSERIAL PRIMARY KEY,
schedule_entry_id BIGINT REFERENCES TechnicianSchedules(schedule_entry_id) ON DELETE CASCADE,
technician_user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
travel_type VARCHAR(20) NOT NULL CHECK (travel_type IN ('ToSite', 'FromSite', 'ToOffice', 'ToHome', 'BetweenSites')),
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ,
start_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
end_location_id UUID REFERENCES Locations(location_id) ON DELETE SET NULL,
start_latitude NUMERIC(9, 6),
start_longitude NUMERIC(9, 6),
end_latitude NUMERIC(9, 6),
end_longitude NUMERIC(9, 6),
calculated_distance_km NUMERIC(10, 2),
data_source VARCHAR(15) DEFAULT 'Estimated' CHECK (data_source IN ('GPS', 'Manual', 'Estimated')),
billing_status VARCHAR(30) DEFAULT 'PendingEvaluation' CHECK (billing_status IN ('PendingEvaluation', 'BilledTime', 'BilledMileage', 'BilledFlatRate', 'BilledSplit', 'NonBillableFirstLast', 'NonBillablePolicy', 'ManualReviewRequired')),
applied_policy_id UUID REFERENCES TravelBillingPolicies(travel_billing_policy_id) ON DELETE SET NULL, -- Which policy was used
policy_evaluation_notes TEXT, -- Explanation of decision (e.g., "First trip, non-billable")
billed_ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Primary ticket billed to (if not split)
billed_split_ticket_id_1 BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Ticket 1 if split billing
billed_split_ticket_id_2 BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Ticket 2 if split billing
billed_time_entry_id BIGINT REFERENCES TimeEntries(time_entry_id) ON DELETE SET NULL, -- Link to Time Entry if billed by time
billed_product_usage_id BIGINT REFERENCES TicketProductsUsed(ticket_product_id) ON DELETE SET NULL, -- Link to Product Usage if billed by Mileage/FlatRate
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -- When the travel log entry was created
);
CREATE TABLE UserExternalCalendarEvents (
external_event_id VARCHAR(255) PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NOT NULL,
subject VARCHAR(255),
is_all_day BOOLEAN NOT NULL DEFAULT false,
show_as VARCHAR(20), -- 'Free', 'Busy', 'Tentative', 'OutOfOffice'
last_synced_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
source VARCHAR(50)
);
CREATE TABLE SchedulingRequests (
request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_id BIGINT NOT NULL REFERENCES Tickets(ticket_id) ON DELETE CASCADE,
requester_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
requester_email VARCHAR(255),
token VARCHAR(64) NOT NULL UNIQUE,
expires_at TIMESTAMPTZ NOT NULL,
status VARCHAR(20) DEFAULT 'Sent' CHECK (status IN ('Sent', 'Viewed', 'Scheduled', 'Expired', 'Cancelled')),
available_slots JSONB,
selected_slot_start TIMESTAMPTZ,
selected_slot_end TIMESTAMPTZ,
related_schedule_entry_id BIGINT REFERENCES TechnicianSchedules(schedule_entry_id) ON DELETE SET NULL,
sent_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
viewed_at TIMESTAMPTZ,
scheduled_at TIMESTAMPTZ,
cancelled_at TIMESTAMPTZ
);
CREATE TABLE TimeOffTypes (
time_off_type_id SERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
is_paid BOOLEAN NOT NULL DEFAULT true,
debits_allotment BOOLEAN NOT NULL DEFAULT true,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE UserTimeOffAllotments (
allotment_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
time_off_type_id INTEGER NOT NULL REFERENCES TimeOffTypes(time_off_type_id) ON DELETE CASCADE,
year INTEGER NOT NULL,
allotted_hours NUMERIC(10,2) NOT NULL DEFAULT 0.00,
used_hours NUMERIC(10,2) NOT NULL DEFAULT 0.00,
remaining_hours NUMERIC(10,2) GENERATED ALWAYS AS (allotted_hours - used_hours) STORED, -- Calculated
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE(user_id, time_off_type_id, year)
);
CREATE TABLE TimeOffRequests (
request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
time_off_type_id INTEGER NOT NULL REFERENCES TimeOffTypes(time_off_type_id) ON DELETE RESTRICT,
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NOT NULL,
requested_hours NUMERIC(10,2) NOT NULL,
reason TEXT,
status VARCHAR(20) DEFAULT 'Pending' CHECK (status IN ('Pending', 'Approved', 'Rejected', 'Cancelled')),
approval_request_id UUID REFERENCES ApprovalRequests(approval_request_id) ON DELETE SET NULL,
processed_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
processed_at TIMESTAMPTZ,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CHECK (end_time_utc > start_time_utc)
);
CREATE TABLE MaintenanceWindows (
maintenance_window_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org defining the window
name VARCHAR(150) NOT NULL, -- e.g., "Monthly Server Patching", "Network Freeze Q4"
reason TEXT,
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NOT NULL,
is_recurring BOOLEAN NOT NULL DEFAULT false,
recurrence_rule TEXT NULL, -- iCalendar RRULE string or similar representation
scope_type VARCHAR(30) NOT NULL CHECK (scope_type IN ('Global', 'Organization', 'Location', 'Device', 'TagGroup')), -- Scope of the window's impact
scope_entity_id UUID NULL, -- Links to Org, Location, Device based on scope_type. NULL if Global or TagGroup.
target_tag_ids UUID[] NULL, -- Array of Tag IDs if scope_type is TagGroup
pauses_sla_timers BOOLEAN NOT NULL DEFAULT true, -- Does this window pause applicable SLA clocks?
restricts_scheduling BOOLEAN NOT NULL DEFAULT false, -- Prevent scheduling during this window?
restricts_changes BOOLEAN NOT NULL DEFAULT false, -- Prevent change implementations during this window? (Overlap with BlackoutPeriods?)
is_active BOOLEAN NOT NULL DEFAULT true,
created_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
CHECK (end_time_utc > start_time_utc),
CHECK ((scope_type != 'TagGroup' AND scope_entity_id IS NOT NULL) OR (scope_type = 'TagGroup' AND target_tag_ids IS NOT NULL) OR (scope_type = 'Global' AND scope_entity_id IS NULL)),
CHECK ((is_recurring = true AND recurrence_rule IS NOT NULL) OR (is_recurring = false))
);
COMMENT ON TABLE MaintenanceWindows IS 'Defines scheduled maintenance periods that can affect SLA calculations, scheduling, or change implementations.';
COMMENT ON COLUMN MaintenanceWindows.scope_type IS 'Defines the scope affected by this maintenance window (Global, specific Org/Location/Device, or devices/users matching specific Tags).';
COMMENT ON COLUMN MaintenanceWindows.target_tag_ids IS 'Array of Tag UUIDs; applies if scope_type is TagGroup.';
COMMENT ON COLUMN MaintenanceWindows.pauses_sla_timers IS 'If true, SLA timers for affected entities will pause during this window.';
-- =============================================
-- Documentation & Knowledge Management
-- =============================================
CREATE TABLE DocumentTypes (
document_type_id SERIAL PRIMARY KEY,
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_system_defined BOOLEAN NOT NULL DEFAULT false,
is_portal_article BOOLEAN NOT NULL DEFAULT false,
UNIQUE NULLS NOT DISTINCT (org_id, name)
);
CREATE TABLE Documents (
document_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
document_type_id INTEGER NOT NULL REFERENCES DocumentTypes(document_type_id) ON DELETE RESTRICT,
current_revision_id UUID, -- FK
status VARCHAR(20) DEFAULT 'Draft' CHECK (status IN ('Draft', 'Published', 'Archived')),
visibility VARCHAR(20) DEFAULT 'Internal' CHECK (visibility IN ('Internal', 'SpecificRoles', 'SpecificOrgs', 'Public')),
allowed_role_ids UUID[],
allowed_org_ids UUID[],
tags TEXT[],
source_template_id UUID REFERENCES DocumentTemplates(template_id) ON DELETE SET NULL,
source_document_id UUID REFERENCES Documents(document_id) ON DELETE SET NULL,
thumbnail_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
published_at TIMESTAMPTZ,
archived_at TIMESTAMPTZ
);
CREATE TABLE DocumentRevisions (
revision_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
document_id UUID NOT NULL REFERENCES Documents(document_id) ON DELETE CASCADE,
revision_number INTEGER NOT NULL,
content_type VARCHAR(20) NOT NULL DEFAULT 'Markdown' CHECK (content_type IN ('Markdown', 'Html', 'JsonSchema')),
content TEXT NOT NULL,
created_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
change_summary TEXT,
UNIQUE (document_id, revision_number)
);
-- Add FK constraint now that DocumentRevisions exists
ALTER TABLE Documents ADD CONSTRAINT fk_docs_current_revision FOREIGN KEY (current_revision_id) REFERENCES DocumentRevisions(revision_id) ON DELETE SET NULL;
CREATE TABLE DocumentRelatedItems (
document_id UUID NOT NULL REFERENCES Documents(document_id) ON DELETE CASCADE,
target_entity_type VARCHAR(50) NOT NULL,
target_entity_id VARCHAR(50) NOT NULL,
relationship_type VARCHAR(50) DEFAULT 'Related',
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (document_id, target_entity_type, target_entity_id)
);
CREATE TABLE DocumentTemplates (
template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
document_type_id INTEGER NOT NULL REFERENCES DocumentTypes(document_type_id) ON DELETE RESTRICT,
content_type VARCHAR(20) NOT NULL DEFAULT 'Markdown',
template_content TEXT NOT NULL,
variables_schema JSONB,
is_system_defined BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE NULLS NOT DISTINCT (org_id, name)
);
CREATE TABLE DocumentReviewRequests (
review_request_id BIGSERIAL PRIMARY KEY,
document_id UUID NOT NULL REFERENCES Documents(document_id) ON DELETE CASCADE,
revision_id UUID NULL REFERENCES DocumentRevisions(revision_id) ON DELETE SET NULL, -- Specific revision flagged, if any
requesting_user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- Who flagged it
request_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
reason TEXT NOT NULL, -- Why it needs review
suggested_changes TEXT, -- Optional user suggestion
status VARCHAR(20) NOT NULL DEFAULT 'Open' CHECK (status IN ('Open', 'InProgress', 'Resolved', 'Rejected')),
resolver_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- Who handled the review
resolved_at TIMESTAMPTZ NULL,
resolution_notes TEXT, -- Outcome notes
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL -- Optional ticket that triggered the review
);
CREATE TABLE ArticleCategories (
article_category_id SERIAL PRIMARY KEY,
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
parent_category_id INTEGER REFERENCES ArticleCategories(article_category_id) ON DELETE SET NULL,
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT true,
UNIQUE NULLS NOT DISTINCT (org_id, name)
);
CREATE TABLE DocumentArticleCategories (
document_id UUID NOT NULL REFERENCES Documents(document_id) ON DELETE CASCADE,
article_category_id INTEGER NOT NULL REFERENCES ArticleCategories(article_category_id) ON DELETE CASCADE,
PRIMARY KEY (document_id, article_category_id)
);
-- =============================================
-- Learning Management System (LMS)
-- =============================================
CREATE TABLE Courses (
course_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
estimated_duration_seconds INTEGER,
difficulty_level VARCHAR(20) CHECK (difficulty_level IN ('Beginner', 'Intermediate', 'Advanced')),
thumbnail_url TEXT,
is_published BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE QuizDefinitions (
quiz_definition_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
passing_score_percent INTEGER NOT NULL CHECK (passing_score_percent >= 0 AND passing_score_percent <= 100),
time_limit_seconds INTEGER,
randomize_questions BOOLEAN NOT NULL DEFAULT false,
randomize_answers BOOLEAN NOT NULL DEFAULT false,
max_attempts INTEGER,
show_results VARCHAR(20) DEFAULT 'AfterAttempt' CHECK (show_results IN ('AfterAttempt', 'AfterPassing', 'Never')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE CourseLessons (
lesson_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID NOT NULL REFERENCES Courses(course_id) ON DELETE CASCADE,
sequence_order INTEGER NOT NULL DEFAULT 0,
title VARCHAR(255) NOT NULL,
lesson_type VARCHAR(20) NOT NULL CHECK (lesson_type IN ('HtmlContent', 'Video', 'Quiz', 'Attachment', 'ExternalLink')),
html_content TEXT,
video_url TEXT,
external_link_url TEXT,
quiz_definition_id UUID REFERENCES QuizDefinitions(quiz_definition_id) ON DELETE SET NULL,
attachment_id UUID REFERENCES Attachments(attachment_id) ON DELETE SET NULL,
estimated_duration_seconds INTEGER,
is_required BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE QuizQuestions (
question_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
quiz_definition_id UUID NOT NULL REFERENCES QuizDefinitions(quiz_definition_id) ON DELETE CASCADE,
sequence_order INTEGER NOT NULL DEFAULT 0,
question_text TEXT NOT NULL,
question_type VARCHAR(20) NOT NULL CHECK (question_type IN ('MultipleChoiceSingle', 'MultipleChoiceMulti', 'TrueFalse', 'FillInBlank')),
points INTEGER NOT NULL DEFAULT 1,
feedback_correct TEXT,
feedback_incorrect TEXT
);
CREATE TABLE QuizQuestionAnswers (
answer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
question_id UUID NOT NULL REFERENCES QuizQuestions(question_id) ON DELETE CASCADE,
answer_text TEXT NOT NULL,
is_correct BOOLEAN NOT NULL DEFAULT false,
sort_order INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE UserQuizAttempts (
attempt_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
lesson_id UUID NOT NULL REFERENCES CourseLessons(lesson_id) ON DELETE CASCADE,
quiz_definition_id UUID NOT NULL REFERENCES QuizDefinitions(quiz_definition_id) ON DELETE CASCADE,
attempt_number INTEGER NOT NULL,
start_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
end_time TIMESTAMPTZ,
score_achieved INTEGER,
score_possible INTEGER,
passed BOOLEAN,
status VARCHAR(20) DEFAULT 'InProgress' CHECK (status IN ('InProgress', 'Completed'))
);
CREATE TABLE UserQuizAttemptAnswers (
attempt_answer_id BIGSERIAL PRIMARY KEY,
attempt_id UUID NOT NULL REFERENCES UserQuizAttempts(attempt_id) ON DELETE CASCADE,
question_id UUID NOT NULL REFERENCES QuizQuestions(question_id) ON DELETE CASCADE,
selected_answer_ids UUID[],
provided_text TEXT,
is_correct BOOLEAN,
points_awarded INTEGER
);
CREATE TABLE CourseEnrollments (
enrollment_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES Courses(course_id) ON DELETE CASCADE,
enrolled_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'NotStarted' CHECK (status IN ('NotStarted', 'InProgress', 'Completed')),
completed_at TIMESTAMPTZ,
UNIQUE (user_id, course_id)
);
CREATE TABLE CourseAssignments (
assignment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID NOT NULL REFERENCES Courses(course_id) ON DELETE CASCADE,
assigned_to_user_id UUID REFERENCES Users(user_id) ON DELETE CASCADE,
assigned_to_role_id UUID REFERENCES Roles(role_id) ON DELETE CASCADE,
assigned_to_org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
assigned_to_tag_id UUID REFERENCES Tags(tag_id) ON DELETE CASCADE,
assigning_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
due_date DATE,
is_active BOOLEAN NOT NULL DEFAULT true,
CHECK (assigned_to_user_id IS NOT NULL OR assigned_to_role_id IS NOT NULL OR assigned_to_org_id IS NOT NULL OR assigned_to_tag_id IS NOT NULL)
);
CREATE TABLE UserLessonProgress (
progress_id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
lesson_id UUID NOT NULL REFERENCES CourseLessons(lesson_id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES Courses(course_id) ON DELETE CASCADE,
status VARCHAR(20) DEFAULT 'NotStarted' CHECK (status IN ('NotStarted', 'InProgress', 'Completed', 'FailedQuiz')),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
last_quiz_attempt_id UUID REFERENCES UserQuizAttempts(attempt_id) ON DELETE SET NULL,
last_accessed_at TIMESTAMPTZ,
UNIQUE (user_id, lesson_id)
);
CREATE TABLE ChatbotFlows (
flow_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
flow_definition JSONB NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE ChatChannels (
channel_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES Organizations(org_id) ON DELETE SET NULL,
channel_type VARCHAR(20) NOT NULL CHECK (channel_type IN ('DirectMessage', 'Ticket', 'Device', 'General')),
related_ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
related_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
name VARCHAR(255),
ai_context_summary TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE ChannelParticipants (
channel_id UUID NOT NULL REFERENCES ChatChannels(channel_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
joined_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_read_message_id BIGINT, -- FK
is_moderator BOOLEAN DEFAULT false,
PRIMARY KEY (channel_id, user_id)
);
CREATE TABLE ChatMessages (
message_id BIGSERIAL PRIMARY KEY,
channel_id UUID NOT NULL REFERENCES ChatChannels(channel_id) ON DELETE CASCADE,
user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
content_type VARCHAR(20) DEFAULT 'Text' CHECK (content_type IN ('Text', 'Image', 'File', 'SystemEvent')),
message_body TEXT,
related_attachment_id UUID REFERENCES Attachments(attachment_id) ON DELETE SET NULL,
is_edited BOOLEAN DEFAULT false,
edited_at TIMESTAMPTZ,
is_deleted BOOLEAN DEFAULT false,
deleted_at TIMESTAMPTZ
);
-- Add FK constraint now that ChatMessages exists
ALTER TABLE ChannelParticipants ADD CONSTRAINT fk_participants_last_read FOREIGN KEY (last_read_message_id) REFERENCES ChatMessages(message_id) ON DELETE SET NULL;
CREATE TABLE PendingChatRequests (
pending_request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
requestor_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- If initiated by logged-in portal user
requestor_contact_id UUID NULL REFERENCES Contacts(contact_id) ON DELETE SET NULL, -- If matched to a known contact
requestor_guest_name VARCHAR(100) NULL, -- Name if provided by an unauthenticated visitor
requestor_guest_email VARCHAR(255) NULL, -- Email if provided by an unauthenticated visitor
target_team_id UUID NULL REFERENCES Teams(team_id) ON DELETE SET NULL, -- Target support team/queue based on entry point
initial_message TEXT NULL, -- First message from the requestor
status VARCHAR(20) NOT NULL DEFAULT 'Pending' CHECK (status IN ('Pending', 'Accepted', 'Abandoned', 'Timeout')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
accepted_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- Tech who accepted
accepted_at TIMESTAMPTZ NULL,
resulting_channel_id UUID NULL REFERENCES ChatChannels(channel_id) ON DELETE SET NULL, -- Channel created upon acceptance
abandoned_at TIMESTAMPTZ NULL -- If the user left before being connected
-- Add index on status, target_team_id, created_at
);
COMMENT ON TABLE PendingChatRequests IS 'Tracks incoming live chat requests waiting for acceptance by a human agent.';
COMMENT ON COLUMN PendingChatRequests.target_team_id IS 'The internal team designated to handle this type of chat request.';
COMMENT ON COLUMN PendingChatRequests.status IS 'Lifecycle status of the pending chat request.';
CREATE TABLE Surveys (
survey_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
trigger_event VARCHAR(50) NOT NULL CHECK (trigger_event IN ('TicketResolved', 'TicketClosed', 'Manual')),
target_audience VARCHAR(20) DEFAULT 'Requester' CHECK (target_audience IN ('Requester', 'AssignedTech', 'AllContacts')),
delay_after_trigger INTERVAL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE SurveyQuestions (
question_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
survey_id UUID NOT NULL REFERENCES Surveys(survey_id) ON DELETE CASCADE,
sequence_order INTEGER NOT NULL DEFAULT 0,
question_text TEXT NOT NULL,
question_type VARCHAR(20) NOT NULL CHECK (question_type IN ('RatingScale1_5', 'RatingScale1_10', 'NPS', 'YesNo', 'FreeText', 'MultipleChoiceSingle')),
is_required BOOLEAN NOT NULL DEFAULT true,
allow_comments BOOLEAN NOT NULL DEFAULT false,
options JSONB
);
CREATE TABLE SurveyInvitations (
invitation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
survey_id UUID NOT NULL REFERENCES Surveys(survey_id) ON DELETE CASCADE,
user_id UUID REFERENCES Users(user_id) ON DELETE CASCADE,
target_email VARCHAR(255),
related_ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
status VARCHAR(20) DEFAULT 'Sent' CHECK (status IN ('Sent', 'Viewed', 'Started', 'Completed', 'Expired')),
sent_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
viewed_at TIMESTAMPTZ,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
token VARCHAR(64) UNIQUE,
expires_at TIMESTAMPTZ
);
CREATE TABLE SurveyResponses (
response_id BIGSERIAL PRIMARY KEY,
invitation_id UUID NOT NULL REFERENCES SurveyInvitations(invitation_id) ON DELETE CASCADE,
question_id UUID NOT NULL REFERENCES SurveyQuestions(question_id) ON DELETE CASCADE,
rating_score INTEGER,
boolean_answer BOOLEAN,
text_answer TEXT,
selected_option_id VARCHAR(50),
responded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(invitation_id, question_id)
);
CREATE TABLE ApprovalWorkflowDefinitions (
definition_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
approval_logic JSONB NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE ApprovalRequests (
approval_request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
definition_id UUID REFERENCES ApprovalWorkflowDefinitions(definition_id) ON DELETE SET NULL,
requesting_user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE RESTRICT,
related_entity_type VARCHAR(50) NOT NULL,
related_entity_id VARCHAR(50) NOT NULL,
status VARCHAR(20) DEFAULT 'Pending' CHECK (status IN ('Pending', 'Approved', 'Rejected', 'Cancelled')),
decision_summary TEXT,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE ApprovalSteps (
approval_step_id BIGSERIAL PRIMARY KEY,
approval_request_id UUID NOT NULL REFERENCES ApprovalRequests(approval_request_id) ON DELETE CASCADE,
step_order INTEGER NOT NULL,
step_name VARCHAR(100),
approver_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
approver_role_id UUID REFERENCES Roles(role_id) ON DELETE SET NULL,
approval_type VARCHAR(20) DEFAULT 'Any' CHECK (approval_type IN ('Any', 'All')),
status VARCHAR(20) DEFAULT 'Pending' CHECK (status IN ('Pending', 'Approved', 'Rejected', 'Skipped')),
decision_made_at TIMESTAMPTZ,
due_date TIMESTAMPTZ,
CHECK (approver_user_id IS NOT NULL OR approver_role_id IS NOT NULL)
);
CREATE TABLE ApprovalStepActions (
action_id BIGSERIAL PRIMARY KEY,
approval_step_id BIGINT NOT NULL REFERENCES ApprovalSteps(approval_step_id) ON DELETE CASCADE,
acting_user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE RESTRICT,
action VARCHAR(10) NOT NULL CHECK (action IN ('Approve', 'Reject', 'Comment')),
comment TEXT,
action_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- =============================================
-- Identity & Security Monitoring
-- =============================================
CREATE TABLE PasswordSecurityFindings (
finding_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL,
account_name VARCHAR(255) NOT NULL,
account_type VARCHAR(10) NOT NULL CHECK (account_type IN ('Local', 'Domain')),
domain_name VARCHAR(255),
weakness_type VARCHAR(50) NOT NULL,
details TEXT,
matched_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
first_detected_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_detected_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'Active' CHECK (status IN ('Active', 'Remediated', 'Acknowledged', 'Ignored')),
remediation_ticket_id BIGINT REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
remediated_at TIMESTAMPTZ,
acknowledged_at TIMESTAMPTZ,
ignored_until TIMESTAMPTZ
-- UNIQUE constraint for active findings handled by index if needed
);
CREATE TABLE DarkWebBreachEvents (
breach_event_id BIGSERIAL PRIMARY KEY,
user_email_id BIGINT REFERENCES UserEmails(user_email_id) ON DELETE SET NULL,
email_address VARCHAR(255) NOT NULL,
source_breach_name VARCHAR(255),
breach_date DATE,
discovered_date DATE NOT NULL DEFAULT CURRENT_DATE,
compromised_data_types TEXT[],
password_hash_type VARCHAR(50),
password_hash TEXT,
password_plaintext_available BOOLEAN NOT NULL DEFAULT false,
details_url TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'New' CHECK (status IN ('New', 'Acknowledged', 'Remediated', 'Ignored')),
acknowledged_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
acknowledged_at TIMESTAMPTZ,
remediation_notes TEXT,
first_imported_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE CredentialTypes (
credential_type_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT
);
CREATE TABLE CredentialFolders (
folder_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
parent_folder_id UUID REFERENCES CredentialFolders(folder_id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, parent_folder_id, name)
);
CREATE TABLE Credentials (
credential_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
folder_id UUID REFERENCES CredentialFolders(folder_id) ON DELETE SET NULL,
credential_type_id INTEGER REFERENCES CredentialTypes(credential_type_id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
username VARCHAR(255),
url TEXT,
notes TEXT,
expiry_date DATE,
external_vault_ref TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE CredentialPermissions (
permission_id BIGSERIAL PRIMARY KEY,
credential_id UUID NOT NULL REFERENCES Credentials(credential_id) ON DELETE CASCADE,
role_id UUID REFERENCES Roles(role_id) ON DELETE CASCADE,
user_id UUID REFERENCES Users(user_id) ON DELETE CASCADE,
permission_level VARCHAR(10) NOT NULL CHECK (permission_level IN ('View', 'Link')),
granted_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
granted_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
CHECK (role_id IS NOT NULL OR user_id IS NOT NULL),
UNIQUE (credential_id, role_id, user_id)
);
CREATE TABLE CredentialLinks (
credential_id UUID NOT NULL REFERENCES Credentials(credential_id) ON DELETE CASCADE,
target_entity_type VARCHAR(50) NOT NULL,
target_entity_id VARCHAR(50) NOT NULL,
linked_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
linked_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
PRIMARY KEY (credential_id, target_entity_type, target_entity_id)
);
-- =============================================
-- Agent & Integration Support
-- =============================================
CREATE TABLE AgentCommandQueue (
command_queue_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
command_type VARCHAR(50) NOT NULL,
script_id UUID REFERENCES Scripts(script_id) ON DELETE SET NULL,
command_parameters JSONB,
status VARCHAR(20) DEFAULT 'Pending' CHECK (status IN ('Pending', 'SentToAgent', 'InProgress', 'Completed', 'Failed', 'Timeout', 'Cancelled')),
priority INTEGER DEFAULT 100,
queued_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
sent_at TIMESTAMPTZ,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
result_code INTEGER,
result_output TEXT,
created_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL
);
CREATE TABLE AgentUpdatePolicies (
update_policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP Org defining the policy
name VARCHAR(150) NOT NULL,
description TEXT NULL,
target_service_version VARCHAR(50) NOT NULL DEFAULT 'latest_stable', -- e.g., 'latest_stable', 'beta', '1.2.3'
target_ui_version VARCHAR(50) NOT NULL DEFAULT 'latest_stable',
target_probe_version VARCHAR(50) NOT NULL DEFAULT 'latest_stable',
target_updater_version VARCHAR(50) NOT NULL DEFAULT 'latest_stable',
update_schedule JSONB NULL, -- Define update window, staggering (e.g., { "window_start_local": "22:00", "window_end_local": "04:00", "days_of_week": [0,1,2,3,4,5,6], "stagger_minutes": 120 })
allow_manual_update BOOLEAN NOT NULL DEFAULT true, -- Allow manual trigger of updates outside schedule
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (org_id, name)
);
COMMENT ON TABLE AgentUpdatePolicies IS 'Defines policies for controlling agent component updates.';
COMMENT ON COLUMN AgentUpdatePolicies.target_service_version IS 'Desired version for the agent service component (can be specific or dynamic like ''latest_stable'').';
COMMENT ON COLUMN AgentUpdatePolicies.update_schedule IS 'JSONB defining the schedule and method for applying updates (e.g., time windows, staggering).';
CREATE TABLE AgentConfigurations (
agent_config_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP Org defining the configuration
name VARCHAR(150) NOT NULL,
description TEXT NULL,
check_in_interval_seconds INTEGER NOT NULL DEFAULT 300 CHECK (check_in_interval_seconds >= 60),
log_level VARCHAR(10) NOT NULL DEFAULT 'Info' CHECK (log_level IN ('Debug', 'Info', 'Warning', 'Error')),
enabled_features TEXT[] NULL, -- Array of features enabled by this config (e.g., ['Monitoring', 'Patching', 'RemoteControl', 'ProbeNetworkScan'])
update_policy_id UUID NULL REFERENCES AgentUpdatePolicies(update_policy_id) ON DELETE SET NULL, -- Link to the desired update policy
probe_config JSONB NULL, -- Specific settings for the Probe component (e.g., { "scan_threads": 4, "default_snmp_community": "public", "scan_subnets": ["192.168.1.0/24"] })
is_default_for_org BOOLEAN NOT NULL DEFAULT false, -- Is this the default config for new agents in the org?
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (org_id, name)
);
COMMENT ON TABLE AgentConfigurations IS 'Defines configuration profiles for CommandIT agents.';
COMMENT ON COLUMN AgentConfigurations.enabled_features IS 'Array listing the features/modules activated by this configuration profile.';
COMMENT ON COLUMN AgentConfigurations.probe_config IS 'JSONB containing specific configuration parameters for the network probe component.';
CREATE TABLE DeviceAgentConfigAssignments (
device_id UUID PRIMARY KEY REFERENCES Devices(device_id) ON DELETE CASCADE, -- One config per device
agent_config_id UUID NOT NULL REFERENCES AgentConfigurations(agent_config_id) ON DELETE RESTRICT, -- Don't delete config if assigned
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
assigned_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL
);
COMMENT ON TABLE DeviceAgentConfigAssignments IS 'Assigns an Agent Configuration profile to a specific device.';
CREATE TABLE AgentUpdateStatusLog (
update_log_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
component_name VARCHAR(50) NOT NULL CHECK (component_name IN ('Service', 'UI', 'Probe', 'Updater', 'AgentBundle')), -- Component being updated
trigger_source VARCHAR(50) NULL, -- e.g., 'ScheduledPolicy', 'ManualCommand', 'InitialInstall'
requested_version VARCHAR(50) NOT NULL, -- The version the update process aimed for
current_version VARCHAR(50) NULL, -- Version before the update attempt started
status VARCHAR(20) NOT NULL CHECK (status IN ('Pending', 'Downloading', 'Installing', 'Success', 'Failed', 'Cancelled', 'Skipped')),
attempt_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the update process started
completion_timestamp TIMESTAMPTZ NULL, -- When the update process finished (successfully or not)
error_message TEXT NULL, -- Details if status is 'Failed'
details JSONB NULL -- Additional details (e.g., download size, exit code)
);
COMMENT ON TABLE AgentUpdateStatusLog IS 'Logs the status and outcome of agent component update attempts on devices.';
COMMENT ON COLUMN AgentUpdateStatusLog.component_name IS 'The specific agent component targeted by the update.';
CREATE TABLE AgentEnrollmentTokens (
token_hash VARCHAR(64) PRIMARY KEY, -- Hash of the enrollment token
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org this token is valid for
description TEXT NULL, -- Optional description (e.g., "Token for new server deployments")
expires_at TIMESTAMPTZ NOT NULL, -- When the token becomes invalid
max_uses INTEGER NULL, -- Optional: Limit number of times token can be used (NULL for unlimited)
current_uses INTEGER NOT NULL DEFAULT 0,
used_by_device_ids UUID[] NULL, -- Optional: Track which devices used this token
is_active BOOLEAN NOT NULL DEFAULT true, -- Can this token currently be used?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL
);
COMMENT ON TABLE AgentEnrollmentTokens IS 'Stores temporary tokens used for enrolling new CommandIT agents.';
COMMENT ON COLUMN AgentEnrollmentTokens.token_hash IS 'A secure hash of the enrollment token provided to the agent installer.';
CREATE TABLE IntegrationInstances (
instance_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
integration_type VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL UNIQUE,
configuration JSONB NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT true,
status VARCHAR(20) DEFAULT 'Ok' CHECK (status IN ('Ok', 'Error', 'Disabled', 'Syncing')),
last_sync_time TIMESTAMPTZ,
last_error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE AiTools (
tool_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT NOT NULL,
parameters_schema JSONB,
implementation_details JSONB,
is_active BOOLEAN NOT NULL DEFAULT true
);
CREATE TABLE AiAgentConfigs (
config_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
system_prompt TEXT NOT NULL,
llm_model_name VARCHAR(100) NOT NULL,
model_parameters JSONB,
is_platform_defined BOOLEAN NOT NULL DEFAULT false
);
CREATE TABLE AiAgentConfigTools (
config_id UUID NOT NULL REFERENCES AiAgentConfigs(config_id) ON DELETE CASCADE,
tool_id UUID NOT NULL REFERENCES AiTools(tool_id) ON DELETE CASCADE,
PRIMARY KEY (config_id, tool_id)
);
CREATE TABLE SpAiAgentOverrides (
override_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sp_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
base_config_id UUID NOT NULL REFERENCES AiAgentConfigs(config_id) ON DELETE RESTRICT,
display_name VARCHAR(100),
avatar_url TEXT,
prompt_additions TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
UNIQUE(sp_org_id, base_config_id)
);
CREATE TABLE MagicLoginTokens (
token_hash VARCHAR(64) PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
used_at TIMESTAMPTZ
);
CREATE TABLE SsoTokens (
token_hash VARCHAR(64) PRIMARY KEY,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
used_at TIMESTAMPTZ,
intended_url TEXT
);
-- =============================================
-- Ancillary / Specific Modules
-- =============================================
CREATE TABLE RiskRegister (
risk_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
risk_identifier VARCHAR(50) NOT NULL UNIQUE,
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
owner_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
likelihood VARCHAR(20),
impact VARCHAR(20),
risk_score INTEGER,
status VARCHAR(20) DEFAULT 'Open' CHECK (status IN ('Open', 'Mitigating', 'Monitoring', 'Closed', 'Accepted')),
treatment_plan TEXT,
last_assessed_at DATE,
next_review_date DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE RiskControls (
risk_id UUID NOT NULL REFERENCES RiskRegister(risk_id) ON DELETE CASCADE,
control_id UUID NOT NULL REFERENCES ComplianceFrameworkControls(control_id) ON DELETE CASCADE,
PRIMARY KEY (risk_id, control_id)
);
CREATE TABLE VendorContracts (
contract_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
vendor_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
managing_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
contract_name VARCHAR(255) NOT NULL,
contract_number VARCHAR(100),
start_date DATE NOT NULL,
end_date DATE,
renewal_type VARCHAR(20) CHECK (renewal_type IN ('AutoRenew', 'Manual', 'None')),
notice_period_days INTEGER,
status VARCHAR(20) DEFAULT 'Active' CHECK (status IN ('Draft', 'Active', 'Expired', 'Terminated')),
value NUMERIC(19,4),
currency_code VARCHAR(3),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE VendorContractAttachments (
contract_id UUID NOT NULL REFERENCES VendorContracts(contract_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
description TEXT,
attached_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (contract_id, attachment_id)
);
CREATE TABLE VendorRiskAssessments (
assessment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
vendor_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
managing_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
assessment_date DATE NOT NULL DEFAULT CURRENT_DATE,
assessor_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
risk_score INTEGER,
summary TEXT,
recommendation TEXT,
next_assessment_date DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE BcdrPlans (
plan_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
plan_type VARCHAR(50) CHECK (plan_type IN ('BCP', 'DRP', 'IncidentResponse')),
owner_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
status VARCHAR(20) DEFAULT 'Draft' CHECK (status IN ('Draft', 'Active', 'Archived')),
document_id UUID REFERENCES Documents(document_id) ON DELETE SET NULL,
recovery_time_objective_seconds INTEGER,
recovery_point_objective_seconds INTEGER,
last_reviewed_at DATE,
next_review_date DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE BcdrPlanAssets (
bcdr_plan_asset_id BIGSERIAL PRIMARY KEY,
plan_id UUID NOT NULL REFERENCES BcdrPlans(plan_id) ON DELETE CASCADE,
target_entity_type VARCHAR(50) NOT NULL, -- e.g., 'Device', 'Application', 'Location'
target_entity_id VARCHAR(50) NOT NULL, -- UUID or other ID
criticality VARCHAR(20) CHECK (criticality IN ('VeryHigh', 'High', 'Medium', 'Low')),
recovery_strategy VARCHAR(30) NULL CHECK (recovery_strategy IN ('HotStandby', 'WarmStandby', 'ColdRestoreBackup', 'ReplicateAndRestore', 'RebuildManual', 'Other')),
required_backup_policy_id UUID NULL REFERENCES BackupPolicies(policy_id) ON DELETE SET NULL, -- Link to expected backup policy
recovery_notes TEXT, -- Specific recovery steps/notes for this asset
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (plan_id, target_entity_type, target_entity_id)
);
COMMENT ON COLUMN BcdrPlanAssets.recovery_strategy IS 'Defines the planned recovery method for this asset (e.g., HotStandby, ColdRestoreBackup).';
COMMENT ON COLUMN BcdrPlanAssets.required_backup_policy_id IS 'Optional: Link to the BackupPolicy expected to meet the RPO requirements for this critical asset.';
CREATE TABLE BcdrTests (
test_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
plan_id UUID NOT NULL REFERENCES BcdrPlans(plan_id) ON DELETE CASCADE,
test_date DATE NOT NULL,
test_type VARCHAR(50),
scenario TEXT,
outcome VARCHAR(20) CHECK (outcome IN ('Success', 'PartialSuccess', 'Failure')),
summary TEXT,
duration_seconds INTEGER,
lead_tester_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE BcdrTestParticipants (
test_id UUID NOT NULL REFERENCES BcdrTests(test_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
role_in_test VARCHAR(100),
PRIMARY KEY (test_id, user_id)
);
CREATE TABLE Projects (
project_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org project is for
managing_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP Org managing the project
project_number VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(20) DEFAULT 'Planning' CHECK (status IN ('Planning', 'Active', 'OnHold', 'Completed', 'Cancelled')),
project_manager_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
start_date DATE,
end_date DATE,
estimated_budget NUMERIC(19,4),
actual_cost NUMERIC(19,4),
currency_code VARCHAR(3) NULL, -- Currency for budget/cost fields (e.g., 'CAD', 'USD')
estimated_hours NUMERIC(10,2),
actual_hours NUMERIC(10,2),
inbound_email_address VARCHAR(255) UNIQUE NULL, -- Unique email address for sending updates directly to this project
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated by trigger
);
COMMENT ON COLUMN Projects.inbound_email_address IS 'Unique email address ({guid}@commandit.net) for sending updates directly to this project.';
CREATE TABLE ProjectPhases (
phase_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES Projects(project_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
sequence_order INTEGER NOT NULL DEFAULT 0,
start_date DATE,
end_date DATE,
status VARCHAR(20) DEFAULT 'NotStarted' CHECK (status IN ('NotStarted', 'InProgress', 'Completed', 'Skipped')),
UNIQUE (project_id, name)
);
CREATE TABLE ProjectTasks (
project_task_id BIGSERIAL PRIMARY KEY,
project_id UUID NOT NULL REFERENCES Projects(project_id) ON DELETE CASCADE,
phase_id UUID REFERENCES ProjectPhases(phase_id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(20) DEFAULT 'NotStarted' CHECK (status IN ('NotStarted', 'InProgress', 'Completed', 'OnHold', 'Cancelled')),
assigned_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
work_type_id UUID REFERENCES WorkTypes(work_type_id) ON DELETE SET NULL,
estimated_hours NUMERIC(10,2),
actual_hours NUMERIC(10,2) DEFAULT 0.00,
start_date DATE,
due_date DATE,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE ProjectTaskDependencies (
task_id BIGINT NOT NULL REFERENCES ProjectTasks(project_task_id) ON DELETE CASCADE,
predecessor_task_id BIGINT NOT NULL REFERENCES ProjectTasks(project_task_id) ON DELETE CASCADE,
dependency_type VARCHAR(10) DEFAULT 'FS' CHECK (dependency_type IN ('FS', 'SS', 'FF', 'SF')),
lag_days INTEGER DEFAULT 0,
PRIMARY KEY (task_id, predecessor_task_id),
CHECK (task_id != predecessor_task_id)
);
CREATE TABLE PortalBrandingSettings (
org_id UUID PRIMARY KEY REFERENCES Organizations(org_id) ON DELETE CASCADE,
primary_color VARCHAR(7),
secondary_color VARCHAR(7),
logo_override_url TEXT,
favicon_url TEXT,
portal_title VARCHAR(100),
updated_at TIMESTAMPTZ -- Auto-updated
);
-- Stores notes/email logs for projects
CREATE TABLE ProjectUpdates (
project_update_id BIGSERIAL PRIMARY KEY,
project_id UUID NOT NULL REFERENCES Projects(project_id) ON DELETE CASCADE,
user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- User or AI creating the update
timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
body TEXT NOT NULL,
note_type VARCHAR(30) NULL CHECK (note_type IN ('Standard', 'Internal', 'EmailLog', 'SystemEvent')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_projectupdates_project_id ON ProjectUpdates(project_id, timestamp DESC);
COMMENT ON TABLE ProjectUpdates IS 'Stores notes, logs of emails received via project email address, and other updates related to a project.';
-- Links attachments to project updates
CREATE TABLE ProjectUpdateAttachments (
project_update_attachment_id BIGSERIAL PRIMARY KEY,
project_update_id BIGINT NOT NULL REFERENCES ProjectUpdates(project_update_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (project_update_id, attachment_id)
);
COMMENT ON TABLE ProjectUpdateAttachments IS 'Links file attachments (stored in Attachments table) to specific ProjectUpdates.';
-- =============================================
-- Automations
-- =============================================
CREATE TABLE Automations (
automation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_org_id UUID REFERENCES Organizations(org_id) ON DELETE CASCADE,
scope_type VARCHAR(20) NOT NULL DEFAULT 'Organization' CHECK (scope_type IN ('Global', 'Organization')),
scope_id UUID NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
automation_type VARCHAR(20) NOT NULL CHECK (automation_type IN ('Script', 'Workflow')),
script_id UUID REFERENCES Scripts(script_id) ON DELETE SET NULL,
workflow_definition JSONB,
target_os_types TEXT[], -- e.g., ['Windows'], ['Linux', 'macOS'], NULL/['Any']
target_cpu_architectures TEXT[], -- e.g., ['x86_64'], ['arm64'], NULL/['Any']
input_parameters_schema JSONB,
output_variables JSONB,
execution_timeout_seconds INTEGER DEFAULT 300,
is_system_defined BOOLEAN NOT NULL DEFAULT false,
is_available_adhoc BOOLEAN NOT NULL DEFAULT true,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT automations_owner_scope_name_unique UNIQUE NULLS NOT DISTINCT (owner_org_id, scope_type, scope_id, name)
);
CREATE TABLE AutomationSchedules (
automation_schedule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
definer_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
automation_id UUID NOT NULL REFERENCES Automations(automation_id) ON DELETE RESTRICT,
input_variable_values JSONB,
approval_request_id UUID NULL REFERENCES ApprovalRequests(approval_request_id) ON DELETE SET NULL,
-- Targeting Definition
target_scope_type VARCHAR(20) CHECK (target_scope_type IN ('Organization', 'Location', 'Device', 'TagGroup')),
target_scope_id UUID NULL,
-- Scheduling Definition
schedule_type VARCHAR(20) NOT NULL CHECK (schedule_type IN ('OneTime', 'Hourly', 'Daily', 'Weekly', 'Monthly')),
one_time_run_at_utc TIMESTAMPTZ NULL,
interval INTEGER DEFAULT 1 CHECK (interval >= 1),
days_of_week INTEGER[],
months_of_year INTEGER[],
monthly_occurrence_type VARCHAR(30) CHECK (monthly_occurrence_type IN ('SpecificDay', 'OrdinalDayOfMonth', 'LastDay', 'WeekdayOfMonth', 'WeekendDayOfMonth')),
monthly_day_of_month INTEGER,
monthly_ordinal INTEGER,
monthly_day_specifier VARCHAR(20),
scheduled_time_local TIME NOT NULL, -- Defines time for non-hourly, start for hourly? Needs clarification in logic
minute_offset INTEGER NULL CHECK (minute_offset >= 0 AND minute_offset < 60),
target_timezone_mode VARCHAR(20) NOT NULL DEFAULT 'DeviceLocal' CHECK (target_timezone_mode IN ('DeviceLocal', 'SpecificTimezone')),
specific_timezone VARCHAR(100),
-- Execution Options
override_execution_timeout_seconds INTEGER NULL,
set_downtime_during_execution BOOLEAN NOT NULL DEFAULT false,
missed_execution_behavior VARCHAR(10) NOT NULL DEFAULT 'RunAsap' CHECK (missed_execution_behavior IN ('RunAsap', 'Skip')),
run_asap_within_seconds INTEGER NULL,
-- Execution Tracking
calculated_next_run_utc TIMESTAMPTZ NULL,
last_run_start_time_utc TIMESTAMPTZ NULL,
error_message TEXT,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
-- Constraints
UNIQUE(definer_org_id, name),
CHECK (
(target_scope_type != 'TagGroup' AND target_scope_id IS NOT NULL) OR
(target_scope_type = 'TagGroup' AND target_scope_id IS NULL)
),
CHECK (
(schedule_type = 'OneTime' AND one_time_run_at_utc IS NOT NULL) OR
(schedule_type != 'OneTime' AND one_time_run_at_utc IS NULL)
)
);
CREATE TABLE AutomationScheduleTagTargets ( -- M2M for Tag targeting
automation_schedule_id UUID NOT NULL REFERENCES AutomationSchedules(automation_schedule_id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE,
PRIMARY KEY (automation_schedule_id, tag_id)
);
CREATE TABLE AutomationScheduleExecutions ( -- Tracks individual device runs triggered by a schedule
execution_id BIGSERIAL PRIMARY KEY,
automation_schedule_id UUID NOT NULL REFERENCES AutomationSchedules(automation_schedule_id) ON DELETE CASCADE,
target_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
scheduled_run_time_utc TIMESTAMPTZ NOT NULL, -- Calculated UTC time for this specific device run
command_queue_id BIGINT REFERENCES AgentCommandQueue(command_queue_id) ON DELETE SET NULL, -- Link to the actual command execution
status VARCHAR(20) NOT NULL DEFAULT 'Pending' CHECK (status IN ('Pending', 'Queued', 'Running', 'Completed', 'Failed', 'SkippedTimeZone')),
start_time_utc TIMESTAMPTZ NULL, -- When AgentCommandQueue entry started
end_time_utc TIMESTAMPTZ NULL, -- When AgentCommandQueue entry finished
result_summary TEXT, -- Brief result or error message
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -- When this execution record was planned
);
CREATE TABLE AutomationComponents (
component_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE, -- User-facing name (e.g., "Run Script")
category_id INTEGER NULL REFERENCES AutomationComponentCategories(category_id) ON DELETE SET NULL, -- Link to category
description TEXT, -- Help Pane: Detailed description/tooltip
icon VARCHAR(100), -- Optional UI icon
component_type VARCHAR(20) NOT NULL CHECK (component_type IN ('Script', 'Workflow', 'AgentAction', 'ApiCall', 'ControlFlow', 'AiTool')),
implementation_ref VARCHAR(255), -- Reference based on component_type
input_schema JSONB, -- Help Pane: Defines Input Parameters (name, type, description, required, example) using JSON Schema
output_schema JSONB, -- Help Pane: Defines Output Parameters (name, type, description) using JSON Schema or array
minimum_agent_version VARCHAR(30) NULL, -- Help Pane: Minimum agent version required (e.g., "1.2.0")
is_system_defined BOOLEAN NOT NULL DEFAULT true,
is_enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
CREATE TABLE AutomationComponentCategories (
category_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, -- Name of the category (e.g., "Control Flow", "Filesystem")
parent_category_id INTEGER NULL REFERENCES AutomationComponentCategories(category_id) ON DELETE CASCADE, -- For hierarchical categories
description TEXT NULL, -- Optional description
sort_order INTEGER NOT NULL DEFAULT 0, -- Controls display order in the UI tree
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
CONSTRAINT category_cannot_be_own_parent CHECK (category_id != parent_category_id)
-- Note: If names only need to be unique WITHIN a parent, adjust the UNIQUE constraint:
-- UNIQUE (parent_category_id, name) -- Requires careful handling of NULL parent_category_id
);
CREATE TABLE UserFavoriteAutomationComponents (
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE, -- User who favorited
component_id UUID NOT NULL REFERENCES AutomationComponents(component_id) ON DELETE CASCADE, -- Component that was favorited
added_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When it was favorited
PRIMARY KEY (user_id, component_id) -- Ensures a user can only favorite a component once
);
CREATE TABLE Notes (
note_id BIGSERIAL PRIMARY KEY,
target_entity_type VARCHAR(50) NOT NULL, -- e.g., 'Organization', 'Location', 'User', 'Device', 'Ticket', 'Peripheral', 'Agreement', 'SoftwareProduct', 'WirelessNetwork', 'EquipmentAsset'
target_entity_id VARCHAR(50) NOT NULL, -- Stores UUID or BIGINT/INT as string based on entity type
note_type VARCHAR(30) NULL CHECK (note_type IN ('Description', 'Comment', 'Update', 'Alert', 'SystemLog', 'FlaggedNote')), -- Consider refining this enum based on actual use
note_content TEXT NOT NULL, -- The content of the note
is_pinned BOOLEAN NOT NULL DEFAULT false, -- Renamed from is_flagged: If true, indicates note should be displayed prominently (pinned)
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
updated_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL
);
-- Comments
COMMENT ON TABLE Notes IS 'Stores notes associated with various entities (Devices, Tickets, Orgs, etc.) using polymorphic association.';
COMMENT ON COLUMN Notes.target_entity_type IS 'The type of entity this note is linked to (e.g., ''Device'', ''Ticket'').';
COMMENT ON COLUMN Notes.target_entity_id IS 'The primary key (UUID, ID) of the entity this note is linked to.';
COMMENT ON COLUMN Notes.is_pinned IS 'If true, indicates this note should be displayed prominently or pinned at the top in UI views.';
-- Indexes
CREATE INDEX idx_notes_target ON Notes(target_entity_type, target_entity_id);
CREATE INDEX idx_notes_pinned_target ON Notes(target_entity_type, target_entity_id, is_pinned, created_at DESC); -- Index to efficiently retrieve pinned notes first
CREATE TABLE PortalNotices (
portal_notice_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
creator_org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org creating/managing the notice
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL, -- Content to display (supports Markdown/HTML?)
notice_level VARCHAR(20) DEFAULT 'Info' CHECK (notice_level IN ('Info', 'Warning', 'Critical', 'Maintenance')), -- For UI styling
display_start_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When notice becomes visible
display_end_time TIMESTAMPTZ NULL, -- Optional: When notice automatically hides (NULL = indefinite)
-- Targeting Scope
scope_type VARCHAR(20) NOT NULL CHECK (scope_type IN ('GlobalToClients', 'Organization', 'Location')),
scope_entity_id UUID NULL, -- Stores target OrgID or LocationID based on scope_type. NULL for GlobalToClients.
target_role_ids UUID[] NULL, -- Array of Role IDs that can see this notice. NULL/Empty means all roles within scope.
-- Status & Timestamps
is_active BOOLEAN NOT NULL DEFAULT true, -- Master enabled/disabled toggle
created_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
-- Constraints
CHECK (
(scope_type = 'GlobalToClients' AND scope_entity_id IS NULL) OR
(scope_type = 'Organization' AND scope_entity_id IS NOT NULL) OR
(scope_type = 'Location' AND scope_entity_id IS NOT NULL)
)
);
-- =============================================
-- Active Directory Object Synchronization
-- =============================================
CREATE TABLE AdDomains (
ad_domain_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Owning Org
domain_name VARCHAR(255) NOT NULL UNIQUE, -- Fully qualified domain name (e.g., ad.example.com)
netbios_name VARCHAR(15) UNIQUE,
domain_sid VARCHAR(100) UNIQUE, -- Security Identifier for the domain
functional_level VARCHAR(50), -- e.g., 'Windows2016Server'
monitored_by_probe_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL, -- Which probe/agent monitors this domain
-- FSMO Role Holders (Links to AdComputers which should represent DCs)
fsmo_schema_master_dc_id UUID NULL REFERENCES AdComputers(ad_computer_id) ON DELETE SET NULL,
fsmo_domain_naming_master_dc_id UUID NULL REFERENCES AdComputers(ad_computer_id) ON DELETE SET NULL,
fsmo_pdc_emulator_dc_id UUID NULL REFERENCES AdComputers(ad_computer_id) ON DELETE SET NULL,
fsmo_rid_master_dc_id UUID NULL REFERENCES AdComputers(ad_computer_id) ON DELETE SET NULL,
fsmo_infrastructure_master_dc_id UUID NULL REFERENCES AdComputers(ad_computer_id) ON DELETE SET NULL,
last_sync_time TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT true, -- Is monitoring enabled?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE AdDomains IS 'Represents discovered Active Directory domains and their core properties, including FSMO role holders.';
COMMENT ON COLUMN AdDomains.fsmo_schema_master_dc_id IS 'Link to the AdComputers record for the DC holding the Schema Master role.';
COMMENT ON COLUMN AdDomains.fsmo_domain_naming_master_dc_id IS 'Link to the AdComputers record for the DC holding the Domain Naming Master role.';
COMMENT ON COLUMN AdDomains.fsmo_pdc_emulator_dc_id IS 'Link to the AdComputers record for the DC holding the PDC Emulator role.';
COMMENT ON COLUMN AdDomains.fsmo_rid_master_dc_id IS 'Link to the AdComputers record for the DC holding the RID Master role.';
COMMENT ON COLUMN AdDomains.fsmo_infrastructure_master_dc_id IS 'Link to the AdComputers record for the DC holding the Infrastructure Master role.';
CREATE TABLE AdOrganizationalUnits (
ad_ou_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
ad_domain_id UUID NOT NULL REFERENCES AdDomains(ad_domain_id) ON DELETE CASCADE,
object_guid UUID UNIQUE, -- AD GUID (if available and reliable)
distinguished_name TEXT NOT NULL UNIQUE, -- Full DN (e.g., OU=Sales,DC=ad,DC=example,DC=com)
name VARCHAR(255) NOT NULL, -- Name of the OU itself
parent_ou_id UUID REFERENCES AdOrganizationalUnits(ad_ou_id) ON DELETE CASCADE, -- Self-reference for hierarchy
gpo_link_order JSONB, -- Optional: Store GPO link order if needed
is_protected BOOLEAN DEFAULT false, -- Accidental deletion protection flag in AD
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE AdUsers (
ad_user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
ad_domain_id UUID NOT NULL REFERENCES AdDomains(ad_domain_id) ON DELETE CASCADE,
object_guid UUID UNIQUE, -- AD GUID
object_sid VARCHAR(100) UNIQUE, -- AD SID
sam_account_name VARCHAR(256) NOT NULL,
user_principal_name VARCHAR(255),
distinguished_name TEXT NOT NULL UNIQUE,
display_name VARCHAR(255),
first_name VARCHAR(100),
last_name VARCHAR(100),
email_address VARCHAR(255),
description TEXT,
is_enabled BOOLEAN,
is_locked_out BOOLEAN,
last_logon_timestamp TIMESTAMPTZ,
last_bad_password_time TIMESTAMPTZ,
bad_password_count INTEGER,
password_last_set TIMESTAMPTZ,
password_expires TIMESTAMPTZ,
password_never_expires BOOLEAN,
cannot_change_password BOOLEAN,
smart_card_required BOOLEAN,
when_created TIMESTAMPTZ, -- Timestamp from AD
when_changed TIMESTAMPTZ, -- Timestamp from AD
commandit_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- Optional link to matched CommandIT User
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (ad_domain_id, sam_account_name)
);
CREATE TABLE AdGroups (
ad_group_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
ad_domain_id UUID NOT NULL REFERENCES AdDomains(ad_domain_id) ON DELETE CASCADE,
object_guid UUID UNIQUE,
object_sid VARCHAR(100) UNIQUE,
sam_account_name VARCHAR(256) NOT NULL,
distinguished_name TEXT NOT NULL UNIQUE,
group_scope VARCHAR(20), -- 'DomainLocal', 'Global', 'Universal'
group_type VARCHAR(20), -- 'Security', 'Distribution'
description TEXT,
email_address VARCHAR(255),
when_created TIMESTAMPTZ,
when_changed TIMESTAMPTZ,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (ad_domain_id, sam_account_name)
);
CREATE TABLE AdComputers (
ad_computer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
ad_domain_id UUID NOT NULL REFERENCES AdDomains(ad_domain_id) ON DELETE CASCADE,
object_guid UUID UNIQUE,
object_sid VARCHAR(100) UNIQUE,
sam_account_name VARCHAR(256) NOT NULL, -- Usually hostname$
dns_hostname VARCHAR(255),
distinguished_name TEXT NOT NULL UNIQUE,
operating_system VARCHAR(255),
operating_system_version VARCHAR(50),
description TEXT,
is_enabled BOOLEAN,
last_logon_timestamp TIMESTAMPTZ,
password_last_set TIMESTAMPTZ,
when_created TIMESTAMPTZ,
when_changed TIMESTAMPTZ,
commandit_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL, -- Optional link to matched CommandIT Device
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (ad_domain_id, sam_account_name)
);
CREATE TABLE AdGroupMemberships (
membership_id BIGSERIAL PRIMARY KEY,
ad_group_id UUID NOT NULL REFERENCES AdGroups(ad_group_id) ON DELETE CASCADE, -- The group
member_type VARCHAR(10) NOT NULL CHECK (member_type IN ('User', 'Group', 'Computer')), -- Type of member
member_object_guid UUID NOT NULL, -- GUID of the member (User, Group, or Computer)
member_object_sid VARCHAR(100), -- SID of the member (denormalized for convenience)
member_dn TEXT, -- DN of the member (denormalized for convenience)
first_seen TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_seen TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN NOT NULL DEFAULT true, -- Track if membership is currently seen
UNIQUE (ad_group_id, member_object_guid) -- A member can only be in a group once
-- Add index on member_object_guid for reverse lookups ("what groups is X a member of?")
);
CREATE TABLE AdGroupPolicyObjects (
ad_gpo_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
ad_domain_id UUID NOT NULL REFERENCES AdDomains(ad_domain_id) ON DELETE CASCADE,
object_guid UUID UNIQUE, -- GPO GUID
display_name VARCHAR(255) NOT NULL,
gpo_status VARCHAR(20), -- 'Enabled', 'Disabled', 'UserSettingsDisabled', 'ComputerSettingsDisabled'
description TEXT,
when_created TIMESTAMPTZ,
when_changed TIMESTAMPTZ,
-- Consider storing version numbers if detailed change tracking is needed
-- Consider storing a hash or summary of key settings if policy drift detection is needed
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (ad_domain_id, display_name)
);
CREATE TABLE AdGpoLinks (
gpo_link_id BIGSERIAL PRIMARY KEY,
ad_gpo_id UUID NOT NULL REFERENCES AdGroupPolicyObjects(ad_gpo_id) ON DELETE CASCADE,
target_type VARCHAR(10) NOT NULL CHECK (target_type IN ('Domain', 'OU')),
target_domain_id UUID REFERENCES AdDomains(ad_domain_id) ON DELETE CASCADE,
target_ou_id UUID REFERENCES AdOrganizationalUnits(ad_ou_id) ON DELETE CASCADE,
is_enforced BOOLEAN,
link_order INTEGER,
is_enabled BOOLEAN, -- Is the link itself enabled?
last_sync_time TIMESTAMPTZ,
CHECK ((target_type = 'Domain' AND target_domain_id IS NOT NULL AND target_ou_id IS NULL) OR
(target_type = 'OU' AND target_domain_id IS NULL AND target_ou_id IS NOT NULL)),
UNIQUE (ad_gpo_id, target_domain_id) WHERE target_domain_id IS NOT NULL,
UNIQUE (ad_gpo_id, target_ou_id) WHERE target_ou_id IS NOT NULL
);
-- =============================================
-- AD Change & Security Event Logging
-- =============================================
CREATE TABLE AdObjectChangeLog (
change_log_id BIGSERIAL PRIMARY KEY,
ad_domain_id UUID NOT NULL REFERENCES AdDomains(ad_domain_id) ON DELETE CASCADE,
change_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When change was detected/recorded
source_dc VARCHAR(255), -- Domain Controller where change originated (if known)
object_guid UUID, -- GUID of the object changed
object_sid VARCHAR(100),
object_dn TEXT,
object_type VARCHAR(20), -- 'User', 'Group', 'Computer', 'GPO', 'OU', 'GroupMembership'
change_type VARCHAR(20) NOT NULL CHECK (change_type IN ('Create', 'Modify', 'Delete', 'AddMember', 'RemoveMember', 'LinkGPO', 'UnlinkGPO')),
attribute_name VARCHAR(255), -- For 'Modify' type
old_value TEXT, -- For 'Modify' type
new_value TEXT, -- For 'Modify', 'AddMember', 'RemoveMember' (stores member DN/SID)
actor_user_sid VARCHAR(100), -- SID of user/principal performing the action (if available from logs)
raw_event_data JSONB -- Optional storage for raw event details
);
CREATE TABLE DeviceEventLogs (
device_event_log_id BIGSERIAL PRIMARY KEY, -- Renamed PK
event_timestamp_utc TIMESTAMPTZ NOT NULL, -- Timestamp from the source event log (UTC preferred)
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When CommandIT recorded it
reporting_device_id UUID REFERENCES Devices(device_id) ON DELETE SET NULL, -- Endpoint/Probe reporting event
org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context (derived from device)
event_definition_id UUID NULL REFERENCES EventLogDefinitions(definition_id) ON DELETE SET NULL, -- Link to predefined event if matched
source_log_name VARCHAR(100), -- e.g., 'Security', 'System', 'Application', 'auth.log'
channel_path TEXT NULL, -- Specific Windows Event Channel Path if applicable
source_name VARCHAR(255) NULL, -- Specific event source name if applicable
event_id VARCHAR(50) NOT NULL, -- Event ID (numeric/string)
source_event_record_id VARCHAR(255) NULL, -- Unique identifier from the source OS log (e.g., Windows Event Record ID). Used for server-side deduplication.
level VARCHAR(20), -- 'Information', 'Warning', 'Error', 'AuditSuccess', 'AuditFailure' etc.
event_type VARCHAR(50) NULL CHECK (event_type IN ('LoginSuccess', 'LoginFailure', 'AccountLockout', 'PasswordChange', 'PasswordReset', 'GroupMembershipChange', 'PrivilegeUse', 'Other')), -- Categorization
ad_domain_id UUID REFERENCES AdDomains(ad_domain_id) ON DELETE SET NULL, -- Context
target_user_name VARCHAR(255) NULL,
target_user_sid VARCHAR(100) NULL,
target_device_name VARCHAR(255) NULL,
target_device_sid VARCHAR(100) NULL,
source_ip_address INET NULL,
source_port INTEGER NULL,
source_hostname VARCHAR(255) NULL,
logon_type INTEGER NULL,
details TEXT NULL, -- Event Message/Description
dynamic_parameters JSONB NULL, -- Extracted parameters
raw_event_data JSONB NULL -- Full raw event data
-- Consider partitioning this table by timestamp for performance
);
-- Comments
COMMENT ON TABLE DeviceEventLogs IS 'Stores event log entries collected from managed devices or other integrated sources.';
COMMENT ON COLUMN DeviceEventLogs.org_id IS 'Organization context, typically derived from the reporting device.';
COMMENT ON COLUMN DeviceEventLogs.event_definition_id IS 'Link to the EventLogDefinitions table if this event was reported based on a known definition.';
COMMENT ON COLUMN DeviceEventLogs.channel_path IS 'Specific Windows Event Channel path, if applicable.';
COMMENT ON COLUMN DeviceEventLogs.source_name IS 'Specific event source name (e.g., Microsoft-Windows-Security-Auditing).';
COMMENT ON COLUMN DeviceEventLogs.source_event_record_id IS 'Unique identifier for the event from the source OS log (e.g., Windows Event Record ID). Used for server-side deduplication.';
COMMENT ON COLUMN DeviceEventLogs.details IS 'Generic description (if using definition lookup) or specific message from the event payload.';
COMMENT ON COLUMN DeviceEventLogs.dynamic_parameters IS 'Stores key dynamic values extracted from the event message payload, especially when using definition lookup.';
-- Add relevant indexes (with updated names)
CREATE INDEX idx_deviceeventlogs_timestamp ON DeviceEventLogs(event_timestamp_utc DESC);
CREATE INDEX idx_deviceeventlogs_device ON DeviceEventLogs(reporting_device_id) WHERE reporting_device_id IS NOT NULL;
CREATE INDEX idx_deviceeventlogs_org_time ON DeviceEventLogs(org_id, event_timestamp_utc DESC) WHERE org_id IS NOT NULL;
CREATE INDEX idx_deviceeventlogs_event_id ON DeviceEventLogs(event_id);
CREATE INDEX idx_deviceeventlogs_dedupe ON DeviceEventLogs (reporting_device_id, source_event_record_id) WHERE reporting_device_id IS NOT NULL AND source_event_record_id IS NOT NULL;
CREATE TABLE AdDomainSecurityPolicy (
ad_domain_id UUID PRIMARY KEY REFERENCES AdDomains(ad_domain_id) ON DELETE CASCADE, -- Links 1-to-1 with the domain
-- Password Policy Settings
min_password_length INTEGER,
max_password_age_days INTEGER, -- Max lifetime in days (0 if disabled)
min_password_age_days INTEGER, -- Min lifetime in days
password_history_count INTEGER, -- Number of passwords remembered
password_complexity_enabled BOOLEAN, -- Enforce complexity requirements?
reversible_encryption_enabled BOOLEAN, -- Store passwords using reversible encryption? (Highly discouraged)
source_password_policy_gpo_id UUID REFERENCES AdGroupPolicyObjects(ad_gpo_id) ON DELETE SET NULL, -- Optional: GPO GUID that defined these settings
-- Account Lockout Policy Settings
lockout_threshold INTEGER, -- Number of failed attempts before lockout (0 if disabled)
lockout_duration_minutes INTEGER, -- Duration of lockout in minutes (0 for manual unlock)
reset_lockout_counter_after_minutes INTEGER, -- Time after which failed attempt counter resets
source_lockout_policy_gpo_id UUID REFERENCES AdGroupPolicyObjects(ad_gpo_id) ON DELETE SET NULL, -- Optional: GPO GUID that defined these settings
-- Kerberos Policy Settings (Common examples)
max_ticket_age_hours INTEGER, -- Maximum lifetime for user ticket
max_renew_age_days INTEGER, -- Maximum lifetime for user ticket renewal
max_service_ticket_age_minutes INTEGER, -- Maximum lifetime for service ticket
max_clock_skew_minutes INTEGER, -- Maximum tolerance for computer clock synchronization
enforce_user_logon_restrictions BOOLEAN, -- Validate user rights and account restrictions for service tickets?
source_kerberos_policy_gpo_id UUID REFERENCES AdGroupPolicyObjects(ad_gpo_id) ON DELETE SET NULL, -- Optional: GPO GUID that defined these settings
-- Sync Info
last_sync_time TIMESTAMPTZ, -- When these effective settings were last retrieved/updated
updated_at TIMESTAMPTZ -- Auto-updated by trigger (when CommandIT record changes)
);
-- =============================================
-- Azure AD Object Synchronization
-- =============================================
CREATE TABLE AzureAdUsers (
azure_ad_user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE, -- Link to the specific Azure tenant integration
azure_object_id UUID NOT NULL, -- Azure AD User Object ID (GUID)
user_principal_name VARCHAR(255) NOT NULL, -- UPN (e.g., [email protected])
display_name VARCHAR(255),
account_enabled BOOLEAN, -- Is the account enabled in Azure AD?
mfa_methods_registered TEXT[], -- List of registered MFA methods (e.g., 'PhoneAppOTP', 'Sms', 'FIDO2')
mfa_capable BOOLEAN, -- Calculated based on methods or specific property
is_guest BOOLEAN, -- Is this a B2B guest user?
creation_type VARCHAR(50), -- e.g., 'Cloud', 'Synced'
created_date_time TIMESTAMPTZ, -- Timestamp from Azure AD
last_sign_in_date_time TIMESTAMPTZ NULL, -- Last successful interactive sign-in
sign_in_risk_level VARCHAR(20), -- Requires Azure AD Premium P1/P2 ('low', 'medium', 'high', 'none')
commandit_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- Optional link to matched CommandIT User
last_sync_time TIMESTAMPTZ, -- When CommandIT last synced this record
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (integration_instance_id, azure_object_id),
UNIQUE (integration_instance_id, user_principal_name)
-- Add index on azure_object_id, user_principal_name
);
CREATE TABLE AzureAdGroups (
azure_ad_group_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
azure_object_id UUID NOT NULL, -- Azure AD Group Object ID (GUID)
display_name VARCHAR(255) NOT NULL,
description TEXT,
group_types TEXT[], -- e.g., ['Unified', 'Security'] or ['Security']
membership_rule TEXT, -- Rule for dynamic membership (if applicable)
is_assignable_to_role BOOLEAN, -- Can Azure AD roles be assigned to this group?
visibility VARCHAR(50), -- e.g., 'Public', 'Private' (for M365 groups)
mail_enabled BOOLEAN,
security_enabled BOOLEAN,
created_date_time TIMESTAMPTZ,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (integration_instance_id, azure_object_id),
UNIQUE (integration_instance_id, display_name) -- Assuming display names are unique per tenant scope in practice
-- Add index on azure_object_id
);
CREATE TABLE AzureAdGroupMemberships (
membership_id BIGSERIAL PRIMARY KEY, -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
group_object_id UUID NOT NULL, -- Azure GUID of the Group
member_type VARCHAR(20) NOT NULL CHECK (member_type IN ('User', 'Group', 'Device', 'ServicePrincipal')),
member_object_id UUID NOT NULL, -- Azure GUID of the Member
member_display_name VARCHAR(255), -- Denormalized member name for easier display
first_seen TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When CommandIT first saw this membership
last_seen TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When CommandIT last confirmed this membership
is_active BOOLEAN NOT NULL DEFAULT true, -- Track if membership is currently seen vs historical
UNIQUE (integration_instance_id, group_object_id, member_object_id)
-- Add index on group_object_id, member_object_id
);
-- =============================================
-- Azure AD Policy and Configuration Sync
-- =============================================
CREATE TABLE AzureAdConditionalAccessPolicies (
azure_ad_ca_policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
azure_policy_id VARCHAR(50) NOT NULL, -- Azure's Policy GUID/ID
display_name VARCHAR(255) NOT NULL,
state VARCHAR(50) NOT NULL, -- 'enabled', 'disabled', 'enabledForReportingButNotEnforced'
conditions JSONB, -- { users, applications, locations, platforms, deviceStates, signInRiskLevels }
grant_controls JSONB, -- { operator ('AND'/'OR'), builtInControls ('mfa', 'compliantDevice', etc.), customControls, termsOfUse }
session_controls JSONB, -- { applicationEnforcedRestrictions, cloudAppSecurity, signInFrequency, persistentBrowserSession }
last_modified_date_time TIMESTAMPTZ,
created_date_time TIMESTAMPTZ,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (integration_instance_id, azure_policy_id)
-- Add index on azure_policy_id
);
CREATE TABLE AzureAdTenantSecurityPolicy (
integration_instance_id UUID PRIMARY KEY REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE, -- Links 1-to-1 with the Azure tenant integration
-- Security Defaults & MFA
security_defaults_enabled BOOLEAN,
mfa_enforcement_method VARCHAR(50), -- 'SecurityDefaults', 'ConditionalAccess', 'PerUser', 'None'
-- Password Policy (Cloud or Synced)
password_policy_settings JSONB, -- { minLength, complexityRequired, lockoutThreshold, lockoutDuration }
password_protection_enabled BOOLEAN, -- Azure AD Password Protection for on-prem sync
-- Self-Service Password Reset (SSPR)
sspr_enabled_scope VARCHAR(20), -- 'all', 'selected', 'none'
sspr_target_group_ids UUID[], -- Azure Group Object IDs if scope=selected
sspr_auth_methods_required INTEGER, -- Number of methods needed for reset
sspr_allowed_auth_methods TEXT[], -- ['email', 'mobilePhone', 'officePhone', 'securityQuestions', 'appNotification', 'appCode']
-- External Identities / Collaboration
b2b_external_users_allowed BOOLEAN,
guest_invite_restrictions VARCHAR(255),
guest_user_access_level VARCHAR(50), -- 'sameAsMembers', 'limitedAccess', 'restrictedAccess'
-- Device Management
device_registration_allowed_scope VARCHAR(20), -- 'all', 'selected', 'none'
require_mfa_to_join_devices BOOLEAN,
require_device_marked_as_compliant BOOLEAN, -- Via Intune/MDM for CA
require_hybrid_azure_ad_joined BOOLEAN, -- Via Intune/MDM for CA
-- Other Settings
-- (Add other relevant tenant-wide security settings as needed)
last_sync_time TIMESTAMPTZ,
updated_at TIMESTAMPTZ -- Auto-updated by trigger
);
-- =============================================
-- Azure AD Application and Consent Tracking
-- =============================================
CREATE TABLE AzureAdApplications ( -- Represents Application Registrations
azure_ad_app_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
azure_app_id VARCHAR(50) NOT NULL, -- Application (client) ID
display_name VARCHAR(255),
publisher_domain VARCHAR(255),
sign_in_audience VARCHAR(100), -- e.g., 'AzureADMyOrg', 'AzureADMultipleOrgs', 'AzureADandPersonalMicrosoftAccount'
created_date_time TIMESTAMPTZ,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (integration_instance_id, azure_app_id)
-- Add index on azure_app_id
);
CREATE TABLE AzureAdAppServicePrincipals ( -- Represents Enterprise Applications / Service Principals
azure_ad_sp_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
azure_object_id UUID NOT NULL, -- Service Principal Object ID
app_id VARCHAR(50) NOT NULL, -- Application (client) ID it represents
display_name VARCHAR(255),
service_principal_type VARCHAR(50), -- e.g., 'Application', 'ManagedIdentity'
account_enabled BOOLEAN,
app_owner_org_id UUID, -- Azure Tenant ID of the app publisher
homepage_url TEXT,
logout_url TEXT,
reply_urls TEXT[],
notes TEXT,
tags TEXT[],
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (integration_instance_id, azure_object_id),
UNIQUE (integration_instance_id, app_id) -- Typically 1 SP per App ID in a tenant
-- Add index on azure_object_id, app_id
);
CREATE TABLE AzureAdAppOAuth2PermissionsGrants ( -- Tracks delegated permissions (User/Admin Consent)
grant_id VARCHAR(255) PRIMARY KEY, -- The unique ID of the grant object from Azure AD Graph API
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
consent_type VARCHAR(50), -- 'AllPrincipals' (Admin Consent) or 'Principal' (User Consent)
principal_id UUID NULL, -- User/Group Object ID if consent_type='Principal' (Who consented/granted on behalf of)
client_id UUID NOT NULL, -- Service Principal Object ID of the *client* application receiving the permission
resource_id UUID NOT NULL, -- Service Principal Object ID of the *resource* application (API) being accessed
scope TEXT NOT NULL, -- Space-delimited string of granted permission scopes (e.g., 'User.Read Mail.Read')
grant_time TIMESTAMPTZ, -- When the grant was initially recorded by Azure
expiry_time TIMESTAMPTZ, -- When the grant expires (less common for OAuth2)
last_sync_time TIMESTAMPTZ
-- Add index on client_id, resource_id, principal_id
);
CREATE TABLE AzureAdAppRoleAssignments ( -- Tracks assignment of users/groups to Application Roles
assignment_id VARCHAR(255) PRIMARY KEY, -- The unique ID of the assignment object from Azure AD Graph API
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
app_role_id UUID NOT NULL, -- ID of the specific app role (defined on the resource app) being assigned
principal_id UUID NOT NULL, -- Object ID of the User, Group, or Service Principal being assigned the role
principal_type VARCHAR(50), -- 'User', 'Group', 'ServicePrincipal'
resource_id UUID NOT NULL, -- Object ID of the Service Principal of the resource application (whose role is being assigned)
resource_display_name VARCHAR(255), -- Denormalized resource SP name
principal_display_name VARCHAR(255), -- Denormalized principal name
created_date_time TIMESTAMPTZ, -- When assignment was made
last_sync_time TIMESTAMPTZ
-- Add index on principal_id, resource_id
);
-- =============================================
-- Azure AD Event Logging (Selective)
-- =============================================
CREATE TABLE AzureAdRiskySignInEvents ( -- Store flagged sign-ins from Identity Protection
event_id VARCHAR(255) PRIMARY KEY, -- ID from Azure AD audit/signin logs or Identity Protection event
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
correlation_id VARCHAR(50),
request_id VARCHAR(50),
user_principal_name VARCHAR(255),
user_display_name VARCHAR(255),
user_object_id UUID,
risk_level_aggregated VARCHAR(20), -- 'low', 'medium', 'high' (at time of event)
risk_level_during_signin VARCHAR(20),
risk_state VARCHAR(20), -- 'atRisk', 'confirmedCompromised', 'remediated', 'dismissed'
risk_detail VARCHAR(100), -- e.g., 'anonymizedIpAddress', 'unfamiliarLocation', 'malwareInfectedIp'
risk_event_types TEXT[], -- Types of detections contributing
ip_address INET,
location JSONB, -- { city, state, countryOrRegion, geoCoordinates { latitude, longitude } }
device_detail JSONB, -- { deviceId, displayName, operatingSystem, browser, isCompliant, isManaged }
authentication_details JSONB, -- { authMethod ('password', 'mfa', 'federated'), succeeded }
conditional_access_status VARCHAR(50), -- 'success', 'failure', 'notApplied', 'reportOnlySuccess' etc.
mfa_result VARCHAR(50), -- Result of MFA challenge if performed
application_display_name VARCHAR(255),
application_id VARCHAR(50), -- Client App ID
resource_display_name VARCHAR(255),
resource_id VARCHAR(50), -- Resource App ID
created_date_time TIMESTAMPTZ NOT NULL, -- Event time UTC
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When CommandIT recorded it
status VARCHAR(20) DEFAULT 'New' CHECK (status IN ('New', 'Investigating', 'Remediated', 'Dismissed', 'FalsePositive'))
-- Add index on created_date_time, user_principal_name, risk_level_aggregated, status
);
-- =============================================
-- Azure AD Privileged Identity Management (PIM)
-- =============================================
CREATE TABLE AzureAdPimRoleAssignments ( -- Tracks Eligible and Active PIM role assignments
assignment_id VARCHAR(255) PRIMARY KEY, -- ID of the assignment schedule instance from Graph API
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE,
role_definition_id VARCHAR(255) NOT NULL, -- ID of the Azure AD Role Definition (e.g., Global Admin)
role_display_name VARCHAR(255), -- Denormalized role name
principal_id UUID NOT NULL, -- Object ID of the User or Group assigned
principal_type VARCHAR(20), -- 'User', 'Group'
directory_scope_id VARCHAR(255) NOT NULL, -- Usually the Tenant ID ('/') or an Administrative Unit ID
assignment_state VARCHAR(20) NOT NULL CHECK (assignment_state IN ('Eligible', 'Active')), -- Type of assignment
assignment_type VARCHAR(20), -- 'Activated', 'Assigned'
start_date_time TIMESTAMPTZ, -- When the assignment/activation starts
end_date_time TIMESTAMPTZ, -- When the assignment/activation ends (can be permanent)
justification TEXT, -- Justification provided during activation
ticket_number VARCHAR(50), -- Associated ticket number if provided during activation
activated_by_user_id UUID, -- User who activated an eligible assignment
last_sync_time TIMESTAMPTZ
-- Add index on principal_id, role_definition_id, assignment_state
);
-- =============================================
-- SQL Server Monitoring Tables
-- =============================================
CREATE TABLE SqlServerInstances (
sql_instance_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
host_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Device hosting the instance
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Inherited from host device
instance_name VARCHAR(255) NOT NULL, -- e.g., MSSQLSERVER (default), SQLEXPRESS, or named instance
version VARCHAR(100), -- e.g., '15.0.2000.5' (SQL Server 2019 RTM)
edition VARCHAR(100), -- e.g., 'Standard Edition', 'Enterprise Edition', 'Express'
product_level VARCHAR(50), -- e.g., 'RTM', 'SP1', 'CU10'
service_name VARCHAR(255), -- Windows service name
service_account VARCHAR(255), -- Account running the SQL Server service
authentication_mode VARCHAR(50), -- 'Windows Authentication', 'Mixed Mode'
tcp_port INTEGER,
is_clustered BOOLEAN,
status VARCHAR(50), -- 'Running', 'Stopped', 'Unknown'
collation VARCHAR(100),
max_server_memory_mb INTEGER,
min_server_memory_mb INTEGER,
processors_used INTEGER,
-- Add other key instance-level configuration settings as needed
last_assessment_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (host_device_id, instance_name)
);
CREATE TABLE SqlDatabases (
sql_database_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
sql_instance_id UUID NOT NULL REFERENCES SqlServerInstances(sql_instance_id) ON DELETE CASCADE,
database_name VARCHAR(255) NOT NULL,
status VARCHAR(50), -- 'ONLINE', 'OFFLINE', 'RESTORING', 'RECOVERING', 'SUSPECT'
size_mb BIGINT,
recovery_model VARCHAR(20), -- 'SIMPLE', 'FULL', 'BULK_LOGGED'
collation VARCHAR(100),
owner_sid VARCHAR(100),
owner_name VARCHAR(255),
compatibility_level INTEGER, -- e.g., 150 (SQL 2019), 140 (SQL 2017)
created_date TIMESTAMPTZ,
is_read_only BOOLEAN,
auto_shrink_enabled BOOLEAN,
-- Add other key database-level settings as needed (e.g., encryption, file paths)
last_assessment_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (sql_instance_id, database_name)
);
CREATE TABLE SqlLogins (
sql_login_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
sql_instance_id UUID NOT NULL REFERENCES SqlServerInstances(sql_instance_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
sid VARCHAR(100), -- SID if Windows login/group
type VARCHAR(20) NOT NULL CHECK (type IN ('SqlLogin', 'WindowsUser', 'WindowsGroup', 'Certificate', 'AsymmetricKey')),
default_database VARCHAR(255),
is_disabled BOOLEAN,
password_policy_enforced BOOLEAN, -- For SqlLogin
password_expiration_enabled BOOLEAN, -- For SqlLogin
last_login_time TIMESTAMPTZ,
server_roles TEXT[], -- e.g., ['sysadmin', 'serveradmin']
database_permissions JSONB, -- Store simplified DB user mappings/roles if needed: { "dbName": ["db_owner", "db_datareader"] }
created_date TIMESTAMPTZ,
last_modified_date TIMESTAMPTZ,
last_assessment_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (sql_instance_id, name)
);
CREATE TABLE SqlAgentJobs (
sql_agent_job_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
sql_instance_id UUID NOT NULL REFERENCES SqlServerInstances(sql_instance_id) ON DELETE CASCADE,
job_name VARCHAR(255) NOT NULL,
category_name VARCHAR(100),
owner_sid VARCHAR(100),
owner_name VARCHAR(255),
is_enabled BOOLEAN,
last_run_outcome VARCHAR(50), -- 'Succeeded', 'Failed', 'Cancelled', 'Unknown'
last_run_timestamp TIMESTAMPTZ,
last_run_duration_seconds INTEGER,
next_run_timestamp TIMESTAMPTZ,
schedule_details JSONB, -- Store schedule info (frequency, time, etc.)
job_steps_summary JSONB, -- Optional: Store number of steps, types
last_assessment_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (sql_instance_id, job_name)
);
-- =============================================
-- Exchange Monitoring Tables
-- =============================================
CREATE TABLE ExchangeServers ( -- Primarily for On-Premises Exchange
exchange_server_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
host_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Inherited from host device
exchange_version VARCHAR(100), -- e.g., 'Version 15.2 (Build 986.5)' (Exchange 2019 CU12)
edition VARCHAR(50), -- 'Standard', 'Enterprise'
installed_roles TEXT[], -- ['Mailbox', 'EdgeTransport', 'ClientAccess']
ad_site_name VARCHAR(255),
last_assessment_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE ExchangeDatabases ( -- Primarily for On-Premises Exchange
exchange_database_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
exchange_server_id UUID NOT NULL REFERENCES ExchangeServers(exchange_server_id) ON DELETE CASCADE,
database_name VARCHAR(255) NOT NULL,
status VARCHAR(50), -- 'Mounted', 'Dismounted', 'Unknown'
size_gb BIGINT,
circular_logging_enabled BOOLEAN,
last_full_backup TIMESTAMPTZ,
-- Add other key database settings as needed (e.g., file paths, mailbox count)
last_assessment_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (exchange_server_id, database_name)
);
CREATE TABLE ExchangeMailboxes ( -- Can represent On-Prem or Exchange Online mailboxes
exchange_mailbox_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Owning Org
integration_instance_id UUID REFERENCES IntegrationInstances(instance_id) ON DELETE SET NULL, -- FK if Exchange Online via Integration
exchange_server_id UUID REFERENCES ExchangeServers(exchange_server_id) ON DELETE SET NULL, -- FK if On-Prem Mailbox
azure_ad_user_id UUID REFERENCES AzureAdUsers(azure_ad_user_id) ON DELETE SET NULL, -- Optional link to Azure AD User
ad_user_id UUID REFERENCES AdUsers(ad_user_id) ON DELETE SET NULL, -- Optional link to AD User
display_name VARCHAR(255) NOT NULL,
primary_smtp_address VARCHAR(255) NOT NULL,
user_principal_name VARCHAR(255),
mailbox_guid UUID,
mailbox_type VARCHAR(30) NOT NULL CHECK (mailbox_type IN ('UserMailbox', 'SharedMailbox', 'RoomMailbox', 'EquipmentMailbox', 'LinkedMailbox', 'DiscoveryMailbox')),
size_mb BIGINT,
item_count BIGINT,
prohibit_send_quota_mb BIGINT,
issue_warning_quota_mb BIGINT,
archive_status VARCHAR(20), -- 'None', 'Active', 'AutoExpanding'
archive_size_mb BIGINT,
litigation_hold_enabled BOOLEAN,
litigation_hold_duration_days INTEGER,
retention_policy_name VARCHAR(255),
forwarding_address VARCHAR(255), -- Email address if forwarding is set
deliver_to_mailbox_and_forward BOOLEAN,
last_logon_time TIMESTAMPTZ,
is_active_sync_enabled BOOLEAN,
is_owa_enabled BOOLEAN,
-- Add other relevant settings (e.g., permissions summary, specific protocol enablement)
last_assessment_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
CHECK ((integration_instance_id IS NOT NULL AND exchange_server_id IS NULL) OR (integration_instance_id IS NULL AND exchange_server_id IS NOT NULL)), -- Must be EXO or OnPrem, not both/neither directly linked here
UNIQUE (org_id, primary_smtp_address) -- Assuming unique within an Org context
);
CREATE TABLE ExchangeConnectors ( -- Primarily for On-Premises Exchange Send/Receive Connectors
exchange_connector_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
exchange_server_id UUID NOT NULL REFERENCES ExchangeServers(exchange_server_id) ON DELETE CASCADE,
connector_name VARCHAR(255) NOT NULL,
connector_type VARCHAR(20) NOT NULL CHECK (connector_type IN ('Send', 'Receive')),
is_enabled BOOLEAN,
usage VARCHAR(50), -- For Receive Connectors (e.g., 'Client', 'Internal', 'Partner')
bindings JSONB, -- IP/Port bindings for Receive Connectors
address_spaces TEXT[], -- For Send Connectors (domains it handles)
smart_hosts TEXT[], -- For Send Connectors
source_transport_servers TEXT[], -- For Send Connectors
security_settings JSONB, -- Authentication methods, TLS settings
last_assessment_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (exchange_server_id, connector_name)
);
-- =============================================
-- Vulnerability Scan Results Tables
-- =============================================
-- Revised based on user request for Org/Location/CI level linking
CREATE TABLE ExternalScanTargets (
scan_target_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org responsible for this target definition
target VARCHAR(255) NOT NULL, -- IP address, hostname, or CIDR range
description TEXT,
-- Context Links (Nullable FKs to allow different levels of association)
location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Optional: Link to a specific Location
device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Optional: Link if this target maps to a specific managed Device (CI)
is_active BOOLEAN DEFAULT true, -- Is this target definition currently active for scanning?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
-- Ensure the target itself is unique within the responsible Org
UNIQUE (org_id, target)
-- We might add a CHECK constraint if we want to enforce rules like "if device_id is set, location_id must match device's location", but keeping it simple for now.
);
COMMENT ON TABLE ExternalScanTargets IS 'Defines external assets (IPs, hostnames, CIDRs) to be monitored or scanned, allowing association at the Organization, Location, or specific Device (CI) level.';
COMMENT ON COLUMN ExternalScanTargets.org_id IS 'The Organization primarily responsible for or associated with this external target.';
COMMENT ON COLUMN ExternalScanTargets.target IS 'The actual IP address, hostname, or CIDR range representing the external asset.';
COMMENT ON COLUMN ExternalScanTargets.location_id IS 'Optional FK to Locations, linking this external target to a specific site.';
COMMENT ON COLUMN ExternalScanTargets.device_id IS 'Optional FK to Devices, linking this external target directly to a managed Configuration Item (e.g., a firewall, external web server represented as a device).';
CREATE TABLE VulnerabilityScans (
scan_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context for the scan
scan_type VARCHAR(10) NOT NULL CHECK (scan_type IN ('Internal', 'External')), -- Type of scan run
scan_engine VARCHAR(100), -- e.g., 'Nmap', 'OpenVAS', 'QualysCloudPlatform', 'InternalScript', 'AgentScan'
scan_name VARCHAR(255), -- User-defined name for the scan job/policy
scan_start_time TIMESTAMPTZ NOT NULL,
scan_end_time TIMESTAMPTZ,
status VARCHAR(20) NOT NULL CHECK (status IN ('Pending', 'Running', 'Completed', 'Failed', 'Cancelled')),
targets_scanned TEXT[], -- List of targets included in this specific scan run (IPs, hostnames, CIDRs)
source_probe_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Probe device used for internal scans
scan_configuration JSONB, -- Details about scan settings used (e.g., policy name, intensity)
initiated_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE VulnerabilityScans IS 'Stores metadata about executed vulnerability scans, both internal and external.';
COMMENT ON COLUMN VulnerabilityScans.scan_type IS 'Indicates whether the scan was run against internal network assets or external targets.';
COMMENT ON COLUMN VulnerabilityScans.targets_scanned IS 'List of specific IPs, hostnames, or CIDRs included in this particular scan execution.';
COMMENT ON COLUMN VulnerabilityScans.source_probe_device_id IS 'The probe device that executed this scan, typically used for internal scans.';
COMMENT ON COLUMN VulnerabilityScans.scan_configuration IS 'JSONB field storing details about the scan settings, policy used, intensity, etc.';
-- =============================================
-- UNIFIED VULNERABILITY FINDINGS TABLE (Revised)
-- =============================================
CREATE TABLE VulnerabilityFindings (
finding_id BIGSERIAL PRIMARY KEY, -- Unique ID for this finding instance
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context
-- Context - Nullable fields depending on finding_source_type
device_id UUID NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Link to device if internal finding or if external target is matched
scan_id UUID NULL REFERENCES VulnerabilityScans(scan_id) ON DELETE SET NULL, -- Link to the specific scan job that found this (internal or external)
scan_target VARCHAR(255) NULL, -- Target IP/hostname from external scan
-- Vulnerability Identification
vulnerability_id VARCHAR(100) NOT NULL, -- CVE, Check ID, Vuln Name Key, etc. Use a consistent scheme.
vulnerability_name TEXT,
detection_source VARCHAR(100) NOT NULL, -- e.g., 'ExternalScanToolX', 'AgentComplianceCheck', 'AgentPatchScan', 'ManualEntry', 'InternalScanToolY'
finding_source_type VARCHAR(10) NOT NULL CHECK (finding_source_type IN ('Internal', 'External')), -- Discriminator
-- Vulnerability Details (General - Not Nessus Specific)
severity VARCHAR(20) CHECK (severity IN ('Info', 'Low', 'Medium', 'High', 'Critical')),
cvss_base_score NUMERIC(3, 1) NULL,
cvss_temporal_score NUMERIC(3, 1) NULL,
cvss_vector_string VARCHAR(255) NULL,
cross_references JSONB NULL, -- Store additional IDs like BID, OSVDB, etc. e.g., {"BID": ["12345"]}
exploit_available BOOLEAN NULL,
exploit_frameworks TEXT[] NULL, -- e.g., ['Metasploit', 'ExploitDB']
reference_urls TEXT[] NULL, -- General reference URLs ('See Also')
-- Finding Context
port INTEGER NULL, -- Port if applicable
protocol VARCHAR(10) NULL CHECK (protocol IN ('TCP', 'UDP', 'ICMP')), -- Protocol if applicable
service_name VARCHAR(100) NULL, -- Service if applicable
software_product_id UUID NULL REFERENCES SoftwareProducts(software_product_id) ON DELETE SET NULL, -- Link if related to specific software
-- Finding Details & Remediation
description TEXT, -- Description of the vulnerability/finding
evidence TEXT, -- Output or proof from the scanner/check
remediation TEXT, -- Recommended fix or link to KB
-- Status & Tracking
status VARCHAR(20) DEFAULT 'New' CHECK (status IN ('New', 'Investigating', 'Confirmed', 'RiskAccepted', 'Remediated', 'FalsePositive')),
first_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this finding first appeared for this target
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this finding was last confirmed present by a scan/check
status_updated_time TIMESTAMPTZ, -- When the status field was last changed
status_updated_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL, -- Optional link to remediation ticket
notes TEXT, -- Internal notes about this finding
-- Uniqueness constraint needs careful thought. This attempts uniqueness based on key identifiers per target type and source.
UNIQUE (device_id, vulnerability_id, port, protocol, detection_source) WHERE finding_source_type = 'Internal' AND device_id IS NOT NULL,
UNIQUE (scan_target, vulnerability_id, port, protocol, scan_id) WHERE finding_source_type = 'External' AND scan_target IS NOT NULL AND scan_id IS NOT NULL
);
COMMENT ON TABLE VulnerabilityFindings IS 'Unified table storing vulnerability findings from both internal (device-based) and external scans/sources.';
COMMENT ON COLUMN VulnerabilityFindings.finding_source_type IS 'Indicates if the finding originated from an internal source (like an agent on a device) or an external scan.';
COMMENT ON COLUMN VulnerabilityFindings.device_id IS 'Link to the specific device if the finding is internal or if an external target was successfully mapped to a managed device.';
COMMENT ON COLUMN VulnerabilityFindings.scan_id IS 'Link to the specific scan job record (VulnerabilityScans table) if the finding came from a defined scan.';
COMMENT ON COLUMN VulnerabilityFindings.scan_target IS 'The IP address or hostname targeted by an external scan where the finding was observed.';
COMMENT ON COLUMN VulnerabilityFindings.vulnerability_id IS 'Primary identifier for the vulnerability (e.g., CVE ID, internal check ID).';
COMMENT ON COLUMN VulnerabilityFindings.cross_references IS 'JSONB storing related vulnerability identifiers (e.g., BID, MSKB).';
COMMENT ON COLUMN VulnerabilityFindings.cvss_vector_string IS 'The full CVSS vector string (v2 or v3) for detailed scoring context.';
COMMENT ON COLUMN VulnerabilityFindings.exploit_available IS 'Boolean indicating if a known, public exploit is readily available.';
COMMENT ON COLUMN VulnerabilityFindings.exploit_frameworks IS 'Array listing frameworks known to contain exploits for this vulnerability.';
COMMENT ON COLUMN VulnerabilityFindings.reference_urls IS 'Array of URLs providing further information about the vulnerability (e.g., vendor advisories, CVE details).';
-- Add relevant indexes
CREATE INDEX idx_vulnfindings_device ON VulnerabilityFindings(device_id) WHERE device_id IS NOT NULL;
CREATE INDEX idx_vulnfindings_scan_target ON VulnerabilityFindings(scan_target) WHERE scan_target IS NOT NULL;
CREATE INDEX idx_vulnfindings_scan_id ON VulnerabilityFindings(scan_id) WHERE scan_id IS NOT NULL;
CREATE INDEX idx_vulnfindings_vuln_id ON VulnerabilityFindings(vulnerability_id);
CREATE INDEX idx_vulnfindings_status ON VulnerabilityFindings(status);
CREATE INDEX idx_vulnfindings_severity ON VulnerabilityFindings(severity);
CREATE INDEX idx_vulnfindings_org_status ON VulnerabilityFindings(org_id, status);
CREATE INDEX idx_vulnfindings_source_type ON VulnerabilityFindings(finding_source_type);
-- =============================================
-- Network Share Discovery Tables
-- =============================================
CREATE TABLE NetworkShares ( -- Discovered shares on devices
network_share_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
host_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Inherited from host device
share_name VARCHAR(255) NOT NULL, -- e.g., 'C$', 'ShareDocs', 'Printers'
path TEXT NOT NULL, -- Local path on the host device being shared
description TEXT,
share_type VARCHAR(20) CHECK (share_type IN ('Disk', 'Printer', 'IPC', 'Special', 'Unknown')), -- Based on Windows share types
max_users INTEGER, -- Maximum concurrent users allowed (-1 for unlimited)
last_seen_time TIMESTAMPTZ, -- When the agent last confirmed this share exists
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (host_device_id, share_name)
);
CREATE TABLE SharePermissions ( -- Share-level permissions (ACLs)
share_permission_id BIGSERIAL PRIMARY KEY, -- Internal CommandIT ID
network_share_id UUID NOT NULL REFERENCES NetworkShares(network_share_id) ON DELETE CASCADE,
account_name VARCHAR(255) NOT NULL, -- User or Group name (e.g., 'DOMAIN\User', 'BUILTIN\Administrators', 'Everyone')
account_sid VARCHAR(100), -- Optional: SID of the user/group for better accuracy
access_type VARCHAR(10) NOT NULL CHECK (access_type IN ('Allow', 'Deny')),
permission VARCHAR(20) NOT NULL CHECK (permission IN ('Read', 'Change', 'FullControl')),
last_seen_time TIMESTAMPTZ,
UNIQUE (network_share_id, account_name, permission, access_type) -- Should be unique combination
);
-- =============================================
-- Backup Monitoring
-- =============================================
CREATE TABLE BackupJobStatus (
backup_job_status_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Device being backed up
integration_instance_id UUID NULL REFERENCES IntegrationInstances(instance_id) ON DELETE SET NULL, -- Link to Backup system integration config
job_name VARCHAR(255) NOT NULL, -- Name of the backup job
job_type VARCHAR(50), -- e.g., 'FileSystem', 'SQL', 'Exchange', 'VM', 'Full', 'Incremental'
last_run_timestamp TIMESTAMPTZ NULL, -- When the job last attempted to run (or completed)
last_run_status VARCHAR(30) CHECK (last_run_status IN ('Success', 'Warning', 'Failed', 'Running', 'Cancelled', 'Skipped', 'Unknown')),
last_success_timestamp TIMESTAMPTZ NULL, -- Timestamp of the last *successful* completion
next_run_timestamp TIMESTAMPTZ NULL, -- Estimated next scheduled run time (if available)
backup_set_details TEXT, -- Brief description of what is included (e.g., "C:\Users", "Full System")
destination TEXT, -- Description of backup destination (e.g., "Cloud Storage", "Local NAS")
last_run_size_bytes BIGINT, -- Size of data backed up in last run
last_run_duration_seconds INTEGER, -- Duration of last run
last_error_message TEXT, -- Error message if last_run_status was 'Failed'
last_reported_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When CommandIT last received this status update
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, job_name) -- Assuming job names are unique per device
-- Add index on last_run_status, last_success_timestamp
);
CREATE TABLE BackupJobConfiguration (
backup_job_config_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
integration_instance_id UUID NULL REFERENCES IntegrationInstances(instance_id) ON DELETE SET NULL,
job_name VARCHAR(255) NOT NULL,
backup_system_job_id VARCHAR(255) NULL,
-- Common Configuration Settings
is_enabled BOOLEAN,
backup_method VARCHAR(50),
schedule_summary TEXT,
schedule_details JSONB,
source_summary TEXT,
source_details JSONB,
retention_summary TEXT,
retention_details JSONB,
encryption_enabled BOOLEAN,
encryption_type VARCHAR(50),
application_aware_processing BOOLEAN,
consistency_check_enabled BOOLEAN,
-- Sync Info
last_config_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, job_name)
);
CREATE TABLE BackupPolicies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP defining the policy
name VARCHAR(150) NOT NULL,
description TEXT,
is_default_for_org BOOLEAN NOT NULL DEFAULT false, -- Org's default backup policy
is_active BOOLEAN NOT NULL DEFAULT true,
-- Schedule Expectation
expected_frequency VARCHAR(20) NOT NULL CHECK (expected_frequency IN ('Hourly', 'Daily', 'Weekly', 'Monthly', 'Workdays', 'Weekends', 'ManualOnly')), -- How often should a success be seen?
alert_if_missed_after_hours INTEGER NOT NULL DEFAULT 25, -- Grace period (hours) after expected run time before alerting on 'Missed'
-- Result Handling Rules (Defines action taken based on reported status)
-- Action Options: 'CreateTicket', 'AlertOnly', 'CreateTicketAndAlert', 'Ignore'
on_success_action VARCHAR(30) NOT NULL DEFAULT 'Ignore',
on_success_ticket_template_id UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
on_warning_action VARCHAR(30) NOT NULL DEFAULT 'AlertOnly',
on_warning_ticket_template_id UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
on_failure_action VARCHAR(30) NOT NULL DEFAULT 'CreateTicketAndAlert',
on_failure_ticket_template_id UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
on_missed_action VARCHAR(30) NOT NULL DEFAULT 'CreateTicketAndAlert', -- Action if alert_if_missed_after_hours breached
on_missed_ticket_template_id UUID REFERENCES TicketTemplates(template_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name)
);
CREATE TABLE BackupDestinations (
backup_destination_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org managing this destination definition
name VARCHAR(255) NOT NULL, -- User-friendly name (e.g., "Primary NAS - Office", "Azure Blob Vault WestUS", "Rotating USB Set Alpha")
media_type VARCHAR(30) NOT NULL CHECK (media_type IN ('Disk', 'NAS', 'SAN', 'TapeLibrary', 'TapeDrive', 'CloudStorage', 'USB', 'NetworkPath', 'Other')),
location_description TEXT NULL, -- Free-text description (e.g., "Azure West US Region", "Basement Rack 3", "Fireproof Safe Room 101")
physical_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Link if it corresponds to a managed Location
is_offsite BOOLEAN NOT NULL DEFAULT false, -- Is this considered offsite relative to primary data?
is_immutable BOOLEAN NOT NULL DEFAULT false, -- Does the destination support/enforce immutability?
rotation_type VARCHAR(20) NULL CHECK (rotation_type IN ('None', 'MediaPerBackup', 'Daily', 'Weekly', 'Monthly', 'GFS')), -- Media rotation strategy if applicable
access_details JSONB NULL, -- Store non-sensitive connection info (e.g., { "provider": "AzureBlob", "container": "prod-backups", "url": "..." } ). Sensitive parts use Credentials table.
credential_id UUID NULL REFERENCES Credentials(credential_id) ON DELETE SET NULL, -- Link to stored credentials if needed for access
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, name)
);
COMMENT ON TABLE BackupDestinations IS 'Defines reusable backup storage destinations and their properties.';
COMMENT ON COLUMN BackupDestinations.is_offsite IS 'Indicates if this storage location is considered physically separate (offsite).';
COMMENT ON COLUMN BackupDestinations.is_immutable IS 'Indicates if the storage destination supports object/backup immutability.';
COMMENT ON COLUMN BackupDestinations.rotation_type IS 'Describes the media rotation strategy, if applicable (especially for Disk/Tape/USB).';
COMMENT ON COLUMN BackupDestinations.access_details IS 'Stores non-sensitive connection details (URLs, paths, container names). Use credential_id for secrets.';
CREATE TABLE BackupJobDestinationLinks (
backup_job_config_id BIGINT NOT NULL REFERENCES BackupJobConfiguration(backup_job_config_id) ON DELETE CASCADE,
backup_destination_id UUID NOT NULL REFERENCES BackupDestinations(backup_destination_id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL CHECK (role IN ('Primary', 'Secondary', 'Archive')), -- Role of this destination for the job (Primary target, Copy/Replication target, Long-term archive)
last_verified_connectivity TIMESTAMPTZ NULL, -- Optional: Track when connectivity was last confirmed
PRIMARY KEY (backup_job_config_id, backup_destination_id, role) -- Job can link to same dest maybe with diff role?
);
COMMENT ON TABLE BackupJobDestinationLinks IS 'Links Backup Job Configurations to their Backup Destinations, specifying the role (Primary, Secondary, Archive).';
COMMENT ON COLUMN BackupJobDestinationLinks.role IS 'The role this destination plays for the linked backup job.';
CREATE TABLE TagBackupPolicyAssignments (
assignment_id BIGSERIAL PRIMARY KEY,
tag_id UUID NOT NULL REFERENCES Tags(tag_id) ON DELETE CASCADE, -- Tag assigned to Device or Org
backup_policy_id UUID NOT NULL REFERENCES BackupPolicies(policy_id) ON DELETE CASCADE,
is_enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 0, -- Conflict resolution priority
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (tag_id, backup_policy_id)
);
-- =============================================
-- Website & Domain Monitoring Tables
-- =============================================
CREATE TABLE MonitoredWebsites (
website_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org this site belongs to
url TEXT NOT NULL, -- URL to monitor (e.g., https://www.example.com)
display_name VARCHAR(255), -- User-friendly name
check_interval_seconds INTEGER NOT NULL DEFAULT 300, -- How often to check
expected_status_code INTEGER DEFAULT 200, -- Expected HTTP status code for 'Up'
expected_content_match TEXT NULL, -- Optional: String that must be present in response body
check_ssl_expiry BOOLEAN NOT NULL DEFAULT true, -- Check SSL certificate validity?
ssl_expiry_threshold_days INTEGER DEFAULT 30, -- Days before expiry to warn
ssl_expiry_date DATE NULL, -- Last known SSL cert expiry date
ssl_issuer VARCHAR(255) NULL, -- Last known SSL cert issuer
last_check_time TIMESTAMPTZ NULL, -- When the last check was performed
last_status VARCHAR(30) DEFAULT 'Unknown' CHECK (last_status IN ('Up', 'Down', 'Error', 'ContentMismatch', 'SslExpiringSoon', 'SslExpired', 'Unknown')),
last_response_time_ms INTEGER NULL, -- Response time in milliseconds
last_error_message TEXT NULL,
is_active BOOLEAN NOT NULL DEFAULT true, -- Is monitoring enabled?
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, url)
);
CREATE TABLE MonitoredDomains (
domain_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org this domain belongs to
org_domain_id BIGINT NULL REFERENCES OrganizationDomains(org_domain_id) ON DELETE SET NULL, -- Link to the corresponding verified domain association
domain_name VARCHAR(255) NOT NULL,
registrar_name VARCHAR(255) NULL, -- Name of the domain registrar
registration_expiry_date DATE NULL, -- Domain registration expiry
expiry_threshold_days INTEGER DEFAULT 30, -- Days before expiry to warn
dns_check_config JSONB NULL, -- Define DNS records to check { "checks": [ {"type": "A", "host": "@", "expected_value": "1.2.3.4"}, {"type": "MX", "expected_value": "mail.example.com.", "preference": 10} ]}
last_check_time TIMESTAMPTZ NULL,
last_status VARCHAR(30) DEFAULT 'Unknown' CHECK (last_status IN ('Ok', 'ExpiringSoon', 'Expired', 'DnsMismatch', 'Error', 'Unknown')),
last_error_message TEXT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (org_id, domain_name)
);
COMMENT ON TABLE MonitoredDomains IS 'Tracks domains being actively monitored for registration expiry and DNS records.';
COMMENT ON COLUMN MonitoredDomains.org_domain_id IS 'Link to the corresponding verified domain association in OrganizationDomains table.';
CREATE INDEX idx_monitoreddomains_org_domain_id ON MonitoredDomains(org_domain_id);
-- =============================================
-- Cloud Infrastructure Inventory
-- =============================================
CREATE TABLE CloudSubscriptions (
cloud_subscription_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE, -- Link to Azure Tenant Integration
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Associated Client Org
azure_subscription_id VARCHAR(50) NOT NULL, -- Azure Subscription GUID
display_name VARCHAR(255) NOT NULL,
state VARCHAR(50), -- e.g., 'Enabled', 'Warned', 'PastDue', 'Disabled'
tenant_id VARCHAR(50), -- Azure Tenant GUID
-- cost_center VARCHAR(100), -- If available via tags/management groups
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (integration_instance_id, azure_subscription_id)
);
CREATE TABLE CloudResourceGroups (
cloud_resource_group_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
cloud_subscription_id UUID NOT NULL REFERENCES CloudSubscriptions(cloud_subscription_id) ON DELETE CASCADE,
azure_rg_name VARCHAR(255) NOT NULL, -- Name of the Azure Resource Group
azure_rg_id TEXT UNIQUE, -- Full Azure Resource ID
location VARCHAR(100), -- Azure region
tags JSONB, -- Resource tags from Azure
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (cloud_subscription_id, azure_rg_name)
);
-- Note: Cloud Virtual Machines are primarily handled by enhancing the 'Devices' table:
-- Set Devices.device_type = 'CloudServer'
-- Store cloud-specific details in Devices.configuration JSONB:
-- { "cloud_provider": "Azure",
-- "azure_vm_id": "/subscriptions/.../resourceGroups/.../providers/Microsoft.Compute/virtualMachines/...",
-- "azure_resource_group_name": "...",
-- "azure_subscription_id": "...",
-- "vm_size": "Standard_D2s_v3",
-- "region": "canadacentral",
-- "power_state": "VM running",
-- "private_ip_addresses": ["10.0.0.4"],
-- "public_ip_addresses": ["20.x.x.x"]
-- }
CREATE TABLE CloudManagedDatabases (
cloud_database_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
cloud_subscription_id UUID NOT NULL REFERENCES CloudSubscriptions(cloud_subscription_id) ON DELETE CASCADE,
cloud_resource_group_id UUID REFERENCES CloudResourceGroups(cloud_resource_group_id) ON DELETE SET NULL,
azure_db_resource_id TEXT UNIQUE, -- Full Azure Resource ID
name VARCHAR(255) NOT NULL,
db_type VARCHAR(50) NOT NULL, -- e.g., 'AzureSqlDatabase', 'AzureSqlManagedInstance', 'AzureMySql', 'AzurePostgreSql'
server_name VARCHAR(255), -- Name of logical server if applicable
location VARCHAR(100),
sku JSONB, -- { "name": "GP_Gen5", "tier": "GeneralPurpose", "capacity": 2 }
status VARCHAR(50), -- e.g., 'Online', 'Creating', 'Disabled'
collation VARCHAR(100),
tags JSONB,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE CloudStorageAccounts (
cloud_storage_account_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
cloud_subscription_id UUID NOT NULL REFERENCES CloudSubscriptions(cloud_subscription_id) ON DELETE CASCADE,
cloud_resource_group_id UUID REFERENCES CloudResourceGroups(cloud_resource_group_id) ON DELETE SET NULL,
azure_sa_resource_id TEXT UNIQUE, -- Full Azure Resource ID
name VARCHAR(255) NOT NULL,
location VARCHAR(100),
account_kind VARCHAR(50), -- e.g., 'StorageV2', 'BlobStorage'
sku JSONB, -- { "name": "Standard_LRS", "tier": "Standard" }
access_tier VARCHAR(50), -- 'Hot', 'Cool' (for Blob)
https_only_enabled BOOLEAN,
tls_version VARCHAR(10), -- Minimum TLS version required
tags JSONB,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE CloudSubscriptionCosts ( -- Aggregated costs per subscription period
subscription_cost_id BIGSERIAL PRIMARY KEY,
cloud_subscription_id UUID NOT NULL REFERENCES CloudSubscriptions(cloud_subscription_id) ON DELETE CASCADE,
billing_period_start_date DATE NOT NULL,
billing_period_end_date DATE NOT NULL,
currency_code VARCHAR(3) NOT NULL,
total_cost NUMERIC(19, 4) NOT NULL,
cost_by_service JSONB, -- Optional: Breakdown like { "Virtual Machines": 123.45, "Storage": 67.89 }
cost_by_resource_group JSONB, -- Optional: Breakdown { "rg-prod": 100.00, "rg-dev": 91.34 }
last_updated TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this cost data was retrieved/calculated
UNIQUE (cloud_subscription_id, billing_period_start_date)
);
-- AWS
CREATE TABLE AwsAccounts (
aws_account_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE, -- Link to AWS Integration config
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Associated Client Org
aws_account_number VARCHAR(20) NOT NULL UNIQUE, -- AWS Account Number (12 digits)
account_alias VARCHAR(100) NULL, -- Friendly alias for the account
iam_user_alias VARCHAR(100) NULL, -- Alias for IAM sign-in URL
root_email VARCHAR(255) NULL, -- Email of the root user (if known, use with caution)
status VARCHAR(50) DEFAULT 'Active', -- 'Active', 'Suspended', 'Closed'
-- Consider adding OU info if using AWS Organizations heavily
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE AwsAccounts IS 'Represents linked AWS Accounts.';
CREATE TABLE AwsVpcs (
aws_vpc_record_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
aws_account_id UUID NOT NULL REFERENCES AwsAccounts(aws_account_id) ON DELETE CASCADE,
aws_vpc_id VARCHAR(50) NOT NULL UNIQUE, -- AWS VPC ID (e.g., vpc-xxxxxxxx)
region VARCHAR(50) NOT NULL, -- AWS Region (e.g., us-east-1)
cidr_block CIDR NOT NULL,
is_default BOOLEAN,
state VARCHAR(20), -- 'pending', 'available'
tags JSONB,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE AwsVpcs IS 'Represents AWS Virtual Private Clouds (VPCs).';
CREATE TABLE AwsRdsInstances ( -- Includes Databases, not just servers
aws_rds_instance_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
aws_account_id UUID NOT NULL REFERENCES AwsAccounts(aws_account_id) ON DELETE CASCADE,
db_instance_identifier VARCHAR(255) NOT NULL, -- User-defined identifier
db_instance_arn TEXT UNIQUE, -- Full AWS ARN
region VARCHAR(50) NOT NULL,
engine VARCHAR(50), -- 'mysql', 'postgres', 'sqlserver-se', 'oracle-ee', etc.
engine_version VARCHAR(50),
db_instance_class VARCHAR(50), -- e.g., 'db.t3.micro'
instance_state VARCHAR(50), -- 'available', 'creating', 'modifying', 'deleting'
endpoint_address VARCHAR(255),
endpoint_port INTEGER,
allocated_storage_gb INTEGER,
storage_type VARCHAR(50), -- 'gp2', 'io1', etc.
multi_az BOOLEAN,
publicly_accessible BOOLEAN,
tags JSONB,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (aws_account_id, region, db_instance_identifier)
);
COMMENT ON TABLE AwsRdsInstances IS 'Represents AWS Relational Database Service (RDS) instances.';
CREATE TABLE AwsS3Buckets (
aws_s3_bucket_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
aws_account_id UUID NOT NULL REFERENCES AwsAccounts(aws_account_id) ON DELETE CASCADE,
bucket_name VARCHAR(255) NOT NULL UNIQUE, -- Globally unique bucket name
region VARCHAR(50), -- Region where the bucket resides
creation_date TIMESTAMPTZ,
public_access_block_configuration JSONB,
versioning_enabled BOOLEAN,
logging_target_bucket VARCHAR(255),
logging_target_prefix VARCHAR(255),
tags JSONB,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE AwsS3Buckets IS 'Represents AWS Simple Storage Service (S3) buckets.';
CREATE TABLE AwsAccountCosts (
account_cost_id BIGSERIAL PRIMARY KEY,
aws_account_id UUID NOT NULL REFERENCES AwsAccounts(aws_account_id) ON DELETE CASCADE,
billing_period_start_date DATE NOT NULL,
billing_period_end_date DATE NOT NULL,
currency_code CHAR(3) NOT NULL, -- Removed FK to CurrencyCodes for flexibility
total_cost NUMERIC(19, 4) NOT NULL,
cost_by_service JSONB, -- Optional: Breakdown like { "AmazonEC2": 123.45, "AmazonS3": 67.89 }
cost_by_region JSONB, -- Optional: Breakdown { "us-east-1": 100.00, "ca-central-1": 91.34 }
last_updated TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (aws_account_id, billing_period_start_date)
);
COMMENT ON TABLE AwsAccountCosts IS 'Aggregated AWS costs per account billing period.';
-- GCP
CREATE TABLE GcpProjects (
gcp_project_record_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE, -- Link to GCP Integration config
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Associated Client Org
gcp_project_id VARCHAR(100) NOT NULL UNIQUE, -- GCP Project ID (e.g., 'my-cool-project-123')
gcp_project_number BIGINT UNIQUE, -- GCP Project Number
name VARCHAR(100), -- Friendly Name
parent_type VARCHAR(20), -- 'organization', 'folder'
parent_id VARCHAR(50), -- ID of the parent Org or Folder
lifecycle_state VARCHAR(20), -- 'ACTIVE', 'DELETE_REQUESTED'
labels JSONB, -- GCP Labels (similar to tags)
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE GcpProjects IS 'Represents linked Google Cloud Platform (GCP) Projects.';
CREATE TABLE GcpVpcNetworks (
gcp_vpc_record_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
gcp_project_record_id UUID NOT NULL REFERENCES GcpProjects(gcp_project_record_id) ON DELETE CASCADE,
gcp_network_id BIGINT UNIQUE, -- GCP Network resource ID
name VARCHAR(255) NOT NULL,
self_link TEXT UNIQUE, -- Full GCP resource URL
auto_create_subnetworks BOOLEAN,
routing_mode VARCHAR(20), -- 'REGIONAL', 'GLOBAL'
description TEXT,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE(gcp_project_record_id, name)
);
COMMENT ON TABLE GcpVpcNetworks IS 'Represents GCP Virtual Private Cloud (VPC) Networks.';
CREATE TABLE GcpCloudSqlInstances (
gcp_cloudsql_instance_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
gcp_project_record_id UUID NOT NULL REFERENCES GcpProjects(gcp_project_record_id) ON DELETE CASCADE,
instance_name VARCHAR(255) NOT NULL, -- User-defined instance name
self_link TEXT UNIQUE, -- Full GCP resource URL
region VARCHAR(50) NOT NULL,
database_version VARCHAR(50), -- e.g., 'MYSQL_8_0', 'POSTGRES_14'
state VARCHAR(50), -- 'RUNNABLE', 'SUSPENDED', 'PENDING_CREATE'
settings_tier VARCHAR(50), -- e.g., 'db-f1-micro'
settings_data_disk_size_gb BIGINT,
settings_ip_configuration JSONB, -- Includes authorized networks, private IP etc.
settings_backup_configuration JSONB,
settings_availability_type VARCHAR(20), -- 'REGIONAL', 'ZONAL'
labels JSONB,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (gcp_project_record_id, instance_name)
);
COMMENT ON TABLE GcpCloudSqlInstances IS 'Represents GCP Cloud SQL instances.';
CREATE TABLE GcpCloudStorageBuckets (
gcp_cs_bucket_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Internal CommandIT ID
gcp_project_record_id UUID NOT NULL REFERENCES GcpProjects(gcp_project_record_id) ON DELETE CASCADE,
bucket_name VARCHAR(255) NOT NULL UNIQUE, -- Globally unique bucket name
self_link TEXT UNIQUE, -- Full GCP resource URL
location VARCHAR(50), -- e.g., 'US-CENTRAL1'
location_type VARCHAR(20), -- 'region', 'multi-region', 'dual-region'
storage_class VARCHAR(50), -- 'STANDARD', 'NEARLINE', 'COLDLINE', 'ARCHIVE'
versioning_enabled BOOLEAN,
logging_config JSONB,
iam_configuration JSONB,
lifecycle_rules JSONB,
labels JSONB,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE GcpCloudStorageBuckets IS 'Represents GCP Cloud Storage buckets.';
CREATE TABLE GcpProjectCosts (
project_cost_id BIGSERIAL PRIMARY KEY,
gcp_project_record_id UUID NOT NULL REFERENCES GcpProjects(gcp_project_record_id) ON DELETE CASCADE,
billing_period_start_date DATE NOT NULL,
billing_period_end_date DATE NOT NULL,
currency_code CHAR(3) NOT NULL, -- Removed FK to CurrencyCodes for flexibility
total_cost NUMERIC(19, 4) NOT NULL,
cost_by_service JSONB, -- Optional: Breakdown like { "Compute Engine": 123.45, "Cloud Storage": 67.89 }
cost_by_sku JSONB, -- Optional: More granular breakdown
last_updated TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (gcp_project_record_id, billing_period_start_date)
);
COMMENT ON TABLE GcpProjectCosts IS 'Aggregated GCP costs per project billing period.';
-- Update Devices Table Comment for Cloud VMs
COMMENT ON COLUMN Devices.configuration IS 'JSONB field storing device-specific configuration. Examples: Cloud VM Details (provider, vm_id, size, region, power_state, ips), PBX Details (type, provider, admin_url), Network Device Details (snmp_oid).';
-- Example JSON for AWS EC2 in Devices.configuration:
-- { "cloud_provider": "AWS",
-- "aws_instance_id": "i-0abcdef1234567890",
-- "aws_account_number": "123456789012",
-- "aws_vpc_id": "vpc-abcdef",
-- "aws_subnet_id": "subnet-abcdef",
-- "instance_type": "t3.micro",
-- "region": "us-east-1",
-- "availability_zone": "us-east-1a",
-- "power_state": "running",
-- "private_ip_address": "172.31.10.5",
-- "public_ip_address": "54.x.x.x",
-- "ami_id": "ami-0abcdef123"
-- }
-- Example JSON for GCP GCE in Devices.configuration:
-- { "cloud_provider": "GCP",
-- "gcp_instance_id": "1234567890123456789",
-- "gcp_project_id": "my-cool-project-123",
-- "gcp_zone": "us-central1-a",
-- "machine_type": "e2-micro",
-- "power_status": "RUNNING",
-- "network_interfaces": [ { "network": "global/networks/default", "networkIP": "10.128.0.2", "accessConfigs": [ { "natIP": "35.x.x.x" } ] } ],
-- "disk_info": [ { "deviceName": "boot", "diskSizeGb": 10 } ]
-- }
-- =============================================
-- Telecom & Network Connectivity
-- =============================================
CREATE TABLE TelecomCarriers (
carrier_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP managing the carrier info
name VARCHAR(255) NOT NULL UNIQUE, -- e.g., 'Telus', 'Shaw', 'Twilio', 'Bell'
account_number VARCHAR(100),
support_phone VARCHAR(50),
support_email VARCHAR(255),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
-- Phone Systems can be modeled using the Devices table:
-- Set Devices.device_type = 'PBX' or 'VoIPSystem'
-- Store details in Devices.configuration JSONB:
-- { "system_type": "CloudPBX", "provider_name": "RingCentral", "admin_url": "...", "version": "..." }
-- OR
-- { "system_type": "OnPremPBX", "internal_ip": "...", "version": "...", "make": "Avaya", "model": "IP Office" }
CREATE TABLE PhoneNumbers ( -- Tracking DIDs, Toll-Free, Main Lines etc.
phone_number_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org owning/using the number
e164_number VARCHAR(30) NOT NULL UNIQUE, -- Phone number in E.164 format (e.g., +16045551234)
number_type VARCHAR(20) NOT NULL CHECK (number_type IN ('DID', 'TollFree', 'Main', 'Fax', 'Service', 'Mobile')),
carrier_id UUID NULL REFERENCES TelecomCarriers(carrier_id) ON DELETE SET NULL,
assigned_to_type VARCHAR(20) NULL CHECK (assigned_to_type IN ('User', 'Device', 'Location', 'CallQueue', 'AutoAttendant')),
assigned_to_entity_id VARCHAR(50) NULL, -- UUID or other ID based on assigned_to_type
is_sms_enabled BOOLEAN DEFAULT false, -- Can this number send/receive carrier SMS?
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE PhoneExtensions (
extension_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org
extension_number VARCHAR(20) NOT NULL,
phone_system_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Link to the PBX/VoIPSystem Device record
assigned_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- User assigned this extension
assigned_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Optional: Link to physical handset Device record
voicemail_enabled BOOLEAN,
display_name VARCHAR(100), -- Caller ID Name associated
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (phone_system_device_id, extension_number)
);
CREATE TABLE TelecomCircuits (
circuit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org using the circuit
carrier_id UUID NOT NULL REFERENCES TelecomCarriers(carrier_id) ON DELETE RESTRICT,
circuit_identifier VARCHAR(100) NOT NULL, -- Carrier's Circuit ID or unique identifier
circuit_type VARCHAR(30) NOT NULL CHECK (circuit_type IN ('Internet_Fibre', 'Internet_Coax', 'Internet_DSL', 'MPLS', 'P2P_Fibre', 'P2P_Ethernet', 'WAN', 'SIP_Trunk', 'PRIT1', 'PRIE1', 'POTS', 'Other')),
status VARCHAR(20) NOT NULL DEFAULT 'Active' CHECK (status IN ('Planning', 'Provisioning', 'Active', 'Inactive', 'Decommissioned')),
bandwidth_mbps_down NUMERIC(10, 2) NULL,
bandwidth_mbps_up NUMERIC(10, 2) NULL,
endpoint_a_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Location A (e.g., Office)
endpoint_a_details TEXT NULL, -- Specific demarcation details for A
endpoint_b_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Location B (e.g., Data Center, Carrier POP) - Optional
endpoint_b_details TEXT NULL, -- Specific demarcation details for B
related_contract_id UUID NULL REFERENCES VendorContracts(contract_id) ON DELETE SET NULL, -- Link to service contract
term_months INTEGER NULL,
install_date DATE NULL,
activation_date DATE NULL,
monthly_cost NUMERIC(19,4) NULL,
currency_code CHAR(3) NULL, -- Removed FK for flexibility
notes TEXT, -- Use polymorphic Notes table instead
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (org_id, circuit_identifier, carrier_id)
);
COMMENT ON TABLE TelecomCircuits IS 'Tracks data and voice circuits (Internet, WAN, MPLS, PRI, SIP Trunks etc.).';
CREATE TABLE VpnTunnels (
vpn_tunnel_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context
name VARCHAR(100) NOT NULL, -- User-friendly name (e.g., 'Office-to-Azure', 'Branch1-to-HQ')
tunnel_type VARCHAR(20) NOT NULL CHECK (tunnel_type IN ('SiteToSite', 'ClientToSite', 'RemoteAccessUser')),
status VARCHAR(20) NOT NULL DEFAULT 'Active' CHECK (status IN ('Planning', 'Configured', 'Active', 'Inactive', 'Down', 'Error')),
-- Endpoint Info
local_gateway_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Firewall/Router device managing local end
local_gateway_ip INET NULL, -- Public IP of local gateway
local_networks CIDR[] NULL, -- Local subnets accessible via tunnel
remote_gateway_ip INET NULL, -- Public IP of remote endpoint (for SiteToSite)
remote_gateway_fqdn VARCHAR(255) NULL, -- FQDN if IP is dynamic
remote_gateway_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Link if remote end is also a managed Device
remote_networks CIDR[] NULL, -- Remote subnets accessible via tunnel (for SiteToSite)
-- Client VPN Specific
client_vpn_pool CIDR NULL, -- IP pool for remote access clients
client_auth_method VARCHAR(50) NULL, -- e.g., 'Certificate', 'PreSharedKey', 'Radius', 'SAML'
-- Tunnel Configuration
protocol VARCHAR(20) DEFAULT 'IPsec' CHECK (protocol IN ('IPsec', 'OpenVPN', 'WireGuard', 'SSLVPN', 'Other')),
ike_version INTEGER NULL, -- 1 or 2 for IPsec
encryption_details JSONB NULL, -- { "phase1_alg": "AES256", "phase1_hash": "SHA256", "phase1_dh_group": 14, "phase2_pfs_group": 14, ... }
pre_shared_key_ref TEXT NULL, -- Vault reference for PSK
related_circuit_id UUID NULL REFERENCES TelecomCircuits(circuit_id) ON DELETE SET NULL, -- Underlying circuit if applicable
notes TEXT, -- Use polymorphic Notes table instead
last_status_check TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (org_id, name)
);
COMMENT ON TABLE VpnTunnels IS 'Tracks VPN tunnels (Site-to-Site, Remote Access).';
COMMENT ON COLUMN VpnTunnels.pre_shared_key_ref IS 'Reference to the Pre-Shared Key stored securely (e.g., in external vault or Credentials table).';
-- =============================================
-- Additional Logging Tables
-- =============================================
CREATE TABLE StorageAccessLog (
log_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Endpoint device where action occurred
event_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Timestamp from the endpoint when action attempted/logged
user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- User performing the action
process_id INTEGER NULL, -- Optional: Process ID performing the action
process_path TEXT NULL, -- Optional: Path of the process executable
action_type VARCHAR(30) NOT NULL CHECK (action_type IN ('FileRead', 'FileWrite', 'FileDelete', 'FileCopy', 'FileRename', 'Execute', 'FolderCreate', 'FolderDelete', 'FolderList', 'AccessDenied')), -- Type of operation
action_result VARCHAR(10) NOT NULL CHECK (action_result IN ('Allowed', 'Denied')), -- Outcome based on policy
storage_type VARCHAR(20) CHECK (storage_type IN ('USB', 'NetworkShare', 'LocalFixed', 'LocalRemovable', 'CDDVD', 'Other')), -- Type of storage accessed
device_instance_id VARCHAR(255) NULL, -- Optional: Hardware ID for external/removable devices
serial_number VARCHAR(255) NULL, -- Optional: Serial number of the external device, if available
drive_letter_or_mount VARCHAR(50) NULL, -- Optional: Drive letter or mount point involved
network_share_path TEXT NULL, -- Optional: UNC path if storage_type is 'NetworkShare'
file_path TEXT NOT NULL, -- The primary file/folder path involved in the action
file_size_bytes BIGINT NULL, -- Optional: Size of the file accessed/copied
file_hash_sha256 VARCHAR(64) NULL, -- Optional: SHA256 hash of the file involved
destination_path TEXT NULL, -- Optional: Destination path for FileCopy/FileRename actions
matched_policy_id UUID NULL REFERENCES StorageControlPolicies(policy_id) ON DELETE SET NULL, -- The Storage Control Policy that matched this event
matched_rule_id UUID NULL REFERENCES StorageControlRules(rule_id) ON DELETE SET NULL, -- The specific Storage Control Rule that determined the action_result
details TEXT NULL -- Any other relevant details captured by the agent
-- Add indexes on timestamp, device_id, user_id, action_result, storage_type, file_path (consider FTS)
);
CREATE TABLE ApplicationExecutionLog (
log_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
event_timestamp_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
user_sid VARCHAR(100), -- SID of the user attempting execution
user_name VARCHAR(255), -- Username
process_path TEXT NOT NULL, -- Full path of the executable
process_hash_sha256 VARCHAR(64), -- SHA256 hash of the executable
publisher_info JSONB, -- Extracted certificate/publisher info
parent_process_path TEXT,
command_line TEXT,
action_taken VARCHAR(10) NOT NULL CHECK (action_taken IN ('Allowed', 'Denied')), -- Result of Allowlisting policy
matched_policy_id UUID REFERENCES ExecutionControlPolicies(policy_id) ON DELETE SET NULL,
matched_rule_id UUID REFERENCES ExecutionControlRules(rule_id) ON DELETE SET NULL,
denial_reason VARCHAR(255) -- e.g., 'No matching Allow rule', 'Explicit Deny rule'
-- Add indexes on timestamp, device_id, user_name, process_path, action_taken
);
CREATE TABLE ElevationRequestLog (
log_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
request_timestamp_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
requesting_user_sid VARCHAR(100),
requesting_user_name VARCHAR(255),
process_path TEXT NOT NULL, -- Application requested for elevation
process_hash_sha256 VARCHAR(64),
command_line TEXT,
requested_action VARCHAR(20) NOT NULL CHECK (requested_action IN ('Elevate', 'ElevateWithApproval')), -- From matching rule
approval_request_id UUID REFERENCES ApprovalRequests(approval_request_id) ON DELETE SET NULL, -- Link if approval needed
justification TEXT, -- User-provided justification
status VARCHAR(20) NOT NULL CHECK (status IN ('PendingApproval', 'Approved', 'AutoApproved', 'Denied', 'UserCancelled', 'Timeout', 'Error')), -- Status of the elevation attempt
final_action VARCHAR(10) CHECK (final_action IN ('Allowed', 'Denied')), -- Was elevation ultimately granted?
processed_by_user_id UUID REFERENCES Users(user_id) ON DELETE SET NULL, -- Admin who approved/denied (if manual)
processed_timestamp TIMESTAMPTZ,
denial_reason TEXT
-- Add indexes on timestamp, device_id, requesting_user_name, process_path, status
);
-- =============================================
-- Software Asset Management (SAM)
-- =============================================
CREATE TABLE SoftwareLicenses (
license_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org that owns/purchased the license
software_product_id UUID NOT NULL REFERENCES SoftwareProducts(software_product_id) ON DELETE RESTRICT, -- Link to the software catalog item
license_type VARCHAR(30) NOT NULL CHECK (license_type IN ('Perpetual', 'Subscription', 'CAL_User', 'CAL_Device', 'OEM', 'VolumeMAK', 'VolumeKMS', 'Other')),
license_key TEXT NULL, -- Store securely or use vault reference if storing actual key
purchased_quantity INTEGER NOT NULL CHECK (purchased_quantity >= 0), -- Number of seats/licenses purchased
license_metric VARCHAR(30) CHECK (license_metric IN ('Seat', 'User', 'Device', 'Processor', 'Core', 'PVU', 'CAL_User', 'CAL_Device', 'Usage', 'Other')), -- The primary metric by which this license is measured (Seat, Core, PVU, etc.).
metric_quantity NUMERIC(19,4) NULL, -- The quantity associated with the license_metric (e.g., number of Cores, PVU points). Null if metric is Seat/User/Device (use purchased_quantity).
is_upgrade BOOLEAN DEFAULT false, -- Is this an upgrade license requiring a base license?
base_license_id UUID NULL REFERENCES SoftwareLicenses(license_id) ON DELETE SET NULL, -- Link to required base license if is_upgrade=true
license_pool_id UUID NULL REFERENCES SoftwareLicensePools(license_pool_id) ON DELETE SET NULL, -- Link to a SoftwareLicensePool if this license contributes entitlements to a pool.
purchase_date DATE NULL,
expiry_date DATE NULL, -- Relevant for Subscriptions
purchase_cost NUMERIC(19,4) NULL,
recurring_cost NUMERIC(19,4) NULL,
recurring_cycle VARCHAR(20) CHECK (recurring_cycle IN ('Monthly', 'Quarterly', 'Annually')),
currency_code CHAR(3) NULL, -- FK to CurrencyCodes optional
purchase_order_id UUID NULL REFERENCES PurchaseOrders(purchase_order_id) ON DELETE SET NULL, -- Link to PO if purchased
agreement_id UUID NULL REFERENCES Agreements(agreement_id) ON DELETE SET NULL, -- Link to Agreement if part of contract
vendor_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE SET NULL, -- Vendor purchased from
external_id VARCHAR(100), -- ID from external asset system
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
COMMENT ON TABLE SoftwareLicenses IS 'Tracks purchased or subscribed software license entitlements, including metric details.';
COMMENT ON COLUMN SoftwareLicenses.license_key IS 'Stores license key if applicable (consider secure storage/vault reference).';
COMMENT ON COLUMN SoftwareLicenses.purchased_quantity IS 'Total number of license seats/activations purchased (primary quantity if metric is seat/user/device based).';
COMMENT ON COLUMN SoftwareLicenses.license_metric IS 'The primary metric by which this license is measured (Seat, Core, PVU, etc.).';
COMMENT ON COLUMN SoftwareLicenses.metric_quantity IS 'The quantity associated with the license_metric (e.g., number of Cores, PVU points). Relevant if metric is not seat/user/device based.';
COMMENT ON COLUMN SoftwareLicenses.license_pool_id IS 'Link to a SoftwareLicensePool if this license contributes entitlements to a pool.';
CREATE TABLE SoftwareLicenseAssignments (
assignment_id BIGSERIAL PRIMARY KEY,
license_id UUID NOT NULL REFERENCES SoftwareLicenses(license_id) ON DELETE CASCADE,
assigned_to_user_id UUID NULL REFERENCES Users(user_id) ON DELETE CASCADE, -- Assign to User
assigned_to_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Assign to Device
assigned_date TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
assignment_notes TEXT, -- Use polymorphic Notes table instead
CHECK (assigned_to_user_id IS NOT NULL OR assigned_to_device_id IS NOT NULL), -- Must assign to user OR device
UNIQUE (license_id, assigned_to_user_id) WHERE assigned_to_user_id IS NOT NULL, -- Prevent double assignment to same user
UNIQUE (license_id, assigned_to_device_id) WHERE assigned_to_device_id IS NOT NULL -- Prevent double assignment to same device
);
COMMENT ON TABLE SoftwareLicenseAssignments IS 'Links SoftwareLicenses to the Users or Devices they are assigned to.';
CREATE TABLE SoftwareLicensePools (
license_pool_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org managing this pool
software_product_id UUID NOT NULL REFERENCES SoftwareProducts(software_product_id) ON DELETE RESTRICT, -- Software covered by pool
pool_name VARCHAR(150) NOT NULL, -- e.g., "Microsoft EES Agreement Pool", "Adobe Creative Cloud Pool"
description TEXT,
license_metric VARCHAR(30) NOT NULL, -- Metric for entitlements in this pool (should match related licenses)
total_entitlement NUMERIC(19,4) NOT NULL DEFAULT 0, -- Calculated sum of metric_quantity/purchased_quantity from linked licenses
-- consumed_entitlement NUMERIC(19,4) NOT NULL DEFAULT 0, -- Consider calculating this via assignments or periodic jobs
notes TEXT, -- Use polymorphic Notes table instead
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (org_id, software_product_id, pool_name)
);
COMMENT ON TABLE SoftwareLicensePools IS 'Represents pools of license entitlements, often from volume agreements, aggregating individual licenses.';
COMMENT ON COLUMN SoftwareLicensePools.total_entitlement IS 'Total quantity of licenses available in this pool based on linked SoftwareLicenses.';
-- Add FK constraint to SoftwareLicenses now that SoftwareLicensePools exists
ALTER TABLE SoftwareLicenses ADD CONSTRAINT fk_licenses_pool FOREIGN KEY (license_pool_id) REFERENCES SoftwareLicensePools(license_pool_id) ON DELETE SET NULL;
CREATE TABLE SoftwareLicenseEvidence (
evidence_id BIGSERIAL PRIMARY KEY,
device_software_id BIGINT NOT NULL REFERENCES DeviceSoftware(device_software_id) ON DELETE CASCADE, -- The specific software installation this evidence applies to
evidence_type VARCHAR(30) NOT NULL CHECK (
evidence_type IN (
'AssignedEntitlement', -- Linked to a SoftwareLicenseAssignment record
'ManualKeyEntry', -- Manually entered key (retail box, documentation)
'OEM_COA', -- OEM License evidenced by COA sticker
'OEM_BIOS', -- OEM License embedded in BIOS/Firmware
'VolumeMAK', -- Installation uses a specific MAK key (linked via details/assignment)
'VolumeKMS', -- Installation activated via KMS (detected key might be generic)
'Unlicensed', -- Known to be unlicensed
'LicenseIncludedWithHardware', -- e.g., bundled software not tracked separately
'DocumentedException', -- Approved exception (e.g., dev/test use)
'Other' -- Other documented evidence
)
),
evidence_details TEXT NULL, -- Store Manual Key (use vault ref!), COA notes, BIOS key presence flag, KMS server info, exception reason, etc.
software_license_assignment_id UUID NULL REFERENCES SoftwareLicenseAssignments(assignment_id) ON DELETE SET NULL, -- Link if evidence_type is 'AssignedEntitlement' or relates to a specific assignment
is_primary_evidence BOOLEAN NOT NULL DEFAULT true, -- If multiple evidence records exist for one installation, which is the primary one used for compliance? (Ensure only one TRUE per device_software_id via application logic or partial index)
validation_status VARCHAR(20) DEFAULT 'Pending' CHECK (validation_status IN ('Pending', 'Valid', 'Invalid', 'Mismatch', 'RequiresReview', 'NotApplicable')), -- Status of this evidence record (e.g., does manual key match detected key? Is assignment valid?)
last_validated_at TIMESTAMPTZ, -- When validation_status was last updated
validated_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
updated_at TIMESTAMPTZ, -- Auto-updated
updated_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL
-- Optional: Add a unique constraint to enforce only one primary evidence per installation
-- CONSTRAINT softwarelicenseevidence_one_primary UNIQUE (device_software_id, is_primary_evidence) WHERE is_primary_evidence = true;
-- Note: PostgreSQL 15+ supports UNIQUE NULLS NOT DISTINCT which might be better if is_primary_evidence can be NULL. For now, assume only one primary.
);
COMMENT ON TABLE SoftwareLicenseEvidence IS 'Links a specific software installation (DeviceSoftware) to its licensing proof or method.';
COMMENT ON COLUMN SoftwareLicenseEvidence.evidence_type IS 'Describes the nature of the licensing proof (Assignment, Manual Key, OEM, Volume, Exception, etc.).';
COMMENT ON COLUMN SoftwareLicenseEvidence.evidence_details IS 'Contains the actual proof details (vaulted key reference, COA notes, exception reason, etc.).';
COMMENT ON COLUMN SoftwareLicenseEvidence.software_license_assignment_id IS 'Links to the formal license assignment record if applicable.';
COMMENT ON COLUMN SoftwareLicenseEvidence.is_primary_evidence IS 'Indicates if this is the primary record used for compliance determination for this installation.';
COMMENT ON COLUMN SoftwareLicenseEvidence.validation_status IS 'Indicates whether this evidence has been validated (e.g., key checked, assignment verified).';
CREATE TABLE SoftwareLicenseEvidenceAttachments (
evidence_id BIGINT NOT NULL REFERENCES SoftwareLicenseEvidence(evidence_id) ON DELETE CASCADE, -- Link to the specific license evidence record
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE, -- Link to the uploaded file/image in the Attachments table
description TEXT NULL, -- Optional description (e.g., "Photo of COA Sticker", "Scan of License Certificate", "Invoice showing purchase")
attached_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the link was created
PRIMARY KEY (evidence_id, attachment_id) -- A piece of evidence can have multiple attachments, and an attachment could theoretically link to multiple evidence items (though less likely)
);
COMMENT ON TABLE SoftwareLicenseEvidenceAttachments IS 'Links uploaded images or documents (from Attachments table) as proof to SoftwareLicenseEvidence records.';
COMMENT ON COLUMN SoftwareLicenseEvidenceAttachments.description IS 'Optional text describing the attached file in the context of the license evidence.';
-- =============================================
-- SaaS User Assignment
-- =============================================
CREATE TABLE SaaSUserAssignments (
saas_user_assignment_id BIGSERIAL PRIMARY KEY,
integration_instance_id UUID NOT NULL REFERENCES IntegrationInstances(instance_id) ON DELETE CASCADE, -- The specific M365/Google tenant integration
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE, -- The CommandIT user (linked to the SaaS user)
-- Optional direct links if helpful for querying:
-- azure_ad_user_id UUID NULL REFERENCES AzureAdUsers(azure_ad_user_id) ON DELETE SET NULL,
-- google_user_id VARCHAR(255) NULL, -- Placeholder if Google sync added later
product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE RESTRICT, -- Link to the Product representing the SaaS SKU (e.g., M365 E3)
assigned_datetime TIMESTAMPTZ NULL, -- When the license was assigned in the SaaS platform
status VARCHAR(30) DEFAULT 'Active' CHECK (status IN ('Active', 'Suspended', 'Removed', 'Warning')), -- Status from SaaS platform
last_sync_time TIMESTAMPTZ, -- When this assignment info was last synced
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (integration_instance_id, user_id, product_id) -- User should only have one assignment of a specific SKU per tenant
);
COMMENT ON TABLE SaaSUserAssignments IS 'Tracks assignment of specific SaaS licenses/SKUs (represented as Products) to users within integrated cloud platforms.';
COMMENT ON COLUMN SaaSUserAssignments.integration_instance_id IS 'Links to the specific tenant (e.g., M365 tenant integration) where the license is assigned.';
COMMENT ON COLUMN SaaSUserAssignments.product_id IS 'Links to the Product table entry representing the specific SaaS license SKU (e.g., Microsoft 365 E3, Google Workspace Business Standard).';
-- =============================================
-- Audit Log
-- =============================================
CREATE TABLE AuditLog (
audit_log_id BIGSERIAL PRIMARY KEY,
event_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the event occurred
user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- User performing the action (NULL if system)
impersonator_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- If an admin impersonated another user
org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE SET NULL, -- Organization context of the action
action_type VARCHAR(100) NOT NULL, -- e.g., 'CREATE', 'UPDATE', 'DELETE', 'LOGIN', 'LOGOUT', 'ENABLE', 'DISABLE', 'RUN_SCRIPT', 'APPROVE', 'REJECT'
target_entity_type VARCHAR(50) NULL, -- e.g., 'Ticket', 'Device', 'User', 'Policy'
target_entity_id VARCHAR(50) NULL, -- Primary key of the target entity (UUID/BIGINT/INT stored as string)
target_entity_name VARCHAR(255) NULL, -- Optional human-readable name/identifier of the target
source_ip_address INET NULL, -- IP address where the action originated
source_platform VARCHAR(50) NULL, -- Platform/OS action originated from (e.g., 'Windows', 'macOS', 'iOS', 'Android', 'Web')
source_language VARCHAR(20) NULL, -- Language/Locale of the source (e.g., 'en-US', 'fr-CA')
user_agent TEXT NULL, -- Browser/client user agent string
source_geo_location JSONB NULL, -- GeoIP lookup results { "city": "...", "region": "...", "country_code": "..." }
change_details JSONB NULL, -- Stores old/new values for updates, or context/parameters for other actions
status VARCHAR(20) NOT NULL DEFAULT 'Success' CHECK (status IN ('Success', 'Failure', 'Attempt')), -- Outcome of the action
failure_reason TEXT NULL -- Reason if status is 'Failure'
-- Add indexes on timestamp, user_id, org_id, action_type, target_entity_type, target_entity_id
);
COMMENT ON TABLE AuditLog IS 'Records significant actions, changes, logins, and logouts within the system for auditing purposes.';
COMMENT ON COLUMN AuditLog.source_platform IS 'Operating System or platform from which the action originated.';
COMMENT ON COLUMN AuditLog.source_language IS 'Language/Locale setting reported by the originating client.';
COMMENT ON COLUMN AuditLog.source_geo_location IS 'Approximate geolocation derived from the source_ip_address (JSONB format).';
COMMENT ON COLUMN AuditLog.change_details IS 'Stores old/new values for UPDATE actions, or context/parameters for other action types (JSONB format).';
-- =============================================
-- Probe
-- =============================================
CREATE TABLE NetworkScanScopes (
scan_scope_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org context for the scan
name VARCHAR(255) NOT NULL, -- User-friendly name for this scan scope (e.g., "Main Office Subnet Scan", "Server VLAN Scan")
description TEXT,
assigned_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Optional link to the primary Location being scanned
assigned_probe_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE RESTRICT, -- Which device runs this scan (must have Probe component enabled)
-- Scan Targeting
included_subnets CIDR[] NOT NULL, -- Array of subnets to actively scan (e.g., ['192.168.1.0/24', '10.0.0.0/16'])
excluded_ips INET[] NULL, -- Array of specific IPs or CIDR ranges to exclude within the included subnets
-- Scan Settings
scan_interval_seconds INTEGER NOT NULL DEFAULT 3600, -- How often to repeat the scan (e.g., 3600 = 1 hour)
scan_intensity VARCHAR(20) DEFAULT 'Normal' CHECK (scan_intensity IN ('Low', 'Normal', 'High')), -- Hint for throttling/timing
perform_ping_sweep BOOLEAN NOT NULL DEFAULT true, -- Basic ICMP discovery
perform_port_scan BOOLEAN NOT NULL DEFAULT true, -- Scan for common/defined open ports
port_scan_tcp_ports TEXT NULL, -- Comma-separated list/range of TCP ports (e.g., '21-23,80,443,3389') or NULL for default list
port_scan_udp_ports TEXT NULL, -- Comma-separated list/range of UDP ports or NULL for default list
perform_os_detection BOOLEAN NOT NULL DEFAULT true, -- Attempt OS fingerprinting
-- Credential Usage Flags (Attempt to use credentials if provided below?)
attempt_snmp BOOLEAN NOT NULL DEFAULT true,
attempt_wmi BOOLEAN NOT NULL DEFAULT true,
attempt_ssh BOOLEAN NOT NULL DEFAULT true,
discover_vmware BOOLEAN NOT NULL DEFAULT true, -- Attempt VMware API connection using creds
discover_ad BOOLEAN NOT NULL DEFAULT true, -- Attempt AD LDAP query using creds
-- Default Credentials for Scan Scope (Links to secure Credential store)
default_snmp_credential_id UUID NULL REFERENCES Credentials(credential_id) ON DELETE SET NULL,
default_windows_credential_id UUID NULL REFERENCES Credentials(credential_id) ON DELETE SET NULL, -- For WMI
default_ssh_credential_id UUID NULL REFERENCES Credentials(credential_id) ON DELETE SET NULL,
vmware_credential_id UUID NULL REFERENCES Credentials(credential_id) ON DELETE SET NULL, -- For vCenter/ESXi
ad_credential_id UUID NULL REFERENCES Credentials(credential_id) ON DELETE SET NULL, -- For LDAP queries
-- Status
is_active BOOLEAN NOT NULL DEFAULT true, -- Is this scan scope enabled?
last_scan_start_time TIMESTAMPTZ NULL,
last_scan_end_time TIMESTAMPTZ NULL,
last_scan_status VARCHAR(20) NULL CHECK (last_scan_status IN ('Success', 'PartialSuccess', 'Failed')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE(org_id, name),
UNIQUE(assigned_probe_device_id, name) -- Probe can have multiple named scopes, but names unique per probe
);
COMMENT ON TABLE NetworkScanScopes IS 'Defines network discovery/scanning tasks assigned to Probe devices.';
COMMENT ON COLUMN NetworkScanScopes.assigned_probe_device_id IS 'The Device ID of the machine running the Probe component for this scan.';
COMMENT ON COLUMN NetworkScanScopes.included_subnets IS 'Array of CIDR notations defining the IP ranges to be scanned.';
COMMENT ON COLUMN NetworkScanScopes.scan_intensity IS 'Hint for agent regarding scan speed/aggressiveness/throttling.';
COMMENT ON COLUMN NetworkScanScopes.default_snmp_credential_id IS 'Link to Credentials table entry holding default SNMP v1/v2c community or v3 creds for this scope.';
COMMENT ON COLUMN NetworkScanScopes.default_windows_credential_id IS 'Link to Credentials table entry holding default Windows Admin creds for WMI scans within this scope.';
CREATE TABLE DeviceOpenPorts (
device_open_port_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- The device where the port was found open
port INTEGER NOT NULL CHECK (port > 0 AND port <= 65535),
protocol VARCHAR(3) NOT NULL CHECK (protocol IN ('TCP', 'UDP')),
service_name VARCHAR(100) NULL, -- Service commonly associated (e.g., 'http', 'ssh', 'rdp') based on port number or banner grab
service_version VARCHAR(100) NULL, -- Version information if banner grabbing was successful
first_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this open port was first detected on this device
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this open port was last confirmed present by a scan
scan_source_probe_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Which probe detected/confirmed this
is_active_in_scan BOOLEAN NOT NULL DEFAULT true, -- Used for delta processing (detecting closed ports)
UNIQUE (device_id, port, protocol)
-- Add index on device_id, last_seen_time
);
COMMENT ON TABLE DeviceOpenPorts IS 'Stores information about open TCP/UDP ports discovered on devices by network probes.';
COMMENT ON COLUMN DeviceOpenPorts.is_active_in_scan IS 'Internal flag used during delta processing to mark ports no longer detected as closed.';
-- =============================================
-- Service Management
-- =============================================
-- Table to track service assignments specifically for Devices
CREATE TABLE DeviceServiceAssignments (
device_assignment_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
service_category_id VARCHAR(100) NOT NULL, -- Matches Products.category for the assigned product
assigned_product_id UUID NULL REFERENCES Products(product_id) ON DELETE SET NULL, -- The specific plan assigned, NULL if 'None'
assignment_method VARCHAR(20) NOT NULL CHECK (assignment_method IN ('Manual', 'Policy', 'Default')), -- How was this assignment determined?
policy_assignment_source_ref TEXT NULL, -- Reference to the policy/rule causing this assignment, e.g., 'TagPolicyAssignment:123', 'OrgDefault:abc'
override_price NUMERIC(19,4) NULL, -- Specific price for this assignment, overriding calculated price
override_currency_code VARCHAR(3) NULL, -- Currency for the override_price (references CurrencyCodes implicitly)
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
assigned_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
-- Ensure only one assignment per device per category
UNIQUE (device_id, service_category_id)
);
-- Add indexes for performance
CREATE INDEX idx_deviceserviceassignments_device ON DeviceServiceAssignments(device_id);
CREATE INDEX idx_deviceserviceassignments_product ON DeviceServiceAssignments(assigned_product_id);
CREATE INDEX idx_deviceserviceassignments_org ON DeviceServiceAssignments(org_id);
CREATE INDEX idx_deviceserviceassignments_category ON DeviceServiceAssignments(service_category_id);
COMMENT ON TABLE DeviceServiceAssignments IS 'Tracks the assignment of specific service products (plans) to devices for different service categories.';
COMMENT ON COLUMN DeviceServiceAssignments.service_category_id IS 'Identifier for the service category (e.g., ''Antivirus'', matching Products.category).';
COMMENT ON COLUMN DeviceServiceAssignments.assigned_product_id IS 'FK to the Products table for the specific service plan assigned. NULL indicates no service (''None'') is assigned for this category.';
COMMENT ON COLUMN DeviceServiceAssignments.assignment_method IS 'Indicates if the assignment was made manually, derived from a policy, or a default.';
COMMENT ON COLUMN DeviceServiceAssignments.policy_assignment_source_ref IS 'Reference identifying the specific policy or rule that dictated this assignment (if method is ''Policy'').';
COMMENT ON COLUMN DeviceServiceAssignments.override_price IS 'A specific price manually set for this assignment, bypassing standard pricing rules.';
-- Table to track service assignments specifically for Users
CREATE TABLE UserServiceAssignments (
user_assignment_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org
user_id UUID NOT NULL REFERENCES Users(user_id) ON DELETE CASCADE,
service_category_id VARCHAR(100) NOT NULL, -- Matches Products.category for the assigned product
assigned_product_id UUID NULL REFERENCES Products(product_id) ON DELETE SET NULL, -- The specific plan assigned, NULL if 'None'
assignment_method VARCHAR(20) NOT NULL CHECK (assignment_method IN ('Manual', 'Policy', 'Default')),
policy_assignment_source_ref TEXT NULL,
override_price NUMERIC(19,4) NULL,
override_currency_code VARCHAR(3) NULL,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
assigned_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
updated_at TIMESTAMPTZ, -- Auto-updated by trigger
-- Ensure only one assignment per user per category
UNIQUE (user_id, service_category_id)
);
-- Add indexes for performance
CREATE INDEX idx_userserviceassignments_user ON UserServiceAssignments(user_id);
CREATE INDEX idx_userserviceassignments_product ON UserServiceAssignments(assigned_product_id);
CREATE INDEX idx_userserviceassignments_org ON UserServiceAssignments(org_id);
CREATE INDEX idx_userserviceassignments_category ON UserServiceAssignments(service_category_id);
COMMENT ON TABLE UserServiceAssignments IS 'Tracks the assignment of specific service products (plans) to users for different service categories.';
-- Optional but recommended: Table to store service recommendations
CREATE TABLE ServiceRecommendations (
recommendation_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Client Org context
scope_type VARCHAR(20) NOT NULL CHECK (scope_type IN ('Organization', 'Location', 'DeviceType', 'UserGroup', 'Default')), -- Scope of recommendation
scope_value VARCHAR(255) NULL, -- OrgID, LocationID, DeviceType Name, Group Name/ID. NULL for 'Default'.
service_category_id VARCHAR(100) NOT NULL, -- Category being recommended for
recommended_product_id UUID NOT NULL REFERENCES Products(product_id) ON DELETE RESTRICT, -- The recommended plan
priority INTEGER NOT NULL DEFAULT 0, -- For resolving conflicting recommendations (lower = higher priority)
reason TEXT NULL, -- Optional explanation for the recommendation
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
-- Ensure recommendation uniqueness based on scope and category
UNIQUE (org_id, scope_type, scope_value, service_category_id)
);
-- Add indexes for performance
CREATE INDEX idx_servicerecommendations_scope ON ServiceRecommendations(org_id, scope_type, scope_value, service_category_id);
CREATE INDEX idx_servicerecommendations_product ON ServiceRecommendations(recommended_product_id);
COMMENT ON TABLE ServiceRecommendations IS 'Stores recommended service products based on different scopes (Org, Location, Device Type, etc.).';
COMMENT ON COLUMN ServiceRecommendations.scope_type IS 'Defines the level at which the recommendation applies (e.g., for all Devices of a certain Type).';
COMMENT ON COLUMN ServiceRecommendations.scope_value IS 'Identifier matching the scope_type (e.g., ''Laptop'', Location UUID). NULL if scope_type is ''Default''.';
COMMENT ON COLUMN ServiceRecommendations.priority IS 'Priority for applying recommendations if multiple rules match (lower number wins).';
-- Stores blocked senders/recipients
CREATE TABLE EmailBlocklist (
blocklist_id BIGSERIAL PRIMARY KEY,
org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- NULL for global platform blocks
target TEXT NOT NULL, -- Email address ([email protected]) or domain (@example.com or example.com)
block_type VARCHAR(20) NOT NULL CHECK (block_type IN ('InboundSender', 'OutboundRecipient')),
scope VARCHAR(10) NOT NULL CHECK (scope IN ('Address', 'Domain')),
reason TEXT NULL,
created_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_emailblocklist_target_type ON EmailBlocklist(target, block_type);
CREATE INDEX idx_emailblocklist_org_target_type ON EmailBlocklist(org_id, target, block_type);
COMMENT ON TABLE EmailBlocklist IS 'Stores email addresses and domains blocked for inbound or outbound mail processing.';
-- Temporary staging table for raw inbound emails
CREATE TABLE InboundEmailProcessingQueue (
queue_id BIGSERIAL PRIMARY KEY,
message_id TEXT NULL UNIQUE, -- Email's Message-ID header, unique if available and reliable
received_at_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the email was ingested into the queue
service_board_email_address TEXT NULL, -- The specific CommandIT address email was sent TO
target_entity_type VARCHAR(50) NULL, -- Type detected from TO address: 'ServiceBoard', 'AlertEndpoint', 'Project', 'Problem', 'ChangeRequest', 'Device', 'Application', 'MonitoredDomain', etc.
target_entity_id VARCHAR(50) NULL, -- Corresponding ID (UUID/INT/BIGINT stored as string) of the target entity
headers TEXT NULL, -- Store full or key email headers as text or JSONB
body_html TEXT NULL, -- HTML body content, if present
body_text TEXT NULL, -- Plain text body content, if present
raw_eml_storage_path TEXT NULL, -- Recommended: Path/key to the raw .eml file stored temporarily in object storage (e.g., S3/Azure Blob with short TTL)
processing_status VARCHAR(30) NOT NULL DEFAULT 'Received' CHECK (processing_status IN (
'Received', -- Initial state upon ingestion
'Processing', -- Picked up by AI Triage Agent
'Failed_Blocklist', -- Failed pre-check: Sender blocked
'Failed_Spam', -- Failed pre-check: Classified as Spam
'Failed_Processing', -- AI Triage failed during processing (needs review/retry)
'Complete_Processed', -- Successfully processed into ticket/project/etc.
'Complete_Discarded' -- Final state for Blocked/Spam before deletion trigger
)),
status_reason TEXT NULL, -- Context for the final status (e.g., 'Blocked domain: xyz.com', 'Spam score 9.5', 'Processed to Ticket #12345', 'Error: Cannot identify organization')
last_attempt_at TIMESTAMPTZ NULL, -- Timestamp of the last processing attempt (by AI)
retry_count INTEGER NOT NULL DEFAULT 0, -- Number of processing attempts made
linked_processing_log_id BIGINT NULL REFERENCES EmailProcessingLog(log_id) ON DELETE SET NULL, -- Link to the final metadata log entry created after processing/discarding
-- Link to the permanent record created (only one should be relevant per processed email)
linked_ticket_update_id BIGINT NULL REFERENCES TicketUpdates(update_id) ON DELETE SET NULL,
linked_project_update_id BIGINT NULL REFERENCES ProjectUpdates(project_update_id) ON DELETE SET NULL,
linked_problem_update_id BIGINT NULL REFERENCES ProblemUpdates(problem_update_id) ON DELETE SET NULL,
linked_change_update_id BIGINT NULL REFERENCES ChangeRequestUpdates(change_update_id) ON DELETE SET NULL
);
-- Indexes for efficient querying by status and potentially for duplicate message ID checks
CREATE INDEX idx_inbound_email_queue_status ON InboundEmailProcessingQueue(processing_status, received_at_utc);
CREATE INDEX idx_inbound_email_queue_message_id ON InboundEmailProcessingQueue(message_id) WHERE message_id IS NOT NULL;
CREATE INDEX idx_inbound_email_queue_target ON InboundEmailProcessingQueue(target_entity_type, target_entity_id);
COMMENT ON TABLE InboundEmailProcessingQueue IS 'Temporary staging table for raw inbound emails awaiting AI Triage processing. Records and associated raw content (e.g., in object storage) are deleted after successful processing or discarding based on status.';
COMMENT ON COLUMN InboundEmailProcessingQueue.message_id IS 'Email Message-ID header, used for potential deduplication if available and reliable.';
COMMENT ON COLUMN InboundEmailProcessingQueue.target_entity_type IS 'Type of CommandIT entity the inbound email address maps to (ServiceBoard, Project, Device, MonitoredDomain etc.). Determined on receipt.';
COMMENT ON COLUMN InboundEmailProcessingQueue.target_entity_id IS 'The Primary Key (UUID, BIGINT etc., stored as string) of the specific entity the inbound email address maps to.';
COMMENT ON COLUMN InboundEmailProcessingQueue.raw_eml_storage_path IS 'Recommended: Path/key to raw .eml file stored temporarily in object storage (e.g., S3/Azure Blob with short TTL). Storing large raw content directly in DB is less ideal.';
COMMENT ON COLUMN InboundEmailProcessingQueue.processing_status IS 'Lifecycle status of the email within the temporary queue.';
COMMENT ON COLUMN InboundEmailProcessingQueue.status_reason IS 'Provides context for the final status before deletion or for identifying processing errors.';
COMMENT ON COLUMN InboundEmailProcessingQueue.linked_processing_log_id IS 'FK to the permanent EmailProcessingLog entry created when this queue item reached a final state.';
COMMENT ON COLUMN InboundEmailProcessingQueue.linked_ticket_update_id IS 'FK to the TicketUpdates record where the processed email content was logged (if applicable).';
COMMENT ON COLUMN InboundEmailProcessingQueue.linked_project_update_id IS 'FK to the ProjectUpdates record where the processed email content was logged (if applicable).';
COMMENT ON COLUMN InboundEmailProcessingQueue.linked_problem_update_id IS 'FK to the ProblemUpdates record where the processed email content was logged (if applicable).';
COMMENT ON COLUMN InboundEmailProcessingQueue.linked_change_update_id IS 'FK to the ChangeRequestUpdates record where the processed email content was logged (if applicable).';
-- Short-term log of email processing outcomes
CREATE TABLE EmailProcessingLog (
log_id BIGSERIAL PRIMARY KEY,
log_timestamp_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
direction VARCHAR(10) NOT NULL CHECK (direction IN ('Inbound', 'Outbound')),
message_id TEXT NULL, -- Email's Message-ID header
from_address TEXT NOT NULL,
to_addresses TEXT[] NULL,
cc_addresses TEXT[] NULL,
subject TEXT NULL,
status VARCHAR(30) NOT NULL CHECK (status IN (
'Processed_NewTicket', 'Processed_UpdatedTicket', 'Processed_ProjectUpdate',
'Blocked_Sender', 'Blocked_Recipient', 'Discarded_Spam', 'Discarded_Rule',
'Sent_Notification', 'Failed_Processing', 'Failed_Sending'
)), -- Added Discarded_Rule, Processed_ProjectUpdate
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
related_project_id UUID NULL REFERENCES Projects(project_id) ON DELETE SET NULL, -- Added project link
related_service_board_id UUID NULL REFERENCES ServiceBoards(board_id) ON DELETE SET NULL,
related_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
related_contact_id UUID NULL REFERENCES Contacts(contact_id) ON DELETE SET NULL,
error_message TEXT NULL,
processing_agent VARCHAR(50) NOT NULL -- e.g., 'AI_Email_Triage', 'CommandIT_Notification_Service'
);
CREATE INDEX idx_emailproc_log_timestamp ON EmailProcessingLog(log_timestamp_utc); -- Essential for 7-day cleanup
CREATE INDEX idx_emailproc_log_status ON EmailProcessingLog(status);
CREATE INDEX idx_emailproc_log_ticket ON EmailProcessingLog(related_ticket_id);
CREATE INDEX idx_emailproc_log_project ON EmailProcessingLog(related_project_id);
CREATE INDEX idx_emailproc_log_message_id ON EmailProcessingLog(message_id);
COMMENT ON TABLE EmailProcessingLog IS 'Short-term (7-day rolling) log of inbound/outbound email processing outcomes.';
CREATE TABLE AlertIngestionEndpoints (
endpoint_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Specific Org scope
location_id UUID NULL REFERENCES Locations(location_id) ON DELETE CASCADE, -- Specific Location scope
email_address VARCHAR(255) NOT NULL UNIQUE, -- The {guid}@commandit.net address
description TEXT NULL, -- Purpose of this endpoint
target_board_id UUID NULL REFERENCES ServiceBoards(board_id) ON DELETE SET NULL, -- Optional default board if rule doesn't specify
processing_policy_id UUID NULL REFERENCES AlertProcessingPolicies(policy_id) ON DELETE SET NULL, -- Default policy to apply
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
-- Ensure scope is defined correctly (Org OR Location, but not both, allows neither for global?)
CONSTRAINT chk_alert_endpoint_scope CHECK ( (org_id IS NOT NULL AND location_id IS NULL) OR (location_id IS NOT NULL AND org_id IS NOT NULL) OR (org_id IS NULL AND location_id IS NULL) ) -- Refined: Allow global (no scope), Org only, or Location (implies Org)
);
COMMENT ON TABLE AlertIngestionEndpoints IS 'Stores dedicated email addresses for receiving alerts, scoped globally, to Orgs, or Locations.';
CREATE TABLE AlertProcessingRules (
rule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP or Platform (NULL) defining the rule base
name VARCHAR(255) NOT NULL,
description TEXT,
condition_logic JSONB NOT NULL, -- Structured conditions for matching alerts/emails
alert_signature_definition JSONB NULL, -- Defines how to build the unique signature for state tracking
clear_condition_logic JSONB NULL, -- Defines conditions for a clearing alert/email
is_system_defined BOOLEAN GENERATED ALWAYS AS (owner_org_id IS NULL) STORED,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE NULLS NOT DISTINCT (owner_org_id, name)
);
COMMENT ON TABLE AlertProcessingRules IS 'Defines conditions for matching incoming alerts/emails and how to uniquely identify alert conditions.';
COMMENT ON COLUMN AlertProcessingRules.condition_logic IS 'JSONB defining conditions to match an alert. Structure: { "match_operator": "AND|OR", "criteria": [ {"field": "...", "operator": "...", "value": "..."} ] }.';
COMMENT ON COLUMN AlertProcessingRules.alert_signature_definition IS 'JSONB defining how to construct a unique alert signature, e.g., ["field:device_id", "field:check_name"] or {"regex_capture": "Subject: Alert for (.*?) on (.*?)"}.';
COMMENT ON COLUMN AlertProcessingRules.clear_condition_logic IS 'Optional JSONB (same structure as condition_logic) defining conditions that signify this alert has cleared.';
-- Groups rules into policies, defines action & inheritance
CREATE TABLE AlertProcessingPolicies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- MSP or Platform (NULL) defining policy
name VARCHAR(255) NOT NULL,
description TEXT,
-- Scope where this *definition* applies (Overrides based on assignment)
scope_type VARCHAR(20) NOT NULL CHECK (scope_type IN ('Global', 'Organization', 'Location')),
scope_id UUID NULL, -- Link to OrgID/LocationID if scoped definition
parent_policy_id UUID NULL REFERENCES AlertProcessingPolicies(policy_id) ON DELETE SET NULL, -- For inheritance chain
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE NULLS NOT DISTINCT (owner_org_id, name, scope_type, scope_id)
);
COMMENT ON TABLE AlertProcessingPolicies IS 'Groups AlertProcessingRules and defines action, scope, and inheritance for email/alert triage.';
-- Add FK constraint from AlertIngestionEndpoints to AlertProcessingPolicies
ALTER TABLE AlertIngestionEndpoints ADD CONSTRAINT fk_alertendpoints_policy FOREIGN KEY (processing_policy_id) REFERENCES AlertProcessingPolicies(policy_id) ON DELETE SET NULL;
-- Add direct links on Org/Location for policy assignment (can also use Tags)
ALTER TABLE Organizations ADD COLUMN alert_processing_policy_id UUID NULL REFERENCES AlertProcessingPolicies(policy_id) ON DELETE SET NULL;
ALTER TABLE Locations ADD COLUMN alert_processing_policy_id UUID NULL REFERENCES AlertProcessingPolicies(policy_id) ON DELETE SET NULL;
CREATE TABLE AlertProcessingPolicyRules (
policy_rule_id BIGSERIAL PRIMARY KEY,
policy_id UUID NOT NULL REFERENCES AlertProcessingPolicies(policy_id) ON DELETE CASCADE,
rule_id UUID NOT NULL REFERENCES AlertProcessingRules(rule_id) ON DELETE CASCADE,
priority INTEGER NOT NULL DEFAULT 0, -- Order of rule evaluation (lower runs first)
action VARCHAR(30) NOT NULL CHECK (action IN (
'CreateTicket',
'Ignore',
'RouteToBoard',
'UpdateExistingCI',
'RunScript',
'SendNotification',
'CallWebhook'
)), -- Action if rule condition matches
action_parameters JSONB NULL, -- Parameters for the action, including thresholds
is_active BOOLEAN NOT NULL DEFAULT true, -- Enable/disable this specific rule within the policy
UNIQUE(policy_id, rule_id),
UNIQUE(policy_id, priority)
);
COMMENT ON TABLE AlertProcessingPolicyRules IS 'Links rules to policies, defining the action, parameters, thresholds, and priority for matching rules.';
COMMENT ON COLUMN AlertProcessingPolicyRules.action IS 'The action to take if the associated rule conditions are met and thresholds passed.';
COMMENT ON COLUMN AlertProcessingPolicyRules.action_parameters IS $$JSONB storing parameters for the specified action AND optional thresholds.
Common Thresholds (Optional):
"trigger_after_occurrences": (Integer, Default: 1) - Trigger action after X occurrences.
"trigger_occurrence_window_seconds": (Integer, Default: 0) - Time window for occurrence count.
"trigger_after_duration_seconds": (Integer, Default: 0) - Trigger if alert active for Z seconds.
Action 'CreateTicket':
"ticket_template_id": (String: UUID, Required)
"priority_id": (Integer, Optional), "status_id": (String: UUID, Optional), "assigned_user_id": (String: UUID, Optional), "target_board_id": (String: UUID, Optional)
Action 'RouteToBoard':
"target_board_id": (String: UUID, Required)
Action 'UpdateExistingCI':
"ci_identifier_field": (String, Required), "ci_identifier_regex": (String, Optional), "ci_type": (String, Required), "max_ticket_age_days": (Integer, Optional, Default: 30), "update_status_to": (String: UUID, Optional), "add_internal_note": (Boolean, Optional, Default: true)
Action 'RunScript':
"script_id": (String: UUID, Required)
"script_parameters": (JSONB, Optional)
"target_device_context": (String, Optional, Default: 'AlertDevice') - 'AlertDevice', 'SpecificDevice', 'ProbeDevice'
"specific_device_id": (String: UUID, Optional) - Required if target is 'SpecificDevice'
Action 'SendNotification':
"notification_profile_id": (String: UUID, Required)
"notification_template_id_override": (String: UUID, Optional)
Action 'CallWebhook':
"webhook_url": (String, Required)
"webhook_method": (String, Optional, Default: 'POST')
"webhook_payload_template": (JSONB, Optional)
"webhook_headers": (JSONB, Optional)
$$;
CREATE TABLE CiEmailMappings (
email_address VARCHAR(255) PRIMARY KEY, -- The unique {guid}@commandit.net address
ci_type VARCHAR(50) NOT NULL, -- Type of the linked CI (e.g., 'Device', 'MonitoredDomain', 'Application', 'TelecomCircuit')
ci_id VARCHAR(50) NOT NULL, -- The primary key of the CI record (UUID/BIGINT stored as VARCHAR)
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context for the CI
generated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
generated_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL
-- Optional: Add is_active flag if needed
);
-- Index for quick lookup based on CI
CREATE INDEX idx_ciemailmappings_ci ON CiEmailMappings(ci_type, ci_id);
-- Index for quick lookup based on Org
CREATE INDEX idx_ciemailmappings_org ON CiEmailMappings(org_id);
COMMENT ON TABLE CiEmailMappings IS 'Maps dedicated inbound email addresses to specific Configuration Items (Devices, Domains, Apps, etc.).';
COMMENT ON COLUMN CiEmailMappings.email_address IS 'The unique {guid}@commandit.net address generated for the CI.';
COMMENT ON COLUMN CiEmailMappings.ci_type IS 'Indicates the type of CommandIT entity this email maps to (matches table names or defined types).';
COMMENT ON COLUMN CiEmailMappings.ci_id IS 'The Primary Key (UUID, BIGINT etc., stored as string) of the specific CI record.';
CREATE TABLE FileSystemPermissions (
fs_permission_id BIGSERIAL PRIMARY KEY,
target_entity_type VARCHAR(50) NOT NULL CHECK (target_entity_type IN ('DeviceLogicalDisk', 'NetworkShare')), -- e.g., Volume C: on Device X, or Share Y
target_entity_id VARCHAR(50) NOT NULL, -- UUID of DeviceLogicalDisk or NetworkShare
path TEXT NOT NULL, -- Specific file or folder path within the target_entity
account_sid VARCHAR(100) NULL, -- Security Identifier (SID) of the principal (User/Group)
account_name VARCHAR(255) NOT NULL, -- Name of the principal
access_type VARCHAR(10) NOT NULL CHECK (access_type IN ('Allow', 'Deny')),
permissions TEXT[] NOT NULL, -- Array of granular permissions (e.g., 'ReadData', 'WriteData', 'AppendData', 'ReadExtendedAttributes', 'WriteExtendedAttributes', 'ExecuteFile', 'DeleteSubdirectoriesAndFiles', 'ReadAttributes', 'WriteAttributes', 'Delete', 'ReadPermissions', 'ChangePermissions', 'TakeOwnership', 'Synchronize', 'FullControl')
inheritance_flags TEXT[] NULL, -- e.g., ['ObjectInherit', 'ContainerInherit', 'NoPropagateInherit', 'InheritOnly']
is_inherited BOOLEAN NOT NULL DEFAULT false,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
scan_source_probe_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL,
UNIQUE (target_entity_type, target_entity_id, path, account_sid, access_type) -- Approximate uniqueness
);
COMMENT ON TABLE FileSystemPermissions IS 'Stores detailed File System Access Control List (ACL) permissions for files/folders on devices or shares.';
COMMENT ON COLUMN FileSystemPermissions.target_entity_type IS 'Type of entity the path belongs to (e.g., a specific logical disk on a device, or a network share).';
COMMENT ON COLUMN FileSystemPermissions.target_entity_id IS 'Identifier (UUID/PK) of the logical disk or network share.';
COMMENT ON COLUMN FileSystemPermissions.path IS 'The specific file or folder path relative to the target entity.';
COMMENT ON COLUMN FileSystemPermissions.account_sid IS 'SID of the user or group the permission applies to.';
COMMENT ON COLUMN FileSystemPermissions.permissions IS 'Array of granular file system permissions granted or denied.';
COMMENT ON COLUMN FileSystemPermissions.inheritance_flags IS 'Flags indicating how this permission is inherited by child objects/containers.';
COMMENT ON COLUMN FileSystemPermissions.is_inherited IS 'Indicates if this specific ACE was inherited from a parent object.';
CREATE TABLE DevicePatchStatus (
device_patch_status_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
patch_definition_id UUID NOT NULL REFERENCES PatchDefinitions(patch_definition_id) ON DELETE CASCADE,
status VARCHAR(30) NOT NULL CHECK (status IN ('Needed', 'Installed', 'ApprovedForInstall', 'PendingReboot', 'Failed', 'Superseded', 'NotApplicable', 'Unknown')),
installed_on TIMESTAMPTZ NULL, -- When the patch was successfully installed
last_detected_needed_on TIMESTAMPTZ NULL, -- When the agent last saw this patch as needed
last_attempted_install_on TIMESTAMPTZ NULL, -- Timestamp of last install attempt (success or fail)
last_scan_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this status was last determined by a scan
source_scan_id VARCHAR(100) NULL, -- Identifier from the specific scan that reported this status
notes TEXT NULL,
UNIQUE (device_id, patch_definition_id)
);
COMMENT ON TABLE DevicePatchStatus IS 'Tracks the installation status of specific patches on individual devices.';
COMMENT ON COLUMN DevicePatchStatus.status IS 'Current status of the patch on the device (Needed, Installed, Failed, etc.).';
CREATE TABLE DeviceScheduledTasks (
device_task_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
task_name TEXT NOT NULL, -- Full path/name of the task
status VARCHAR(30) CHECK (status IN ('Ready', 'Running', 'Disabled', 'Queued', 'Unknown')),
is_enabled BOOLEAN,
last_run_time TIMESTAMPTZ NULL,
last_run_result INTEGER NULL, -- Exit code of the last run
next_run_time TIMESTAMPTZ NULL,
actions JSONB NULL, -- Details of actions (e.g., executable path, arguments)
triggers JSONB NULL, -- Details of triggers (e.g., time, logon, event)
run_as_user VARCHAR(255) NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (device_id, task_name)
);
COMMENT ON TABLE DeviceScheduledTasks IS 'Stores information about scheduled tasks discovered on devices.';
COMMENT ON COLUMN DeviceScheduledTasks.task_name IS 'The full name or path identifying the scheduled task.';
COMMENT ON COLUMN DeviceScheduledTasks.last_run_result IS 'The exit code returned by the last execution of the task.';
CREATE TABLE DnsRecords (
dns_record_id BIGSERIAL PRIMARY KEY,
source_domain_id UUID NULL REFERENCES AdDomains(ad_domain_id) ON DELETE SET NULL, -- Link if discovered via AD DNS
source_monitor_id UUID NULL REFERENCES MonitoredDomains(domain_id) ON DELETE SET NULL, -- Link if discovered via external monitor
zone_name VARCHAR(255) NOT NULL, -- The DNS zone the record belongs to (e.g., 'ahsn.local', 'example.com')
record_name VARCHAR(255) NOT NULL, -- The record name (e.g., '@', 'www', 'mail')
record_type VARCHAR(10) NOT NULL CHECK (record_type IN ('A', 'AAAA', 'CNAME', 'MX', 'TXT', 'SRV', 'NS', 'PTR', 'SOA', 'CAA', 'Other')),
record_value TEXT NOT NULL, -- The value/target of the record
ttl INTEGER NULL, -- Time To Live in seconds
mx_preference INTEGER NULL, -- MX record preference (only applicable if record_type='MX')
srv_priority INTEGER NULL, -- SRV record priority
srv_weight INTEGER NULL, -- SRV record weight
srv_port INTEGER NULL, -- SRV record port
srv_target VARCHAR(255) NULL, -- SRV record target hostname
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
source_description VARCHAR(100), -- How was this record found ('AD_DNS', 'External_Query', 'Manual')
UNIQUE (zone_name, record_name, record_type, record_value, mx_preference, srv_priority, srv_weight, srv_port) -- Attempt at uniqueness
);
COMMENT ON TABLE DnsRecords IS 'Stores details about DNS records discovered within specific zones, either internal (AD) or external.';
CREATE INDEX idx_dnsrecords_zone_name ON DnsRecords(zone_name);
CREATE INDEX idx_dnsrecords_record_name ON DnsRecords(record_name);
CREATE INDEX idx_dnsrecords_record_type ON DnsRecords(record_type);
CREATE TABLE InternetSpeedTestResults (
speed_test_result_id BIGSERIAL PRIMARY KEY,
source_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Device that ran the test (e.g., Probe)
source_location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Location context if not device-specific
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context
test_timestamp_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
download_mbps NUMERIC(10, 2) NULL,
upload_mbps NUMERIC(10, 2) NULL,
latency_ms INTEGER NULL,
jitter_ms INTEGER NULL,
packet_loss_percent NUMERIC(5, 2) NULL,
test_server_details JSONB NULL, -- { "name": "...", "location": "...", "ip": "...", "provider": "..." }
test_provider VARCHAR(100) NULL -- e.g., 'Speedtest.net', 'Fast.com', 'InternalTool'
);
COMMENT ON TABLE InternetSpeedTestResults IS 'Stores results from internet speed tests.';
CREATE TABLE WebContentFilteringResults (
web_filter_result_id BIGSERIAL PRIMARY KEY,
source_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Device where Browse occurred
source_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL, -- User Browse
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context
integration_instance_id UUID NULL REFERENCES IntegrationInstances(instance_id) ON DELETE SET NULL, -- Link to Content Filter integration
event_timestamp_utc TIMESTAMPTZ NOT NULL,
requested_url TEXT NOT NULL,
resolved_ip INET NULL,
action_taken VARCHAR(20) NOT NULL CHECK (action_taken IN ('Allowed', 'Blocked', 'Warned', 'Overridden')),
reason VARCHAR(255) NULL, -- Reason for block/warn (e.g., 'PolicyViolation', 'ThreatDetected')
category_name VARCHAR(100) NULL, -- Category identified by the filter (e.g., 'SocialMedia', 'Malware', 'Phishing')
threat_name VARCHAR(100) NULL, -- Specific threat if identified
source_ip_address INET NULL, -- IP of the Browse device at the time
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE WebContentFilteringResults IS 'Stores logs from web content filtering systems.';
CREATE TABLE LocalSecurityPolicySettings (
local_policy_setting_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
policy_category VARCHAR(100) NOT NULL, -- e.g., 'PasswordPolicy', 'AccountLockoutPolicy', 'AuditPolicy', 'UserRightsAssignment', 'SecurityOptions'
policy_name VARCHAR(255) NOT NULL, -- Specific policy name (e.g., 'MinimumPasswordLength', 'AuditAccountLogonEvents', 'AccessThisComputerFromNetwork')
setting_value TEXT NULL, -- The actual setting value detected on the machine
setting_value_type VARCHAR(20) DEFAULT 'String', -- Helps interpret setting_value (e.g., 'String', 'Integer', 'Boolean', 'StringArray')
source VARCHAR(100) NULL, -- How setting was derived ('LocalPolicy', 'EffectiveGPO', 'Registry')
source_gpo_name VARCHAR(255) NULL, -- Name of GPO if source is 'EffectiveGPO'
last_assessment_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (device_id, policy_category, policy_name)
);
COMMENT ON TABLE LocalSecurityPolicySettings IS 'Stores detailed local security policy settings detected on individual devices.';
COMMENT ON COLUMN LocalSecurityPolicySettings.policy_category IS 'High-level category of the security policy (Password, Audit, etc.).';
COMMENT ON COLUMN LocalSecurityPolicySettings.policy_name IS 'The specific name of the policy setting.';
COMMENT ON COLUMN LocalSecurityPolicySettings.setting_value IS 'The value of the policy setting as detected on the device.';
CREATE TABLE DeviceComplianceResults (
compliance_result_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
compliance_rule_id UUID NOT NULL REFERENCES ComplianceRules(rule_id) ON DELETE CASCADE,
check_timestamp_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
result VARCHAR(20) NOT NULL CHECK (result IN ('Compliant', 'NonCompliant', 'Error', 'NotApplicable')),
result_details TEXT NULL, -- Specific value found or error message
remediation_status VARCHAR(20) NULL CHECK (remediation_status IN ('Pending', 'Running', 'Success', 'Failed', 'Skipped', 'NotAttempted')), -- Status of automated remediation attempt if triggered
remediation_attempted_at TIMESTAMPTZ NULL, -- Timestamp when remediation was last attempted
remediation_log TEXT NULL, -- Optional: Log output captured from the remediation script/command
remediation_command_id BIGINT NULL REFERENCES AgentCommandQueue(command_queue_id) ON DELETE SET NULL, -- Link to the specific command queue entry that performed the remediation
related_alert_id BIGINT NULL REFERENCES Alerts(alert_id) ON DELETE SET NULL,
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the result record was first created
updated_at TIMESTAMPTZ -- Auto-updated when status/remediation fields change
);
-- Comments
COMMENT ON TABLE DeviceComplianceResults IS 'Stores the results of compliance rule checks against specific devices, including status of any automated remediation attempts.';
COMMENT ON COLUMN DeviceComplianceResults.result IS 'The outcome of the compliance check (Compliant, NonCompliant, Error, NotApplicable).';
COMMENT ON COLUMN DeviceComplianceResults.result_details IS 'Specific value found during the check or error message if the check failed.';
COMMENT ON COLUMN DeviceComplianceResults.remediation_status IS 'Tracks the status of automated remediation efforts linked to this compliance finding.';
COMMENT ON COLUMN DeviceComplianceResults.remediation_attempted_at IS 'Timestamp of the last automated remediation attempt for this finding.';
COMMENT ON COLUMN DeviceComplianceResults.remediation_log IS 'Optional log output captured from the executed remediation script or command.';
COMMENT ON COLUMN DeviceComplianceResults.remediation_command_id IS 'Links to the specific command executed via AgentCommandQueue for remediation.';
-- Indexes
CREATE INDEX idx_devicecomplianceresults_device_rule ON DeviceComplianceResults(device_id, compliance_rule_id);
CREATE INDEX idx_devicecomplianceresults_result_time ON DeviceComplianceResults(result, check_timestamp_utc);
CREATE INDEX idx_devicecomplianceresults_remediation_status ON DeviceComplianceResults(remediation_status);
CREATE TABLE DevicePerformanceSnapshots (
snapshot_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
timestamp_utc TIMESTAMPTZ NOT NULL,
cpu_usage_percent NUMERIC(5, 2) NULL,
ram_usage_percent NUMERIC(5, 2) NULL,
ram_used_mb INTEGER NULL,
-- Add other key metrics as needed (disk IO, network throughput per NIC, etc.)
CONSTRAINT device_perf_snap_device_time_unique UNIQUE (device_id, timestamp_utc)
);
COMMENT ON TABLE DevicePerformanceSnapshots IS 'Stores periodic snapshots of key device performance metrics. Note: Can grow very large; consider time-series DB for high frequency.';
CREATE TABLE NetworkPortMetrics (
port_metric_id BIGSERIAL PRIMARY KEY,
port_id UUID NOT NULL REFERENCES NetworkDevicePorts(port_id) ON DELETE CASCADE,
timestamp_utc TIMESTAMPTZ NOT NULL,
bytes_in BIGINT NULL,
bytes_out BIGINT NULL,
packets_in BIGINT NULL,
packets_out BIGINT NULL,
errors_in BIGINT NULL,
errors_out BIGINT NULL,
discards_in BIGINT NULL,
discards_out BIGINT NULL,
-- Add other relevant metrics (queue length, broadcast packets, etc.)
CONSTRAINT net_port_metric_port_time_unique UNIQUE (port_id, timestamp_utc)
);
COMMENT ON TABLE NetworkPortMetrics IS 'Stores historical network interface metrics for ports on network devices. Note: Can grow very large; consider time-series DB for high frequency.';
CREATE TABLE CiStatusHistory (
status_history_id BIGSERIAL PRIMARY KEY,
ci_type VARCHAR(50) NOT NULL, -- e.g., 'Device', 'SqlServerInstance', 'MonitoredWebsite', 'VpnTunnel'
ci_id VARCHAR(50) NOT NULL, -- UUID or other PK stored as string
timestamp_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
old_status VARCHAR(50) NULL,
new_status VARCHAR(50) NOT NULL,
change_reason TEXT NULL, -- e.g., 'Monitoring Check', 'User Action', 'System Event'
changed_by_ref VARCHAR(100) NULL -- e.g., 'User:uuid', 'Rule:uuid', 'Agent'
);
COMMENT ON TABLE CiStatusHistory IS 'Generic table to track status changes for various configuration items over time.';
CREATE INDEX idx_cistatushistory_ci ON CiStatusHistory(ci_type, ci_id, timestamp_utc);
CREATE TABLE FirewallPolicies (
firewall_policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
managing_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- The firewall device this policy resides on
policy_name VARCHAR(255) NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT true,
last_sync_time TIMESTAMPTZ,
UNIQUE (managing_device_id, policy_name)
);
COMMENT ON TABLE FirewallPolicies IS 'Represents named firewall policies or rule sets on a firewall device.';
CREATE TABLE FirewallRules (
firewall_rule_id BIGSERIAL PRIMARY KEY,
firewall_policy_id UUID NULL REFERENCES FirewallPolicies(firewall_policy_id) ON DELETE CASCADE, -- Optional link to a policy group
managing_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Firewall device
rule_name VARCHAR(255) NULL, -- Name/Identifier of the rule
sequence_order INTEGER NOT NULL DEFAULT 0,
source_zone VARCHAR(100),
source_addresses TEXT[], -- IPs, CIDRs, Address Objects/Groups
destination_zone VARCHAR(100),
destination_addresses TEXT[], -- IPs, CIDRs, Address Objects/Groups
services TEXT[], -- Protocol/Port, Service Objects/Groups (e.g., 'TCP/443', 'HTTP', 'DNS')
action VARCHAR(10) NOT NULL CHECK (action IN ('Allow', 'Deny', 'Reject')),
is_enabled BOOLEAN DEFAULT true,
logging_enabled BOOLEAN DEFAULT false,
description TEXT,
last_sync_time TIMESTAMPTZ,
UNIQUE (managing_device_id, rule_name, sequence_order) -- Approximate uniqueness
);
COMMENT ON TABLE FirewallRules IS 'Stores individual firewall rules discovered or configured on firewall devices.';
CREATE TABLE EndpointProtectionStatus (
ep_status_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
product_name VARCHAR(255) NOT NULL, -- e.g., 'Microsoft Defender', 'SentinelOne Agent'
product_version VARCHAR(50) NULL,
engine_version VARCHAR(50) NULL,
definition_version VARCHAR(100) NULL,
last_definition_update TIMESTAMPTZ NULL,
realtime_protection_status VARCHAR(30) NULL CHECK (realtime_protection_status IN ('Enabled', 'Disabled', 'Snoozed', 'Expired', 'Error', 'NotReporting', 'Unknown')), -- Adjusted statuses
firewall_status VARCHAR(30) NULL CHECK (firewall_status IN ('Enabled', 'Disabled', 'Error', 'NotReporting', 'Unknown', 'NotApplicable')), -- Adjusted statuses
last_full_scan_time TIMESTAMPTZ NULL,
last_quick_scan_time TIMESTAMPTZ NULL,
detected_threats_count INTEGER DEFAULT 0, -- Based on recent threat events, maybe reset periodically
configuration_details JSONB NULL, -- Added field for detailed Defender config (populated by agent)
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (device_id, product_name)
);
COMMENT ON TABLE EndpointProtectionStatus IS 'Stores status information about endpoint security products (AV/EPP) detected via OS mechanisms or specific integrations.';
COMMENT ON COLUMN EndpointProtectionStatus.realtime_protection_status IS 'Operational status of real-time protection component (derived from WMI productState or vendor tools).';
COMMENT ON COLUMN EndpointProtectionStatus.configuration_details IS 'JSONB field storing detailed configuration settings (currently populated primarily for Microsoft Defender via Get-MpPreference).';
CREATE INDEX idx_endpointprotectionstatus_device_id ON EndpointProtectionStatus(device_id);
CREATE TABLE FileIntegrityBaselines (
fim_baseline_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
policy_id UUID NOT NULL, -- Link to a FIM policy or Compliance Policy/Rule
target_entity_type VARCHAR(50) NOT NULL, -- e.g., 'Device', 'Tag'
target_entity_id VARCHAR(50) NOT NULL,
file_or_directory_path TEXT NOT NULL,
expected_hash_sha256 VARCHAR(64) NULL, -- For file baselining
monitor_attributes BOOLEAN DEFAULT true, -- Monitor changes to attributes (permissions, owner, timestamps)
monitor_content BOOLEAN DEFAULT true, -- Monitor changes to content (hash)
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE FileIntegrityBaselines IS 'Defines baseline paths and expected states for File Integrity Monitoring.';
CREATE TABLE FileIntegrityAlerts (
fim_alert_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
fim_baseline_id UUID NULL REFERENCES FileIntegrityBaselines(fim_baseline_id) ON DELETE SET NULL, -- Link to the baseline violated
alert_timestamp_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
file_path TEXT NOT NULL,
change_type VARCHAR(30) NOT NULL CHECK (change_type IN ('ContentModified', 'AttributesModified', 'FileCreated', 'FileDeleted', 'FileRenamed')),
old_value TEXT NULL, -- e.g., old hash, old attributes
new_value TEXT NULL, -- e.g., new hash, new attributes
process_path TEXT NULL, -- Process that made the change (if available)
user_name VARCHAR(255) NULL, -- User context of the change (if available)
status VARCHAR(20) DEFAULT 'New' CHECK (status IN ('New', 'Acknowledged', 'Investigating', 'Resolved', 'FalsePositive')),
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL
);
COMMENT ON TABLE FileIntegrityAlerts IS 'Logs detected violations against File Integrity Monitoring baselines.';
CREATE TABLE ApplicationDependency (
dependency_id BIGSERIAL PRIMARY KEY,
parent_app_id UUID NOT NULL REFERENCES Applications(application_id) ON DELETE CASCADE, -- The application that has the dependency
child_ci_type VARCHAR(50) NOT NULL, -- Type of CI the parent depends on (e.g., 'Application', 'SqlServerInstance', 'Device', 'NetworkShare')
child_ci_id VARCHAR(50) NOT NULL, -- ID of the dependent CI
dependency_type VARCHAR(50) DEFAULT 'RunsOn', -- e.g., 'RunsOn', 'UsesDatabase', 'ConsumesAPI', 'RequiresService'
criticality VARCHAR(20) NULL CHECK (criticality IN ('VeryHigh', 'High', 'Medium', 'Low')), -- How critical is this dependency for the parent app?
notes TEXT,
discovered_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
discovered_by VARCHAR(50), -- 'Manual', 'Scan', 'Import'
UNIQUE (parent_app_id, child_ci_type, child_ci_id, dependency_type)
);
COMMENT ON TABLE ApplicationDependency IS 'Maps dependencies between logical Applications and other Configuration Items.';
CREATE TABLE Racks (
rack_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE, -- Where the rack physically is
name VARCHAR(100) NOT NULL, -- User-defined name (e.g., 'Rack A-01', 'Server Rack 3')
u_height INTEGER NOT NULL CHECK (u_height > 0), -- Total usable U height
manufacturer_id UUID NULL REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL,
model VARCHAR(100) NULL,
serial_number VARCHAR(100) NULL,
asset_tag VARCHAR(100) NULL,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE(location_id, name)
);
COMMENT ON TABLE Racks IS 'Represents physical equipment racks within locations.';
CREATE TABLE ElectricalCircuits (
circuit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE, -- Location of the panel
panel_name VARCHAR(100) NOT NULL, -- Identifier for the electrical panel
circuit_number_panel VARCHAR(50) NOT NULL, -- Breaker number/identifier within the panel
breaker_rating_amps INTEGER NOT NULL,
voltage INTEGER NOT NULL, -- e.g., 120, 208, 240
phase VARCHAR(10) CHECK (phase IN ('Single', 'Three')),
description TEXT NULL, -- e.g., 'UPS Input A Feed', 'Rack 3 Bottom PDU'
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (location_id, panel_name, circuit_number_panel)
);
COMMENT ON TABLE ElectricalCircuits IS 'Represents individual electrical circuits originating from panels.';
CREATE TABLE PowerOutlets (
outlet_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
location_id UUID NOT NULL REFERENCES Locations(location_id) ON DELETE CASCADE, -- Location of the outlet/PDU/UPS
outlet_type VARCHAR(10) NOT NULL CHECK (outlet_type IN ('Wall', 'PDU', 'UPS')), -- Type of outlet
source_device_id UUID NULL REFERENCES Devices(device_id) ON DELETE SET NULL, -- Link to the PDU or UPS Device this outlet is ON. NULL if Wall.
circuit_id UUID NULL REFERENCES ElectricalCircuits(circuit_id) ON DELETE SET NULL, -- Circuit feeding this outlet (directly for Wall, or feeding the PDU/UPS)
outlet_label VARCHAR(100) NOT NULL, -- User-defined or PDU/UPS label (e.g., 'Wall-101-A', 'PDU-A:C5', 'UPS-B:Out3')
plug_type_receptacle VARCHAR(50) NOT NULL, -- Type of receptacle (e.g., 'NEMA 5-15R', 'NEMA L6-30R', 'IEC C13')
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (location_id, outlet_label), -- Label should be unique within a location
UNIQUE (source_device_id, outlet_label) WHERE source_device_id IS NOT NULL -- Label should be unique on a specific PDU/UPS
);
COMMENT ON TABLE PowerOutlets IS 'Represents physical power outlets (Wall, PDU, UPS), linking them to locations, circuits, and source devices (PDU/UPS).';
COMMENT ON COLUMN PowerOutlets.outlet_type IS 'Specifies if this is a Wall outlet, or an outlet on a PDU or UPS.';
COMMENT ON COLUMN PowerOutlets.source_device_id IS 'If outlet_type is PDU or UPS, this links to the specific Device record for that PDU/UPS.';
COMMENT ON COLUMN PowerOutlets.circuit_id IS 'The building electrical circuit ultimately feeding this outlet or the PDU/UPS it resides on.';
COMMENT ON COLUMN PowerOutlets.outlet_label IS 'The physical or logical label identifying this specific outlet.';
COMMENT ON COLUMN PowerOutlets.plug_type_receptacle IS 'The type of receptacle/socket (e.g., NEMA 5-15R).';
CREATE INDEX idx_poweroutlets_location ON PowerOutlets(location_id);
CREATE INDEX idx_poweroutlets_circuit ON PowerOutlets(circuit_id);
CREATE INDEX idx_poweroutlets_source_device ON PowerOutlets(source_device_id);
CREATE TABLE PowerOutletAttachments (
outlet_id UUID NOT NULL REFERENCES PowerOutlets(outlet_id) ON DELETE CASCADE,
attachment_id UUID NOT NULL REFERENCES Attachments(attachment_id) ON DELETE CASCADE,
description TEXT NULL, -- Optional: Describe the picture (e.g., 'Close-up of NEMA L6-30R receptacle', 'Photo of label')
is_primary_image BOOLEAN NOT NULL DEFAULT false, -- Optional: Flag one image as the primary one for this outlet
attached_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (outlet_id, attachment_id)
);
COMMENT ON TABLE PowerOutletAttachments IS 'Links attachments (like pictures) stored in the Attachments table to specific PowerOutlets records.';
CREATE TABLE DevicePowerSupplies (
power_supply_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
psu_label VARCHAR(50) NOT NULL, -- User-friendly label within the device (e.g., 'PSU 1', 'Power Supply A')
input_label VARCHAR(50) NULL, -- Label for the specific input port on this PSU (e.g., 'Input 1', 'AC Input')
manufacturer_id UUID NULL REFERENCES Manufacturers(manufacturer_id) ON DELETE SET NULL,
model VARCHAR(100) NULL,
serial_number VARCHAR(100) NULL,
part_number VARCHAR(100) NULL,
wattage_rating INTEGER NULL, -- Rated wattage capacity
input_type VARCHAR(10) NULL CHECK (input_type IN ('AC', 'DC')), -- Type of power input
input_voltage_range VARCHAR(50) NULL, -- e.g., '100-240V'
input_plug_type VARCHAR(50) NULL, -- Type of plug on the PSU's cord (e.g., 'IEC C14', 'NEMA 5-15P')
status VARCHAR(30) NULL CHECK (status IN ('OK', 'Failed', 'Warning', 'NotPresent', 'Unknown')), -- Operational status
last_status_check TIMESTAMPTZ NULL,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
UNIQUE (device_id, psu_label, input_label) -- Unique input per PSU per device
);
COMMENT ON TABLE DevicePowerSupplies IS 'Tracks individual power supply units (PSUs) within devices and their inputs.';
COMMENT ON COLUMN DevicePowerSupplies.psu_label IS 'Identifier for the PSU within the device (e.g., PSU 1, PSU 2).';
COMMENT ON COLUMN DevicePowerSupplies.input_label IS 'Identifier for the specific power input port on this PSU, if it has multiple (e.g., Input 1, Input A).';
COMMENT ON COLUMN DevicePowerSupplies.status IS 'Operational status reported by the system (via SNMP, Agent, etc.).';
CREATE TABLE PowerConnections (
power_connection_id BIGSERIAL PRIMARY KEY,
power_source_outlet_id UUID NOT NULL REFERENCES PowerOutlets(outlet_id) ON DELETE CASCADE, -- The outlet providing power
powered_device_psu_id BIGINT NOT NULL REFERENCES DevicePowerSupplies(power_supply_id) ON DELETE CASCADE, -- The specific PSU input receiving power
connection_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this connection was documented/made
notes TEXT,
UNIQUE (power_source_outlet_id), -- An outlet should only feed one input directly
UNIQUE (powered_device_psu_id) -- A PSU input should only receive power from one outlet directly
);
COMMENT ON TABLE PowerConnections IS 'Maps power connections from a source outlet (Wall, PDU, UPS) to a specific device power supply input.';
COMMENT ON COLUMN PowerConnections.power_source_outlet_id IS 'FK to the PowerOutlets record providing the power.';
COMMENT ON COLUMN PowerConnections.powered_device_psu_id IS 'FK to the specific DevicePowerSupplies record (representing a PSU input port) receiving the power.';
-- Needed for: Data Breach Liability Summary Report
CREATE TABLE PiiFindings (
pii_finding_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Device where PII was found
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org context
location_path TEXT NOT NULL, -- Full path to the file or data store containing PII
pii_type_detected VARCHAR(100) NOT NULL, -- Type of PII found (e.g., 'CreditCardNumber', 'SSN', 'SIN', 'DriversLicense_BC', 'PassportNumber')
match_count INTEGER DEFAULT 1, -- How many instances found in this location
match_context TEXT NULL, -- Optional: A snippet showing the context of the match (use carefully due to sensitivity)
detection_source VARCHAR(100) NULL, -- Tool or method used for detection (e.g., 'AgentScan_DLPPattern', 'ManualReview')
detection_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_seen_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(30) DEFAULT 'New' CHECK (status IN ('New', 'Reviewed', 'Remediated', 'FalsePositive', 'RiskAccepted')),
status_updated_by UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
status_updated_at TIMESTAMPTZ NULL,
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
notes TEXT NULL
);
COMMENT ON TABLE PiiFindings IS 'Stores findings related to the discovery of Personally Identifiable Information (PII) on managed assets.';
COMMENT ON COLUMN PiiFindings.location_path IS 'The full path (e.g., file path, database table/column) where the PII was detected.';
COMMENT ON COLUMN PiiFindings.pii_type_detected IS 'The specific type or category of PII that was found.';
COMMENT ON COLUMN PiiFindings.match_context IS 'An optional, redacted snippet showing the context of the detected PII (use with extreme caution due to data sensitivity).';
CREATE INDEX idx_piifindings_device ON PiiFindings(device_id);
CREATE INDEX idx_piifindings_org_type ON PiiFindings(org_id, pii_type_detected);
CREATE INDEX idx_piifindings_status ON PiiFindings(status);
CREATE TABLE SecurityThreatEvents (
threat_event_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Denormalized from device
detection_timestamp_utc TIMESTAMPTZ NOT NULL,
threat_name VARCHAR(255) NOT NULL,
threat_type VARCHAR(50) NULL, -- e.g., 'Malware', 'PUP', 'Ransomware', 'Exploit'
file_path TEXT NULL, -- Path of the affected file, if applicable
file_hash_sha256 VARCHAR(64) NULL,
process_path TEXT NULL, -- Path of the responsible process, if applicable
source_ip INET NULL, -- Source IP if network-based threat
destination_ip INET NULL, -- Destination IP if network-based threat
action_taken VARCHAR(50) NULL, -- e.g., 'Cleaned', 'Quarantined', 'Deleted', 'Blocked', 'DetectedOnly'
status VARCHAR(20) DEFAULT 'New' CHECK (status IN ('New', 'Investigating', 'Remediated', 'Closed', 'FalsePositive')),
detection_source VARCHAR(100) NULL, -- e.g., 'Sophos Endpoint', 'Windows Defender', 'EDR Agent X'
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE SecurityThreatEvents IS 'Logs specific threat detection events from endpoint security products (AV/EDR).';
CREATE INDEX idx_secthreatevents_time ON SecurityThreatEvents(detection_timestamp_utc);
CREATE INDEX idx_secthreatevents_device ON SecurityThreatEvents(device_id);
CREATE INDEX idx_secthreatevents_org_time ON SecurityThreatEvents(org_id, detection_timestamp_utc);
-- Revised to consolidate External Service and Power/Infrastructure Incidents
CREATE TABLE ExternalServiceIncidents (
ext_incident_id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org reporting/affected by the incident
incident_type VARCHAR(30) NOT NULL CHECK (incident_type IN ('ExternalService', 'PowerOutage', 'InternalInfrastructure', 'Other')), -- Type of incident
location_id UUID NULL REFERENCES Locations(location_id) ON DELETE SET NULL, -- Location affected (primarily for PowerOutage/InternalInfrastructure)
service_name VARCHAR(255) NOT NULL, -- Name of the affected service (e.g., 'M365', 'Salesforce') or description for infrastructure issues (e.g., 'Utility Power Outage - Main Office', 'Building HVAC Failure')
vendor_org_id UUID NULL REFERENCES Organizations(org_id) ON DELETE SET NULL, -- Link to Vendor Org (for ExternalService) or Utility (for PowerOutage) if tracked
status VARCHAR(30) NOT NULL CHECK (status IN ('Investigating', 'Identified', 'Monitoring', 'Resolved', 'FalseAlarm', 'Declared')), -- Added 'Declared' for potential use
reason TEXT NULL, -- Specific reason, especially for Power/Infrastructure (e.g., 'Utility Failure', 'UPS Battery Exhausted', 'Cooling Unit Failure')
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NULL,
duration_seconds INTEGER NULL, -- Can be calculated if end_time_utc is set
impact_description TEXT,
resolution_summary TEXT NULL,
reported_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE ExternalServiceIncidents IS 'Tracks significant incidents/outages related to key third-party services OR internal infrastructure like power failures.';
COMMENT ON COLUMN ExternalServiceIncidents.incident_type IS 'Categorizes the type of incident being logged.';
COMMENT ON COLUMN ExternalServiceIncidents.location_id IS 'The specific location affected, primarily used for PowerOutage or InternalInfrastructure types.';
COMMENT ON COLUMN ExternalServiceIncidents.service_name IS 'Name of the affected external service, or a descriptive name for infrastructure issues (e.g., "Power Outage - Site X").';
COMMENT ON COLUMN ExternalServiceIncidents.vendor_org_id IS 'Link to the Organization record for the external service provider or utility company, if applicable.';
COMMENT ON COLUMN ExternalServiceIncidents.reason IS 'Specific cause or reason, particularly relevant for non-service related incidents.';
-- Add relevant indexes
CREATE INDEX idx_extsvcinc_org_type_time ON ExternalServiceIncidents(org_id, incident_type, start_time_utc);
CREATE INDEX idx_extsvcinc_location ON ExternalServiceIncidents(location_id) WHERE location_id IS NOT NULL;
CREATE INDEX idx_extsvcinc_status ON ExternalServiceIncidents(status);
CREATE TABLE DisasterEvents (
disaster_event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES Organizations(org_id) ON DELETE CASCADE, -- Org affected
event_name VARCHAR(255) NOT NULL, -- e.g., 'Site Alpha Flood Event', 'Datacenter Fire DR'
event_type VARCHAR(100) NULL, -- e.g., 'Natural Disaster', 'Fire', 'Cyber Attack', 'Utility Failure'
declaration_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NULL, -- When normal operations resumed / DR ended
activated_bcdr_plan_id UUID NULL REFERENCES BcdrPlans(plan_id) ON DELETE SET NULL,
impact_summary TEXT,
actions_taken TEXT,
status VARCHAR(30) CHECK (status IN ('Declared', 'ActiveRecovery', 'PostRecovery', 'Resolved')),
declared_by_user_id UUID NULL REFERENCES Users(user_id) ON DELETE SET NULL,
related_ticket_id BIGINT NULL REFERENCES Tickets(ticket_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
COMMENT ON TABLE DisasterEvents IS 'Tracks declared disaster events and the activation of BCDR plans (distinct from planned tests).';
-- Stores information about printers connected locally or mapped from the network
CREATE TABLE DevicePrinters (
device_printer_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, -- Printer queue name
device_id_string VARCHAR(255) NULL, -- DeviceID from Win32_Printer
driver_name VARCHAR(255) NULL,
manufacturer VARCHAR(255) NULL,
model VARCHAR(255) NULL,
port_name VARCHAR(255) NULL,
is_network_printer BOOLEAN,
is_shared BOOLEAN,
server_name VARCHAR(255) NULL, -- Host if network printer
share_name VARCHAR(255) NULL, -- Share name if shared
location TEXT NULL,
is_default BOOLEAN,
status VARCHAR(50) NULL, -- e.g., 'Idle', 'Printing', 'Error'
detected_error_state VARCHAR(100) NULL, -- Raw error state text/code
error_description TEXT NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, name) -- Name should be unique per device
);
COMMENT ON TABLE DevicePrinters IS 'Stores details about local and mapped network printers discovered on a device.';
COMMENT ON COLUMN DevicePrinters.device_id_string IS 'DeviceID property from Win32_Printer.';
COMMENT ON COLUMN DevicePrinters.is_network_printer IS 'Indicates if this is a network-mapped printer (vs. locally connected).';
COMMENT ON COLUMN DevicePrinters.is_shared IS 'Indicates if this local printer is shared on the network.';
CREATE INDEX idx_deviceprinters_device_id ON DevicePrinters(device_id);
-- Stores network drives mapped by users on a device
CREATE TABLE MappedNetworkDrives (
mapped_drive_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
user_sid VARCHAR(100) NOT NULL, -- SID of the user context the drive is mapped under
drive_letter CHAR(2) NOT NULL, -- e.g., 'Z:'
provider_name TEXT NOT NULL, -- UNC Path (e.g., \\Server\Share)
display_name VARCHAR(255) NULL, -- Friendly name if available
file_system VARCHAR(50) NULL, -- e.g., 'NTFS', 'FAT32' (may not always be available)
total_size_bytes BIGINT NULL,
free_space_bytes BIGINT NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, user_sid, drive_letter) -- Unique mapping per user per drive letter on a device
);
COMMENT ON TABLE MappedNetworkDrives IS 'Stores network drives mapped within user sessions on a device.';
COMMENT ON COLUMN MappedNetworkDrives.user_sid IS 'SID of the user for whom this drive mapping exists.';
COMMENT ON COLUMN MappedNetworkDrives.provider_name IS 'The UNC path of the mapped network share.';
CREATE INDEX idx_mappednetworkdrives_device_id ON MappedNetworkDrives(device_id);
CREATE INDEX idx_mappednetworkdrives_user_sid ON MappedNetworkDrives(user_sid);
-- Stores configuration for Windows Firewall profiles (Domain, Private, Public)
CREATE TABLE DeviceFirewallProfiles (
firewall_profile_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
profile_name VARCHAR(20) NOT NULL CHECK (profile_name IN ('Domain', 'Private', 'Public')),
is_enabled BOOLEAN NULL,
default_inbound_action VARCHAR(10) NULL CHECK (default_inbound_action IN ('Allow', 'Block')),
default_outbound_action VARCHAR(10) NULL CHECK (default_outbound_action IN ('Allow', 'Block')),
allow_unicast_response BOOLEAN NULL,
notify_on_listen BOOLEAN NULL, -- Notifications setting
block_all_inbound BOOLEAN NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, profile_name)
);
COMMENT ON TABLE DeviceFirewallProfiles IS 'Stores configuration settings for the different Windows Firewall profiles (Domain, Private, Public) on a device.';
CREATE INDEX idx_devicefirewallprofiles_device_id ON DeviceFirewallProfiles(device_id);
-- Stores individual Windows Firewall rules
CREATE TABLE DeviceFirewallRules (
firewall_rule_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
rule_name VARCHAR(255) NOT NULL,
group_name VARCHAR(255) NULL,
profile_names TEXT[] NULL, -- Profiles this rule applies to ('Domain', 'Private', 'Public', 'Any')
is_enabled BOOLEAN NULL,
direction VARCHAR(10) NULL CHECK (direction IN ('Inbound', 'Outbound')),
action VARCHAR(10) NULL CHECK (action IN ('Allow', 'Block')),
protocol VARCHAR(50) NULL, -- e.g., 'TCP', 'UDP', 'ICMPv4', 'Any', protocol number
local_ports TEXT NULL, -- e.g., '80', '443', '1000-2000', 'RPC'
remote_ports TEXT NULL,
local_addresses TEXT NULL, -- e.g., 'Any', 'LocalSubnet', specific IPs/CIDRs
remote_addresses TEXT NULL,
program_path TEXT NULL, -- Application path restriction
service_name VARCHAR(255) NULL, -- Service name restriction
description TEXT NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, rule_name) -- Assuming rule names are unique per device, adjust if needed
);
COMMENT ON TABLE DeviceFirewallRules IS 'Stores individual Windows Firewall rules discovered on a device.';
CREATE INDEX idx_devicefirewallrules_device_id ON DeviceFirewallRules(device_id);
-- Stores discovered local user accounts on a device
CREATE TABLE LocalUserAccounts (
local_user_account_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
account_name VARCHAR(256) NOT NULL,
full_name VARCHAR(255) NULL,
description TEXT NULL,
sid VARCHAR(100) NOT NULL,
sid_type VARCHAR(30) NULL, -- e.g., 'User', 'Group', 'Domain', 'Alias'
is_disabled BOOLEAN NULL,
is_locked_out BOOLEAN NULL,
is_local_account BOOLEAN NULL, -- Should always be true for this table
is_admin BOOLEAN NULL, -- Indicates membership in local Administrators group
password_changeable BOOLEAN NULL,
password_expires BOOLEAN NULL,
password_required BOOLEAN NULL,
status VARCHAR(20) NULL, -- e.g., 'OK', 'Degraded', 'Error' from WMI Status
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, sid)
);
COMMENT ON TABLE LocalUserAccounts IS 'Stores details about local user accounts discovered on a device.';
COMMENT ON COLUMN LocalUserAccounts.is_admin IS 'Indicates if this local user is a member of the local Administrators group.';
CREATE INDEX idx_localuseraccounts_device_id ON LocalUserAccounts(device_id);
CREATE INDEX idx_localuseraccounts_sid ON LocalUserAccounts(sid);
-- Stores discovered local groups on a device
CREATE TABLE LocalGroups (
local_group_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
group_name VARCHAR(256) NOT NULL,
description TEXT NULL,
sid VARCHAR(100) NOT NULL,
sid_type VARCHAR(30) NULL,
status VARCHAR(20) NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, sid)
);
COMMENT ON TABLE LocalGroups IS 'Stores details about local groups discovered on a device.';
CREATE INDEX idx_localgroups_device_id ON LocalGroups(device_id);
CREATE INDEX idx_localgroups_sid ON LocalGroups(sid);
-- Stores membership of local groups
CREATE TABLE LocalGroupMemberships (
local_group_membership_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
group_sid VARCHAR(100) NOT NULL, -- SID of the local group
member_sid VARCHAR(100) NOT NULL, -- SID of the member (local user, domain user, domain group)
member_name VARCHAR(256) NULL, -- Name of the member (e.g., 'BUILTIN\\Administrators', 'DOMAIN\\User')
member_domain VARCHAR(255) NULL,
member_type VARCHAR(30) NULL, -- From parsing SID or name
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, group_sid, member_sid)
-- Consider adding FKs to LocalGroups(sid) and potentially AdUsers/AdGroups(object_sid) if SIDs are reliable cross-domain
);
COMMENT ON TABLE LocalGroupMemberships IS 'Stores the membership of local groups on a device.';
CREATE INDEX idx_localgroupmemberships_device_id ON LocalGroupMemberships(device_id);
CREATE INDEX idx_localgroupmemberships_group_sid ON LocalGroupMemberships(group_sid);
CREATE INDEX idx_localgroupmemberships_member_sid ON LocalGroupMemberships(member_sid);
CREATE TABLE LocalUserProfiles (
local_user_profile_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
account_sid VARCHAR(100) NOT NULL, -- FK conceptually links to LocalUserAccounts.sid
profile_path TEXT NULL, -- Added: Path to the user profile directory
size_bytes BIGINT NULL, -- Added: Calculated size of the profile directory
status VARCHAR(30) NULL CHECK (status IN ('OK', 'Roaming', 'Temporary', 'Backup', 'Mandatory', 'Corrupt', 'Unknown')), -- Added: Profile status flags
last_used_time_utc TIMESTAMPTZ NULL, -- Added: Timestamp when profile was last used/loaded
last_logon TIMESTAMPTZ NULL, -- From Win32_NetworkLoginProfile
logon_count INTEGER NULL, -- From Win32_NetworkLoginProfile
bad_password_count INTEGER NULL, -- From Win32_NetworkLoginProfile
password_age_seconds BIGINT NULL, -- From Win32_NetworkLoginProfile
account_expires TIMESTAMPTZ NULL, -- From Win32_NetworkLoginProfile
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, account_sid)
);
COMMENT ON TABLE LocalUserProfiles IS 'Stores network login and profile status information (like last logon, password age, profile size, status, last used) associated with local user accounts.';
COMMENT ON COLUMN LocalUserProfiles.account_sid IS 'SID of the local user account this profile belongs to.';
COMMENT ON COLUMN LocalUserProfiles.status IS 'Status flags indicating profile type (Roaming, Temporary, Mandatory) or state.';
CREATE INDEX idx_localuserprofiles_device_id ON LocalUserProfiles(device_id);
CREATE INDEX idx_localuserprofiles_account_sid ON LocalUserProfiles(account_sid);
-- Stores static IP routes configured on a device
CREATE TABLE DeviceStaticRoutes (
static_route_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
destination_cidr CIDR NOT NULL,
gateway_ip INET NOT NULL,
metric INTEGER NULL,
interface_index INTEGER NULL,
interface_name VARCHAR(255) NULL, -- Can be derived from index
is_persistent BOOLEAN NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, destination_cidr, gateway_ip, interface_index)
);
COMMENT ON TABLE DeviceStaticRoutes IS 'Stores static IP routes configured on a device.';
CREATE INDEX idx_devicestaticroutes_device_id ON DeviceStaticRoutes(device_id);
-- Stores battery information for devices that have them (laptops)
CREATE TABLE DeviceBattery (
device_battery_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
caption VARCHAR(255) NULL, -- e.g., 'Internal Battery'
description VARCHAR(255) NULL,
battery_status_enum VARCHAR(50) NULL, -- e.g., 'Charging', 'Discharging', 'Fully Charged', 'Low' (Mapped from codes)
charge_remaining_percent INTEGER NULL,
estimated_runtime_minutes INTEGER NULL,
full_charge_capacity_mwh INTEGER NULL,
design_capacity_mwh INTEGER NULL,
time_on_battery_seconds BIGINT NULL,
expected_lifespan_seconds BIGINT NULL, -- ExpectedLife from WMI
time_to_full_charge_seconds BIGINT NULL,
chemistry VARCHAR(50) NULL, -- e.g., 'Li-Ion'
health_status VARCHAR(50) NULL, -- General health if available
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, caption) -- Assuming caption/ID is unique per device
);
COMMENT ON TABLE DeviceBattery IS 'Stores status and health information for batteries in devices like laptops.';
CREATE INDEX idx_devicebattery_device_id ON DeviceBattery(device_id);
-- Stores specific registry values being monitored based on policy/configuration
CREATE TABLE MonitoredRegistryValues (
monitored_reg_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
key_path TEXT NOT NULL, -- Full path to the registry key
value_name VARCHAR(255) NOT NULL, -- Name of the value (@ for default)
value_data TEXT NULL, -- Value stored as text
data_type VARCHAR(30) NULL, -- e.g., 'REG_SZ', 'REG_DWORD', 'REG_BINARY'
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, key_path, value_name)
);
COMMENT ON TABLE MonitoredRegistryValues IS 'Stores the current value of specific registry keys/values being monitored on devices.';
CREATE INDEX idx_monitoredregistryvalues_device_id ON MonitoredRegistryValues(device_id);
-- Stores applications configured to run at startup
CREATE TABLE DeviceStartupItems (
startup_item_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
item_name VARCHAR(255) NOT NULL,
publisher VARCHAR(255) NULL,
status VARCHAR(20) NULL, -- e.g., 'Enabled', 'Disabled'
startup_type VARCHAR(30) NULL, -- e.g., 'RegistryRun', 'StartupFolder', 'TaskScheduler'
command_line TEXT NULL,
is_running_now BOOLEAN NULL,
process_id INTEGER NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, item_name, startup_type, command_line) -- Best guess at uniqueness
);
COMMENT ON TABLE DeviceStartupItems IS 'Stores information about applications configured to run at system startup.';
CREATE INDEX idx_devicestartupitems_device_id ON DeviceStartupItems(device_id);
-- Stores active user sessions on a device
CREATE TABLE DeviceSessions (
device_session_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
user_name VARCHAR(255) NOT NULL,
user_domain VARCHAR(255) NULL,
winstation_name VARCHAR(100) NULL, -- e.g., 'Console', 'RDP-Tcp#0'
session_state VARCHAR(50) NULL, -- e.g., 'Active', 'Disconnected'
logon_time_utc TIMESTAMPTZ NULL, -- Start time of this specific session
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this session was last detected as active/disconnected
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, winstation_name, user_name, user_domain) -- Combination should be unique for active sessions
);
COMMENT ON TABLE DeviceSessions IS 'Stores information about active user sessions (Console, RDP, etc.) on a device.';
CREATE INDEX idx_devicesessions_device_id ON DeviceSessions(device_id);
-- Stores OS upgrade/feature update history
CREATE TABLE OsUpgradeHistory (
os_upgrade_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
product_name VARCHAR(255) NULL, -- e.g., 'Windows 11 Enterprise'
edition VARCHAR(100) NULL,
release_id VARCHAR(50) NULL, -- e.g., '23H2'
install_type VARCHAR(50) NULL, -- e.g., 'Feature Update', 'Clean Install'
system_root TEXT NULL,
install_timestamp_utc TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, install_timestamp_utc) -- Should be unique install event per device
);
COMMENT ON TABLE OsUpgradeHistory IS 'Stores history of major OS upgrades or feature updates applied to a device.';
CREATE INDEX idx_osupgradehistory_device_id ON OsUpgradeHistory(device_id);
CREATE TABLE DeviceDiskEncryptionStatus (
disk_encryption_id BIGSERIAL PRIMARY KEY,
logical_disk_id UUID NOT NULL REFERENCES DeviceLogicalDisks(disk_id) ON DELETE CASCADE,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Denormalized for easier query
encryption_method VARCHAR(30) NOT NULL, -- e.g., 'BitLocker', 'FileVault', 'LUKS', 'Veracrypt'
encryption_status VARCHAR(30) NOT NULL CHECK (encryption_status IN ('FullyEncrypted', 'FullyDecrypted', 'EncryptionInProgress', 'DecryptionInProgress', 'EncryptionPaused', 'DecryptionPaused', 'Locked', 'NotApplicable', 'Unknown')),
protection_status VARCHAR(20) NULL CHECK (protection_status IN ('On', 'Off', 'Unknown')), -- BitLocker specific: Protection Suspend status
percent_encrypted INTEGER NULL CHECK (percent_encrypted >= 0 AND percent_encrypted <= 100),
lock_status VARCHAR(20) NULL CHECK (lock_status IN ('Locked', 'Unlocked', 'Unknown')), -- Is the volume currently locked?
protector_types TEXT[] NULL, -- e.g., ['TPM', 'Password', 'RecoveryKey', 'StartupKey']
recovery_key_encrypted BYTEA NULL, -- Encrypted BitLocker recovery key (HIGH RISK - see comment)
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE(logical_disk_id) -- Assuming one encryption status per logical disk
);
COMMENT ON TABLE DeviceDiskEncryptionStatus IS 'Tracks the encryption status (BitLocker, FileVault, LUKS, etc.) of logical disks. Includes encrypted recovery key storage (USE WITH EXTREME CAUTION).';
COMMENT ON COLUMN DeviceDiskEncryptionStatus.logical_disk_id IS 'Links to the specific logical disk (volume) record.';
COMMENT ON COLUMN DeviceDiskEncryptionStatus.protector_types IS 'List of key protectors enabled for this volume (e.g., TPM, Password).';
COMMENT ON COLUMN DeviceDiskEncryptionStatus.recovery_key_encrypted IS '[HIGH RISK] Encrypted BitLocker recovery key. Requires robust application-level encryption/decryption with unique keys per tenant/device, strict role-based access control (RBAC) within the application to limit decryption capability, and comprehensive, immutable audit logging for all decryption attempts (successful or failed). Prefer using AD/Azure AD/Vault storage instead.';
-- Add relevant indexes
CREATE INDEX idx_devicediskencryption_device_id ON DeviceDiskEncryptionStatus(device_id);
CREATE INDEX idx_devicediskencryption_status ON DeviceDiskEncryptionStatus(encryption_status);
CREATE INDEX idx_devicediskencryption_method ON DeviceDiskEncryptionStatus(encryption_method);
-- Stores detailed Microsoft Defender configuration settings for a device
CREATE TABLE DeviceDefenderConfiguration (
defender_config_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE UNIQUE, -- One config record per device
-- General / UI
ui_lockdown_enabled BOOLEAN NULL, -- Maps to UILockdown
force_device_control_enabled BOOLEAN NULL, -- Placeholder if needed later
-- Real-time Protection
is_realtime_monitoring_enabled BOOLEAN NULL, -- Maps to DisableRealtimeMonitoring (inverted)
is_behavior_monitoring_enabled BOOLEAN NULL, -- Maps to DisableBehaviorMonitoring (inverted)
is_ioav_protection_enabled BOOLEAN NULL, -- Maps to DisableIOAVProtection (inverted) (Scan downloads/attachments)
is_script_scanning_enabled BOOLEAN NULL, -- Maps to DisableScriptScanning (inverted)
scan_on_realtime_access_enabled BOOLEAN NULL, -- Maps to DisableScanOnRealtimeAccess (inverted) (NTFS Scanning related)
-- Cloud Protection
cloud_block_level VARCHAR(30) NULL, -- Maps to MAPSReporting - Basic/Advanced
cloud_extended_timeout INTEGER NULL, -- Maps to CloudExtendedTimeout
is_cloud_protection_enabled BOOLEAN NULL, -- Maps to MAPSReporting > 0
submit_samples_consent VARCHAR(30) NULL, -- Maps to SubmitSamplesConsent - None/Safe/Malware/All
is_pua_protection_enabled VARCHAR(30) NULL, -- Maps to PUAProtection - Off/On/Audit
-- Scans
scan_only_if_idle_enabled BOOLEAN NULL,
scan_email_enabled BOOLEAN NULL, -- Maps to DisableEmailScanning (inverted)
scan_removable_drives_enabled BOOLEAN NULL, -- Maps to DisableRemovableDriveScanning (inverted)
scan_restore_points_enabled BOOLEAN NULL, -- Maps to DisableRestorePoint (inverted)
scan_network_files_enabled BOOLEAN NULL, -- Maps to DisableScanningNetworkFiles (inverted)
scan_archives_enabled BOOLEAN NULL, -- Maps to DisableArchiveScanning (inverted)
check_for_signatures_before_scan BOOLEAN NULL,
catchup_full_scan_enabled BOOLEAN NULL, -- Maps to DisableCatchupFullScan (inverted)
catchup_quick_scan_enabled BOOLEAN NULL, -- Maps to DisableCatchupQuickScan (inverted)
avg_cpu_load_factor INTEGER NULL, -- Maps to ScanAvgCPULoadFactor (Max Scan CPU %)
quarantine_purge_days INTEGER NULL, -- Maps to QuarantinePurgeItemsAfterDelay
scheduled_scan_day VARCHAR(20) NULL, -- Maps to ScheduledScanDay - Everyday/SpecificDay/Never
scheduled_scan_time_of_day TIME NULL, -- Maps to ScheduledScanTime (requires conversion)
scheduled_scan_type VARCHAR(20) NULL, -- Maps to ScanParameters - Quick/Full
randomize_schedule_task_times BOOLEAN NULL,
-- Threat Actions (Low, Moderate, High, Severe - Maps to ThreatIDDefaultAction_Ids where Id maps to severity)
low_threat_action VARCHAR(30) NULL, -- Clean/Quarantine/Remove/Allow/UserDefined/Block
moderate_threat_action VARCHAR(30) NULL,
high_threat_action VARCHAR(30) NULL,
severe_threat_action VARCHAR(30) NULL,
unknown_threat_action VARCHAR(30) NULL, -- Placeholder, may not map directly
-- ASR / Advanced (Maps to AttackSurfaceReductionRules_Ids and AttackSurfaceReductionOnlyExclusions)
asr_rules_state JSONB NULL, -- Store state per rule GUID, e.g., {"GUID1": "Enabled", "GUID2": "Audit"}
network_protection_level VARCHAR(30) NULL, -- Maps to EnableNetworkProtection - Off/On/Audit
controlled_folder_access_state VARCHAR(30) NULL, -- Maps to EnableControlledFolderAccess - Off/On/Audit
controlled_folder_access_protected_folders TEXT[] NULL,
controlled_folder_access_allowed_apps TEXT[] NULL,
-- Exclusions
excluded_processes TEXT[] NULL, -- Maps to ExcludedProcesses
excluded_paths TEXT[] NULL, -- Maps to ExcludedPaths
excluded_extensions TEXT[] NULL, -- Maps to ExcludedExtensions
-- Updates
signature_update_interval_hours INTEGER NULL, -- Maps to SignatureUpdateInterval
-- Other Relevant Settings... Add as identified from Get-MpPreference
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
COMMENT ON TABLE DeviceDefenderConfiguration IS 'Stores detailed configuration settings for Microsoft Defender Antivirus on a device, primarily collected via Get-MpPreference.';
CREATE INDEX idx_devicedefenderconfig_device_id ON DeviceDefenderConfiguration(device_id);
-- Stores Hardware Sensor Readings (Temperature, Fan Speed)
CREATE TABLE DeviceSensorReadings (
sensor_reading_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
sensor_type VARCHAR(30) NOT NULL CHECK (sensor_type IN ('Temperature', 'FanSpeed', 'Voltage', 'Other')),
sensor_name VARCHAR(255) NOT NULL, -- Identifier from OS/Hardware (e.g., 'CPU Core 0', 'GPU Die', 'Thermal Zone 0', 'Chassis Fan 1')
reading_value REAL NULL, -- The numeric value (e.g., Degrees C for Temp, RPM for Fan)
reading_unit VARCHAR(10) NULL, -- e.g., 'C', 'RPM', 'V'
status VARCHAR(30) NULL, -- Optional status if provided by sensor (e.g., 'OK', 'Warning', 'Critical')
reading_timestamp_utc TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When the reading was taken
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Add index on device_id, sensor_name, reading_timestamp_utc for time-series style queries
UNIQUE (device_id, sensor_name, reading_timestamp_utc) -- Ensure unique reading per sensor at a given time
);
COMMENT ON TABLE DeviceSensorReadings IS 'Stores periodic hardware sensor readings like temperature and fan speed.';
CREATE INDEX idx_devicesensorreadings_device_time ON DeviceSensorReadings(device_id, reading_timestamp_utc DESC);
-- Stores details about installed device drivers
CREATE TABLE DeviceDrivers (
driver_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
device_name VARCHAR(255) NULL, -- Friendly name of the hardware device using the driver
pnp_device_id VARCHAR(512) NULL, -- Plug and Play Device ID from Win32_PNPSignedDriver
driver_provider VARCHAR(255) NULL, -- Manufacturer/Provider of the driver
driver_version VARCHAR(100) NULL,
driver_date DATE NULL,
is_signed BOOLEAN NULL,
inf_name VARCHAR(255) NULL, -- INF file name (Windows)
driver_path TEXT NULL, -- Path to driver file(s) if available
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, pnp_device_id, driver_version) -- Approximate uniqueness for Windows
-- Uniqueness might be harder cross-platform, potentially base on device identifier + driver path/name
);
COMMENT ON TABLE DeviceDrivers IS 'Stores information about installed device drivers on an endpoint.';
CREATE INDEX idx_devicedrivers_device_id ON DeviceDrivers(device_id);
CREATE INDEX idx_devicedrivers_pnp_id ON DeviceDrivers(pnp_device_id);
-- Stores time-series data about active application/window usage per user session
CREATE TABLE UserActivityLog (
activity_log_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
user_session_identifier TEXT NOT NULL, -- Identifier linking to the specific user session (e.g., User SID + Session ID)
application_name VARCHAR(255) NULL, -- Name of the primary application (e.g., 'Google Chrome')
process_name VARCHAR(255) NULL, -- Name of the executable (e.g., 'chrome.exe')
window_title TEXT NULL, -- Title text of the foreground window
url TEXT NULL, -- URL if detected from a supported browser (best effort)
start_time_utc TIMESTAMPTZ NOT NULL, -- When this activity period started
end_time_utc TIMESTAMPTZ NOT NULL, -- When this activity period ended (focus change or idle)
duration_seconds INTEGER NOT NULL CHECK (duration_seconds >= 0), -- Calculated duration
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -- When the server recorded this batch
);
COMMENT ON TABLE UserActivityLog IS 'Stores recorded foreground application usage periods for tracked devices/users.';
COMMENT ON COLUMN UserActivityLog.user_session_identifier IS 'Identifies the specific user session this activity belongs to.';
COMMENT ON COLUMN UserActivityLog.url IS 'URL captured from supported browsers during the activity period (best effort).';
-- Add indexes appropriate for time-series querying and aggregation
CREATE INDEX idx_useractivitylog_device_time ON UserActivityLog(device_id, start_time_utc DESC);
CREATE INDEX idx_useractivitylog_user_session ON UserActivityLog(user_session_identifier);
CREATE INDEX idx_useractivitylog_app_name ON UserActivityLog(application_name);
-- Stores information about detected web browsers on a device
CREATE TABLE DeviceBrowsers (
device_browser_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
browser_name VARCHAR(100) NOT NULL, -- e.g., 'Google Chrome', 'Microsoft Edge', 'Mozilla Firefox', 'Safari'
version VARCHAR(50) NULL,
install_path TEXT NULL,
is_default BOOLEAN NULL,
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, browser_name, install_path) -- Assumes name+path is unique per device
);
COMMENT ON TABLE DeviceBrowsers IS 'Stores basic identifying information about web browsers installed on a device.';
CREATE INDEX idx_devicebrowsers_device_id ON DeviceBrowsers(device_id);
-- Stores details about installed browser extensions
CREATE TABLE BrowserExtensions (
browser_extension_id BIGSERIAL PRIMARY KEY,
device_browser_id BIGINT NOT NULL REFERENCES DeviceBrowsers(device_browser_id) ON DELETE CASCADE, -- Link to the specific browser instance
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Denormalized for easier querying
user_context VARCHAR(100) NOT NULL, -- User SID or identifier ('All Users', 'System') this extension is installed for
extension_id VARCHAR(255) NOT NULL, -- Unique ID from the browser/store
name VARCHAR(255) NULL,
version VARCHAR(50) NULL,
is_enabled BOOLEAN NULL,
install_path TEXT NULL, -- Path within profile if found
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_browser_id, user_context, extension_id)
);
COMMENT ON TABLE BrowserExtensions IS 'Stores details about extensions installed for specific browsers and user contexts on a device.';
CREATE INDEX idx_browserextensions_device_id ON BrowserExtensions(device_id);
CREATE INDEX idx_browserextensions_device_browser_id ON BrowserExtensions(device_browser_id);
CREATE INDEX idx_browserextensions_extension_id ON BrowserExtensions(extension_id);
-- Stores key browser configuration settings, focusing on policy-derived values
CREATE TABLE BrowserConfiguration (
browser_config_id BIGSERIAL PRIMARY KEY,
device_browser_id BIGINT NOT NULL REFERENCES DeviceBrowsers(device_browser_id) ON DELETE CASCADE, -- Link to the specific browser instance
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Denormalized
user_context VARCHAR(100) NOT NULL, -- User SID or identifier ('MachinePolicy', 'Recommended') this setting applies to
setting_name VARCHAR(255) NOT NULL, -- Name of the setting (e.g., 'SafeBrowseEnabled', 'PasswordManagerEnabled', 'AutoUpdateCheckEnabled')
setting_value TEXT NULL, -- Value of the setting
source VARCHAR(30) NULL CHECK (source IN ('Policy', 'MDM', 'UserPreference', 'Default')), -- How the setting was determined
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_browser_id, user_context, setting_name)
);
COMMENT ON TABLE BrowserConfiguration IS 'Stores key configuration settings discovered for specific browsers/user contexts, prioritizing policy sources.';
CREATE INDEX idx_browserconfiguration_device_id ON BrowserConfiguration(device_id);
CREATE INDEX idx_browserconfiguration_device_browser_id ON BrowserConfiguration(device_browser_id);
CREATE INDEX idx_browserconfiguration_setting_name ON BrowserConfiguration(setting_name);
-- Stores detailed OS audit policy settings per subcategory
CREATE TABLE DetailedAuditPolicySettings (
audit_policy_setting_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
policy_category VARCHAR(100) NOT NULL, -- e.g., 'System', 'Logon/Logoff', 'Object Access'
policy_subcategory VARCHAR(255) NOT NULL, -- e.g., 'Security State Change', 'Logon', 'File System'
auditing_flags VARCHAR(20) NOT NULL CHECK (auditing_flags IN ('None', 'Success', 'Failure', 'Success and Failure', 'Unknown')), -- Effective setting
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (device_id, policy_category, policy_subcategory)
);
COMMENT ON TABLE DetailedAuditPolicySettings IS 'Stores granular operating system audit policy settings (Success/Failure) per subcategory.';
CREATE INDEX idx_detailedauditpolicy_device_id ON DetailedAuditPolicySettings(device_id);
-- Track remote control sessions
CREATE TABLE RemoteControlSessions (
remote_control_session_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
technician_user_id BIGINT NULL, -- Link to user initiating the session if available
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NULL, -- Null if session is still active
session_status VARCHAR(20) NOT NULL CHECK (session_status IN ('Requested', 'Connected', 'Disconnected', 'Failed')),
remote_control_tool VARCHAR(100) NULL, -- e.g., 'TeamViewer', 'ScreenConnect'
connection_details TEXT NULL, -- Tool-specific connection info (e.g., session ID, invitation code)
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE RemoteControlSessions IS 'Tracks remote control sessions initiated to managed devices.';
CREATE INDEX idx_remotecontrolsessions_device_id ON RemoteControlSessions(device_id);
CREATE INDEX idx_remotecontrolsessions_technician ON RemoteControlSessions(technician_user_id);
-- Manages software packages for deployment
CREATE TABLE SoftwarePackages (
software_package_id BIGSERIAL PRIMARY KEY,
package_name VARCHAR(255) NOT NULL, -- e.g., 'Google Chrome', '7-Zip'
version VARCHAR(50) NULL,
architecture VARCHAR(20) NULL, -- 'x86', 'x64', 'arm64'
package_type VARCHAR(20) NOT NULL CHECK (package_type IN ('MSI', 'PKG', 'EXE', 'DEB', 'RPM', 'SCRIPT')),
package_location TEXT NULL, -- UNC path or URL to the package
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ, -- Auto-updated
UNIQUE (package_name, version, architecture, package_type)
);
COMMENT ON TABLE SoftwarePackages IS 'Stores details about available software packages for deployment to devices.';
-- Defines deployment jobs for software packages
CREATE TABLE SoftwareDeploymentJobs (
deployment_job_id BIGSERIAL PRIMARY KEY,
software_package_id BIGINT NOT NULL REFERENCES SoftwarePackages(software_package_id) ON DELETE CASCADE,
job_name VARCHAR(255) NULL,
target_type VARCHAR(20) NOT NULL CHECK (target_type IN ('Device', 'DeviceGroup', 'Policy')),
target_id UUID NOT NULL, -- device_id, device_group_id, or policy_id
schedule_type VARCHAR(20) NULL CHECK (schedule_type IN ('Immediate', 'Scheduled', 'Recurring')),
scheduled_time_utc TIMESTAMPTZ NULL, -- For 'Scheduled'
recurring_cron_expression VARCHAR(255) NULL, -- For 'Recurring'
arguments TEXT NULL, -- Command-line arguments for scripts/installers
timeout_seconds INTEGER NULL, -- Max time to wait for completion
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
COMMENT ON TABLE SoftwareDeploymentJobs IS 'Defines software deployment jobs, targeting devices/groups/policies.';
CREATE INDEX idx_softwaredeploymentjobs_package ON SoftwareDeploymentJobs(software_package_id);
-- Tracks the status of a specific software deployment job on a device
CREATE TABLE DeviceDeploymentStatus (
device_deployment_status_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
deployment_job_id BIGINT NOT NULL REFERENCES SoftwareDeploymentJobs(deployment_job_id) ON DELETE CASCADE,
status VARCHAR(20) NOT NULL CHECK (status IN ('Pending', 'Downloading', 'Installing', 'Success', 'Failed', 'Timeout')),
start_time_utc TIMESTAMPTZ NULL, -- When the deployment started
end_time_utc TIMESTAMPTZ NULL, -- When the deployment ended (or null if still running)
exit_code INTEGER NULL, -- For script executions
log_output TEXT NULL, -- Capture logs from the deployment process
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE DeviceDeploymentStatus IS 'Tracks the status of software deployments on individual devices.';
CREATE INDEX idx_devicedeploystatus_device_id ON DeviceDeploymentStatus(device_id);
CREATE INDEX idx_devicedeploystatus_job_id ON DeviceDeploymentStatus(deployment_job_id);
-- Stores crash dump data from devices
CREATE TABLE DeviceCrashDumps (
device_crash_dump_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
crash_time_utc TIMESTAMPTZ NOT NULL,
bugcheck_code VARCHAR(50) NULL, -- Windows BSOD code
crashing_driver VARCHAR(255) NULL,
dump_file_path TEXT NULL, -- Path to the uploaded dump file
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
notes TEXT NULL -- Any additional notes or analysis
);
COMMENT ON TABLE DeviceCrashDumps IS 'Stores details of system crashes (BSODs) reported by devices.';
CREATE INDEX idx_devicecrashdumps_device_id ON DeviceCrashDumps(device_id);
-- Stores granular, potentially high-volume application usage data
CREATE TABLE SoftwareUsageLog (
software_usage_log_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
application_name VARCHAR(255) NOT NULL,
version VARCHAR(50) NULL,
start_time_utc TIMESTAMPTZ NOT NULL,
end_time_utc TIMESTAMPTZ NULL, -- Null if still running
duration_seconds INTEGER NULL, -- Calculated duration
user_context VARCHAR(100) NULL, -- User account
is_foreground BOOLEAN NULL, -- Was the application in the foreground?
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE SoftwareUsageLog IS 'Tracks time-series usage of installed software on devices.';
CREATE INDEX idx_softwareusagelog_device_id ON SoftwareUsageLog(device_id);
CREATE INDEX idx_softwareusagelog_app_name ON SoftwareUsageLog(application_name);
-- Logs USB device connection/disconnection events
CREATE TABLE DeviceUsbHistory (
device_usb_history_id BIGSERIAL PRIMARY KEY,
device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE,
event_time_utc TIMESTAMPTZ NOT NULL,
event_type VARCHAR(20) NOT NULL CHECK (event_type IN ('Connect', 'Disconnect')),
device_name VARCHAR(255) NULL, -- From the OS, if available
vendor_id VARCHAR(10) NULL,
product_id VARCHAR(10) NULL,
serial_number VARCHAR(255) NULL,
user_context VARCHAR(100) NULL, -- User account
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE DeviceUsbHistory IS 'Logs connection/disconnection events for USB devices attached to tracked devices.';
CREATE INDEX idx_deviceusbhistory_device_id ON DeviceUsbHistory(device_id);
-- Stores configuration settings for discovered Hypervisor Hosts
CREATE TABLE HypervisorHostConfiguration (
device_id UUID PRIMARY KEY REFERENCES Devices(device_id) ON DELETE CASCADE, -- Links 1-to-1 with the Host Device
hypervisor_type VARCHAR(30) NOT NULL CHECK (hypervisor_type IN ('Hyper-V', 'VMware Workstation', 'VMware Fusion', 'VirtualBox', 'KVM/Libvirt', 'Parallels', 'Other', 'Unknown')), -- Type detected by agent
os_version TEXT NULL, -- Host OS version string
default_vm_config_path TEXT NULL, -- Default path for VM definitions
default_vhd_path TEXT NULL, -- Default path for virtual disks
numa_spanning_enabled BOOLEAN NULL,
-- Hyper-V specific settings moved to JSONB
-- Other hypervisor specific settings can also go here
configuration_details JSONB NULL, -- Store hypervisor-specific details (Live Migration, Replication, Enhanced Session Mode, vSwitch settings etc.)
error_state TEXT NULL, -- Errors during collection for this host
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ -- Auto-updated
);
COMMENT ON TABLE HypervisorHostConfiguration IS 'Stores configuration settings for discovered hypervisor hosts (Hyper-V, VMware Workstation/Fusion, VirtualBox, KVM, etc.). V1 agent populates only for Hyper-V.';
COMMENT ON COLUMN HypervisorHostConfiguration.hypervisor_type IS 'The type of hypervisor software detected running directly on the host OS.';
COMMENT ON COLUMN HypervisorHostConfiguration.configuration_details IS 'JSONB field storing hypervisor-specific configuration details not covered by common fields (e.g., Hyper-V Live Migration/Replication settings, VMware network types).';
-- Stores details about Virtual Switches configured on a Hypervisor Host
CREATE TABLE HostVirtualSwitches (
host_vswitch_id BIGSERIAL PRIMARY KEY,
host_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Link to the Host Device
switch_name VARCHAR(255) NOT NULL,
switch_type VARCHAR(30) NULL, -- Generic types ('External', 'Internal', 'Private') or hypervisor-specific values
notes TEXT NULL,
bound_adapter_name VARCHAR(255) NULL, -- Physical NIC (External) or Host vNIC (Internal/Host-Only)
details JSONB NULL, -- Store hypervisor-specific properties (e.g., VLAN ID for Internal, IOV settings, NAT config)
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (host_device_id, switch_name)
);
COMMENT ON TABLE HostVirtualSwitches IS 'Stores details about Virtual Switches configured on a hypervisor host.';
COMMENT ON COLUMN HostVirtualSwitches.details IS 'JSONB field for hypervisor-specific virtual switch settings (VLAN ID, IOV, NAT config, etc.).';
CREATE INDEX idx_hostvswitches_host ON HostVirtualSwitches(host_device_id);
-- Stores inventory of Virtual Machines running on a Hypervisor Host
CREATE TABLE VirtualMachines (
vm_inventory_id BIGSERIAL PRIMARY KEY,
host_device_id UUID NOT NULL REFERENCES Devices(device_id) ON DELETE CASCADE, -- Link to the Host Device
hypervisor_type VARCHAR(30) NOT NULL, -- Denormalized from host for easier filtering
vm_identifier VARCHAR(255) NOT NULL, -- Hypervisor's unique ID (e.g., Hyper-V GUID, VMware VMX path hash, Libvirt UUID)
vm_name VARCHAR(255) NULL, -- User-friendly name
state VARCHAR(50) NULL, -- e.g., 'Running', 'Off', 'Paused', 'Saved', 'Starting', 'Stopping'
cpu_count INTEGER NULL, -- Number of vCPUs assigned
memory_assigned_mb INTEGER NULL,
uptime_seconds BIGINT NULL,
guest_os TEXT NULL, -- OS Name reported by guest tools/KVP
network_interfaces JSONB NULL, -- Array: [{ "mac": "...", "ip_v4": ["..."], "ip_v6": ["..."], "switch_name": "..." }]
storage_volumes JSONB NULL, -- Array: [{ "path": "...", "type": "Dynamic/Fixed/Diff", "configured_size_gb": X.X, "actual_size_gb": Y.Y, "parent_path": "...", "access_status": "OK/Error..." }]
notes TEXT NULL,
creation_time_utc TIMESTAMPTZ NULL,
details JSONB NULL, -- Store hypervisor-specific VM details (Health State, Op Status, Dynamic Mem config, CPU limits, KVP raw data, Integration Svc Version etc.)
last_seen_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- When this VM record was last updated by inventory
UNIQUE (host_device_id, vm_identifier)
);
COMMENT ON TABLE VirtualMachines IS 'Stores inventory details for each Virtual Machine discovered on a hypervisor host.';
COMMENT ON COLUMN VirtualMachines.vm_identifier IS 'Unique identifier for the VM within its hypervisor (e.g., Hyper-V GUID, Libvirt UUID).';
COMMENT ON COLUMN VirtualMachines.network_interfaces IS 'JSONB array storing details of assigned virtual NICs.';
COMMENT ON COLUMN VirtualMachines.storage_volumes IS 'JSONB array storing details of attached virtual disks.';
COMMENT ON COLUMN VirtualMachines.details IS 'JSONB field for hypervisor-specific VM details not covered by main columns (e.g., health states, dynamic memory, CPU limits, integration services info).';
CREATE INDEX idx_vms_host ON VirtualMachines(host_device_id);
CREATE INDEX idx_vms_identifier ON VirtualMachines(vm_identifier);
CREATE INDEX idx_vms_name ON VirtualMachines(vm_name);