Files
SAP-PLEX-SYNC/database/init.sql
b0rbor4d 5b447acd1c
Some checks failed
CI/CD Pipeline / Backend Tests (push) Failing after 27s
CI/CD Pipeline / Frontend Tests (push) Failing after 15s
CI/CD Pipeline / Docker Build (push) Has been skipped
CI/CD Pipeline / Security Scan (push) Has been skipped
Initial commit
2026-04-15 01:41:49 +02:00

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