544 lines
22 KiB
PL/PgSQL
Executable File
544 lines
22 KiB
PL/PgSQL
Executable File
-- Enable required extensions
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
|
|
|
-- Update function for updated_at timestamps
|
|
CREATE OR REPLACE FUNCTION update_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Update function for last_accessed
|
|
CREATE OR REPLACE FUNCTION update_last_accessed()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.last_accessed = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- ==========================================
|
|
-- USERS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id SERIAL PRIMARY KEY,
|
|
username VARCHAR(255) UNIQUE NOT NULL,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
role VARCHAR(50) DEFAULT 'admin',
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
mfa_enabled BOOLEAN DEFAULT FALSE,
|
|
mfa_secret VARCHAR(255),
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
last_login TIMESTAMP,
|
|
failed_login_attempts INTEGER DEFAULT 0,
|
|
locked_until TIMESTAMP
|
|
);
|
|
|
|
CREATE TRIGGER users_updated_at BEFORE UPDATE ON users
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
-- ==========================================
|
|
-- SESSIONS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
user_id INTEGER,
|
|
data JSONB DEFAULT '{}',
|
|
expires_at TIMESTAMP NOT NULL,
|
|
last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
user_agent TEXT,
|
|
ip_address INET,
|
|
is_remember_me BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TRIGGER sessions_accessed BEFORE UPDATE ON sessions
|
|
FOR EACH ROW EXECUTE FUNCTION update_last_accessed();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
|
|
|
|
-- ==========================================
|
|
-- CSRF TOKENS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS csrf_tokens (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
user_id INTEGER,
|
|
session_id VARCHAR(255) NOT NULL,
|
|
token_hash VARCHAR(255) UNIQUE NOT NULL,
|
|
expires_at TIMESTAMP NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_csrf_tokens_session ON csrf_tokens(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_csrf_tokens_expires ON csrf_tokens(expires_at);
|
|
|
|
-- ==========================================
|
|
-- MFA BACKUP CODES TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS mfa_backup_codes (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER NOT NULL,
|
|
code_hash VARCHAR(255) NOT NULL,
|
|
is_used BOOLEAN DEFAULT FALSE,
|
|
used_at TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_mfa_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_mfa_backup_codes_user ON mfa_backup_codes(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_mfa_backup_codes_unused ON mfa_backup_codes(user_id, is_used) WHERE is_used = FALSE;
|
|
|
|
-- ==========================================
|
|
-- SESSION AUDIT LOG TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS session_audit_log (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER NOT NULL,
|
|
session_id VARCHAR(255),
|
|
event VARCHAR(50) CHECK (event IN ('login', 'logout', 'expired', 'created', 'destroyed', 'mfa_enabled', 'mfa_disabled')) NOT NULL,
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
metadata JSONB DEFAULT '{}',
|
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_audit_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_audit_user ON session_audit_log(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON session_audit_log(timestamp DESC);
|
|
|
|
-- ==========================================
|
|
-- CONFIG TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS config (
|
|
id SERIAL PRIMARY KEY,
|
|
key VARCHAR(255) UNIQUE NOT NULL,
|
|
value JSONB NOT NULL,
|
|
description TEXT,
|
|
category VARCHAR(100) DEFAULT 'general',
|
|
is_encrypted BOOLEAN DEFAULT FALSE,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_by INTEGER
|
|
);
|
|
|
|
CREATE TRIGGER config_updated_at BEFORE UPDATE ON config
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_config_category ON config(category);
|
|
CREATE INDEX IF NOT EXISTS idx_config_key ON config(key);
|
|
|
|
-- ==========================================
|
|
-- CUSTOMERS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS customers (
|
|
id SERIAL PRIMARY KEY,
|
|
sap_customer_id VARCHAR(255),
|
|
plesk_customer_id VARCHAR(255),
|
|
name VARCHAR(255) NOT NULL,
|
|
email VARCHAR(255),
|
|
status VARCHAR(50) CHECK (status IN ('active', 'inactive', 'pending', 'deleted')) DEFAULT 'active',
|
|
sync_status VARCHAR(50) CHECK (sync_status IN ('in_sync', 'pending_sync', 'sync_error', 'manual_override')) DEFAULT 'pending_sync',
|
|
metadata JSONB DEFAULT '{}',
|
|
last_sync TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TRIGGER customers_updated_at BEFORE UPDATE ON customers
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_customer_mapping ON customers(sap_customer_id, plesk_customer_id)
|
|
WHERE sap_customer_id IS NOT NULL AND plesk_customer_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_customers_status ON customers(status);
|
|
CREATE INDEX IF NOT EXISTS idx_customers_sync_status ON customers(sync_status);
|
|
|
|
-- ==========================================
|
|
-- SUBSCRIPTIONS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
id SERIAL PRIMARY KEY,
|
|
customer_id INTEGER NOT NULL,
|
|
sap_subscription_id VARCHAR(255),
|
|
plesk_subscription_id VARCHAR(255),
|
|
sap_item_code VARCHAR(255),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
start_date DATE,
|
|
end_date DATE,
|
|
billing_cycle VARCHAR(50) CHECK (billing_cycle IN ('monthly', 'annually', 'quarterly', 'custom')),
|
|
status VARCHAR(50) CHECK (status IN ('active', 'suspended', 'terminated', 'pending')) DEFAULT 'active',
|
|
sync_status VARCHAR(50) CHECK (sync_status IN ('in_sync', 'pending_sync', 'sync_error', 'manual_override')) DEFAULT 'pending_sync',
|
|
pricing_data JSONB DEFAULT '{}',
|
|
features JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_subscription_customer FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TRIGGER subscriptions_updated_at BEFORE UPDATE ON subscriptions
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_subscriptions_customer ON subscriptions(customer_id);
|
|
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);
|
|
|
|
-- ==========================================
|
|
-- USAGE METRICS TABLE
|
|
-- ==========================================
|
|
CREATE TYPE metric_type AS ENUM ('cpu', 'ram', 'disk', 'bandwidth', 'database', 'requests', 'emails');
|
|
|
|
CREATE TABLE IF NOT EXISTS usage_metrics (
|
|
id SERIAL PRIMARY KEY,
|
|
subscription_id INTEGER NOT NULL,
|
|
metric_type metric_type NOT NULL,
|
|
metric_value NUMERIC(15,4) NOT NULL,
|
|
unit VARCHAR(50),
|
|
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
metadata JSONB DEFAULT '{}',
|
|
CONSTRAINT fk_usage_subscription FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_usage_subscription ON usage_metrics(subscription_id);
|
|
CREATE INDEX IF NOT EXISTS idx_usage_time ON usage_metrics(recorded_at DESC);
|
|
|
|
-- ==========================================
|
|
-- SYNC JOBS TABLE
|
|
-- ==========================================
|
|
CREATE TYPE sync_job_status AS ENUM ('pending', 'running', 'completed', 'failed', 'cancelled', 'paused');
|
|
|
|
CREATE TABLE IF NOT EXISTS sync_jobs (
|
|
id SERIAL PRIMARY KEY,
|
|
job_type VARCHAR(50) CHECK (job_type IN ('full_sync', 'incremental_sync', 'partial_sync', 'manual_sync')) NOT NULL,
|
|
sync_direction VARCHAR(50) CHECK (sync_direction IN ('sap_to_plesk', 'plesk_to_sap', 'bidirectional')) NOT NULL,
|
|
status sync_job_status DEFAULT 'pending',
|
|
started_at TIMESTAMP,
|
|
completed_at TIMESTAMP,
|
|
records_processed INTEGER DEFAULT 0,
|
|
records_failed INTEGER DEFAULT 0,
|
|
records_skipped INTEGER DEFAULT 0,
|
|
error_message TEXT,
|
|
config_snapshot JSONB DEFAULT '{}',
|
|
progress_percentage NUMERIC(5,2) DEFAULT 0,
|
|
estimated_completion TIMESTAMP,
|
|
created_by INTEGER,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TRIGGER sync_jobs_updated_at BEFORE UPDATE ON sync_jobs
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sync_status ON sync_jobs(status);
|
|
CREATE INDEX IF NOT EXISTS idx_sync_created_at ON sync_jobs(created_at DESC);
|
|
|
|
-- ==========================================
|
|
-- NOTIFICATIONS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
id SERIAL PRIMARY KEY,
|
|
type VARCHAR(50) CHECK (type IN ('info', 'success', 'warning', 'error', 'sync', 'security', 'system')) NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
message TEXT NOT NULL,
|
|
sent_via JSONB NOT NULL DEFAULT '[]',
|
|
recipient_type VARCHAR(50) DEFAULT 'admin',
|
|
recipient VARCHAR(255),
|
|
is_read BOOLEAN DEFAULT FALSE,
|
|
is_actionable BOOLEAN DEFAULT FALSE,
|
|
action_url TEXT,
|
|
data JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
read_at TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_notifications_unread ON notifications(is_read) WHERE is_read = FALSE;
|
|
CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications(created_at DESC);
|
|
|
|
-- ==========================================
|
|
-- WEBHOOKS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS webhooks (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
url VARCHAR(500) NOT NULL,
|
|
secret_key VARCHAR(255),
|
|
events JSONB NOT NULL DEFAULT '[]',
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
retry_policy JSONB DEFAULT '{"max_retries":3,"retry_delay":60}',
|
|
last_triggered TIMESTAMP,
|
|
last_status VARCHAR(50),
|
|
last_error TEXT,
|
|
trigger_count INTEGER DEFAULT 0,
|
|
success_count INTEGER DEFAULT 0,
|
|
failure_count INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
created_by INTEGER
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_webhooks_active ON webhooks(is_active) WHERE is_active = TRUE;
|
|
|
|
-- ==========================================
|
|
-- BACKUPS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS backups (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
type VARCHAR(50) CHECK (type IN ('full', 'config', 'data', 'database')) NOT NULL,
|
|
backup_path VARCHAR(500),
|
|
size_bytes BIGINT,
|
|
checksum VARCHAR(255),
|
|
is_restorable BOOLEAN DEFAULT TRUE,
|
|
metadata JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
created_by INTEGER
|
|
);
|
|
|
|
-- ==========================================
|
|
-- SYNC LOGS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS sync_logs (
|
|
id SERIAL PRIMARY KEY,
|
|
sync_job_id INTEGER NOT NULL,
|
|
entity_type VARCHAR(50) NOT NULL,
|
|
entity_id VARCHAR(255) NOT NULL,
|
|
action VARCHAR(50) NOT NULL,
|
|
status VARCHAR(50) NOT NULL,
|
|
error_message TEXT,
|
|
metadata JSONB DEFAULT '{}',
|
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
resolution_status VARCHAR(50) DEFAULT 'pending',
|
|
resolution_action VARCHAR(50),
|
|
resolved_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
resolved_at TIMESTAMP,
|
|
conflict_details JSONB,
|
|
CONSTRAINT fk_sync_log_job FOREIGN KEY (sync_job_id) REFERENCES sync_jobs(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sync_logs_job ON sync_logs(sync_job_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sync_logs_timestamp ON sync_logs(timestamp DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_sync_logs_status ON sync_logs(status);
|
|
CREATE INDEX IF NOT EXISTS idx_sync_logs_resolution ON sync_logs(resolution_status) WHERE resolution_status = 'pending';
|
|
|
|
-- ==========================================
|
|
-- PLESK SERVERS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS plesk_servers (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
host VARCHAR(255) NOT NULL,
|
|
port INTEGER DEFAULT 8443,
|
|
api_key TEXT,
|
|
username VARCHAR(255),
|
|
password_hash TEXT,
|
|
use_https BOOLEAN DEFAULT TRUE,
|
|
verify_ssl BOOLEAN DEFAULT TRUE,
|
|
two_factor_enabled BOOLEAN DEFAULT FALSE,
|
|
two_factor_method VARCHAR(50),
|
|
connection_status VARCHAR(50) DEFAULT 'unknown',
|
|
last_connected TIMESTAMP,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
metadata JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TRIGGER plesk_servers_updated_at BEFORE UPDATE ON plesk_servers
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_plesk_active ON plesk_servers(is_active) WHERE is_active = TRUE;
|
|
CREATE INDEX IF NOT EXISTS idx_plesk_host ON plesk_servers(host);
|
|
|
|
-- ==========================================
|
|
-- SAP SERVERS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS sap_servers (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
host VARCHAR(255) NOT NULL,
|
|
port INTEGER DEFAULT 50000,
|
|
company_db VARCHAR(255) NOT NULL,
|
|
username VARCHAR(255),
|
|
password_hash TEXT,
|
|
use_ssl BOOLEAN DEFAULT TRUE,
|
|
timeout_seconds INTEGER DEFAULT 30,
|
|
connection_status VARCHAR(50) DEFAULT 'unknown',
|
|
last_connected TIMESTAMP,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
metadata JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TRIGGER sap_servers_updated_at BEFORE UPDATE ON sap_servers
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sap_active ON sap_servers(is_active) WHERE is_active = TRUE;
|
|
CREATE INDEX IF NOT EXISTS idx_sap_company ON sap_servers(company_db);
|
|
|
|
-- ==========================================
|
|
-- SCHEDULED SYNC TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS scheduled_syncs (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
schedule_type VARCHAR(50) CHECK (schedule_type IN ('daily', 'weekly', 'monthly', 'custom')) NOT NULL,
|
|
schedule_config JSONB NOT NULL,
|
|
job_type VARCHAR(50) NOT NULL,
|
|
sync_direction VARCHAR(50) NOT NULL,
|
|
plesk_server_id INTEGER,
|
|
sap_server_id INTEGER,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
last_run TIMESTAMP,
|
|
next_run TIMESTAMP,
|
|
created_by INTEGER,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_scheduled_plesk FOREIGN KEY (plesk_server_id) REFERENCES plesk_servers(id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_scheduled_sap FOREIGN KEY (sap_server_id) REFERENCES sap_servers(id) ON DELETE SET NULL
|
|
);
|
|
|
|
CREATE TRIGGER scheduled_syncs_updated_at BEFORE UPDATE ON scheduled_syncs
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_scheduled_active ON scheduled_syncs(is_active) WHERE is_active = TRUE;
|
|
CREATE INDEX IF NOT EXISTS idx_scheduled_next_run ON scheduled_syncs(next_run);
|
|
|
|
-- ==========================================
|
|
-- PRICING CONFIG TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS pricing_config (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
metric_type metric_type NOT NULL,
|
|
unit VARCHAR(50) NOT NULL,
|
|
rate_per_unit NUMERIC(10,4) NOT NULL,
|
|
currency VARCHAR(3) DEFAULT 'EUR',
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
valid_from DATE DEFAULT CURRENT_DATE,
|
|
valid_to DATE,
|
|
notes TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(metric_type, unit, valid_from)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_pricing_active ON pricing_config(is_active) WHERE is_active = TRUE;
|
|
CREATE INDEX IF NOT EXISTS idx_pricing_metric ON pricing_config(metric_type);
|
|
|
|
-- ==========================================
|
|
-- BILLING RECORDS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS billing_records (
|
|
id SERIAL PRIMARY KEY,
|
|
subscription_id INTEGER NOT NULL,
|
|
customer_id INTEGER NOT NULL,
|
|
period_start DATE NOT NULL,
|
|
period_end DATE NOT NULL,
|
|
usage_data JSONB NOT NULL,
|
|
calculated_amount NUMERIC(12,2) NOT NULL,
|
|
currency VARCHAR(3) DEFAULT 'EUR',
|
|
sap_invoice_id VARCHAR(255),
|
|
sap_invoice_number VARCHAR(255),
|
|
invoice_status VARCHAR(50) CHECK (invoice_status IN ('draft', 'pending', 'sent', 'synced', 'failed')) DEFAULT 'draft',
|
|
invoice_pdf_path VARCHAR(500),
|
|
notes TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
sent_to_sap_at TIMESTAMP,
|
|
CONSTRAINT fk_billing_subscription FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_billing_customer FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TRIGGER billing_records_updated_at BEFORE UPDATE ON billing_records
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_billing_period ON billing_records(period_start, period_end);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_status ON billing_records(invoice_status);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_customer ON billing_records(customer_id);
|
|
|
|
-- ==========================================
|
|
-- ALERT THRESHOLDS TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS alert_thresholds (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
subscription_id INTEGER,
|
|
metric_type metric_type NOT NULL,
|
|
threshold_value NUMERIC(15,4) NOT NULL,
|
|
comparison_operator VARCHAR(10) CHECK (comparison_operator IN ('>', '>=', '<', '<=', '=')) NOT NULL,
|
|
action VARCHAR(50) CHECK (action IN ('notify', 'notify_and_suspend', 'notify_and_limit')) NOT NULL,
|
|
notification_channels JSONB DEFAULT '["email"]',
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
last_triggered TIMESTAMP,
|
|
created_by INTEGER,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TRIGGER alert_thresholds_updated_at BEFORE UPDATE ON alert_thresholds
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_alerts_active ON alert_thresholds(is_active) WHERE is_active = TRUE;
|
|
CREATE INDEX IF NOT EXISTS idx_alerts_subscription ON alert_thresholds(subscription_id);
|
|
CREATE INDEX IF NOT EXISTS idx_alerts_metric ON alert_thresholds(metric_type);
|
|
|
|
-- ==========================================
|
|
-- ALERT HISTORY TABLE
|
|
-- ==========================================
|
|
CREATE TABLE IF NOT EXISTS alert_history (
|
|
id SERIAL PRIMARY KEY,
|
|
threshold_id INTEGER NOT NULL,
|
|
subscription_id INTEGER,
|
|
actual_value NUMERIC(15,4) NOT NULL,
|
|
triggered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
action_taken VARCHAR(50),
|
|
notification_sent BOOLEAN DEFAULT FALSE,
|
|
notification_error TEXT,
|
|
CONSTRAINT fk_alert_history_threshold FOREIGN KEY (threshold_id) REFERENCES alert_thresholds(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_alert_history_triggered ON alert_history(triggered_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_history_threshold ON alert_history(threshold_id);
|
|
|
|
-- Add server_id columns to customers table
|
|
ALTER TABLE customers ADD COLUMN IF NOT EXISTS plesk_server_id INTEGER REFERENCES plesk_servers(id) ON DELETE SET NULL;
|
|
ALTER TABLE customers ADD COLUMN IF NOT EXISTS sap_server_id INTEGER REFERENCES sap_servers(id) ON DELETE SET NULL;
|
|
ALTER TABLE customers ADD COLUMN IF NOT EXISTS last_conflict TIMESTAMP;
|
|
CREATE INDEX IF NOT EXISTS idx_customers_plesk_server ON customers(plesk_server_id);
|
|
CREATE INDEX IF NOT EXISTS idx_customers_sap_server ON customers(sap_server_id);
|
|
|
|
-- Add server_id columns to sync_jobs table
|
|
ALTER TABLE sync_jobs ADD COLUMN IF NOT EXISTS plesk_server_id INTEGER REFERENCES plesk_servers(id) ON DELETE SET NULL;
|
|
ALTER TABLE sync_jobs ADD COLUMN IF NOT EXISTS sap_server_id INTEGER REFERENCES sap_servers(id) ON DELETE SET NULL;
|
|
ALTER TABLE sync_jobs ADD COLUMN IF NOT EXISTS scheduled_sync_id INTEGER REFERENCES scheduled_syncs(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_sync_jobs_plesk_server ON sync_jobs(plesk_server_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sync_jobs_sap_server ON sync_jobs(sap_server_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sync_logs_job ON sync_logs(sync_job_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sync_logs_timestamp ON sync_logs(timestamp DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_sync_logs_status ON sync_logs(status);
|
|
|
|
-- ==========================================
|
|
-- INITIAL DATA
|
|
-- ==========================================
|
|
|
|
-- Default configuration
|
|
INSERT INTO config (key, value, description, category) VALUES
|
|
('sync.default_direction', '"sap_to_plesk"', 'Default sync direction', 'sync'),
|
|
('sync.conflict_resolution', '"timestamp_based"', 'Default conflict resolution strategy', 'sync'),
|
|
('sync.interval_minutes', '60', 'Default sync interval in minutes', 'sync'),
|
|
('auth.session_timeout', '1800', 'Session timeout in seconds (30 min)', 'auth'),
|
|
('auth.max_login_attempts', '5', 'Max failed login attempts before lockout', 'auth'),
|
|
('auth.lockout_duration', '3600', 'Lockout duration in seconds (1 hour)', 'auth'),
|
|
('notifications.email_enabled', 'true', 'Enable email notifications', 'notifications'),
|
|
('notifications.webhook_enabled', 'true', 'Enable webhook notifications', 'notifications'),
|
|
('system.initialized', 'true', 'System initialization flag', 'system')
|
|
ON CONFLICT (key) DO NOTHING;
|
|
|
|
-- Note: Default admin user will be created dynamically by the backend on first startup
|
|
-- Username: admin
|
|
-- Password: Admin123! (default, can be changed via environment variables)
|
|
-- IMPORTANT: Change password immediately after first login |