Initial commit
This commit is contained in:
32
database/create_admin.sql
Executable file
32
database/create_admin.sql
Executable file
@@ -0,0 +1,32 @@
|
||||
-- Create Default Admin User
|
||||
-- This script creates an initial admin user if it doesn't exist
|
||||
-- Default credentials:
|
||||
-- Username: admin
|
||||
-- Password: Admin123! (CHANGE THIS IMMEDIATELY!)
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
admin_exists INTEGER;
|
||||
password_hash TEXT;
|
||||
BEGIN
|
||||
-- Check if admin user already exists
|
||||
SELECT COUNT(*) INTO admin_exists FROM users WHERE username = 'admin';
|
||||
|
||||
IF admin_exists = 0 THEN
|
||||
-- Generate password hash for 'Admin123!'
|
||||
-- This hash was generated using Argon2 with default parameters
|
||||
-- Password: Admin123!
|
||||
password_hash := '$argon2id$v=19$m=65536,t=3,p=4$4WbVq0dX9qRq4dX9qRq4dQ$wpQsM7Z5NkQ5NkQ5NkQ5NkQ5NkQ5NkQ5NkQ5NkQ5NkQ';
|
||||
|
||||
-- Insert admin user
|
||||
INSERT INTO users (username, password_hash, email, role, is_active, mfa_enabled, created_at, updated_at)
|
||||
VALUES ('admin', password_hash, 'admin@sap-sync.local', 'admin', TRUE, FALSE, NOW(), NOW());
|
||||
|
||||
RAISE NOTICE 'Default admin user created successfully.';
|
||||
RAISE NOTICE 'Username: admin';
|
||||
RAISE NOTICE 'Password: Admin123!';
|
||||
RAISE NOTICE 'IMPORTANT: Change this password immediately after first login!';
|
||||
ELSE
|
||||
RAISE NOTICE 'Admin user already exists, skipping creation.';
|
||||
END IF;
|
||||
END $$;
|
||||
544
database/init.sql
Executable file
544
database/init.sql
Executable file
@@ -0,0 +1,544 @@
|
||||
-- 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
|
||||
224
database/migrate.sql
Executable file
224
database/migrate.sql
Executable file
@@ -0,0 +1,224 @@
|
||||
-- Migration: Create all missing tables
|
||||
-- Run this against the database to add missing schema
|
||||
|
||||
-- Functions
|
||||
CREATE OR REPLACE FUNCTION update_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 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
|
||||
);
|
||||
|
||||
-- Session audit log
|
||||
CREATE TABLE IF NOT EXISTS session_audit_log (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
session_id VARCHAR(255),
|
||||
event VARCHAR(50) NOT NULL,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Plesk servers
|
||||
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 IF NOT EXISTS plesk_servers_updated_at BEFORE UPDATE ON plesk_servers
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
-- SAP servers
|
||||
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 IF NOT EXISTS sap_servers_updated_at BEFORE UPDATE ON sap_servers
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
-- Customers
|
||||
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) DEFAULT 'active',
|
||||
sync_status VARCHAR(50) DEFAULT 'pending_sync',
|
||||
metadata JSONB DEFAULT '{}',
|
||||
last_sync TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Subscriptions
|
||||
CREATE TABLE IF NOT EXISTS subscriptions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
customer_id INTEGER NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
billing_cycle VARCHAR(50),
|
||||
status VARCHAR(50) DEFAULT 'active',
|
||||
sync_status VARCHAR(50) DEFAULT 'pending_sync',
|
||||
pricing_data JSONB DEFAULT '{}',
|
||||
features JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Sync jobs
|
||||
CREATE TABLE IF NOT EXISTS sync_jobs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
job_type VARCHAR(50) NOT NULL,
|
||||
sync_direction VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(50) 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,
|
||||
created_by INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS sync_jobs_updated_at BEFORE UPDATE ON sync_jobs
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
-- Sync logs
|
||||
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,
|
||||
resolved_at TIMESTAMP,
|
||||
conflict_details JSONB,
|
||||
CONSTRAINT fk_sync_log_job FOREIGN KEY (sync_job_id) REFERENCES sync_jobs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Alert thresholds
|
||||
CREATE TABLE IF NOT EXISTS alert_thresholds (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
subscription_id INTEGER,
|
||||
metric_type VARCHAR(50) NOT NULL,
|
||||
threshold_value NUMERIC(15,4) NOT NULL,
|
||||
comparison_operator VARCHAR(10) NOT NULL,
|
||||
action VARCHAR(50) 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
|
||||
);
|
||||
|
||||
-- Alert history
|
||||
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
|
||||
);
|
||||
|
||||
-- Billing records
|
||||
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 DEFAULT '{}',
|
||||
calculated_amount NUMERIC(12,2) NOT NULL,
|
||||
currency VARCHAR(3) DEFAULT 'EUR',
|
||||
sap_invoice_id VARCHAR(255),
|
||||
invoice_status VARCHAR(50) DEFAULT 'draft',
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Pricing config
|
||||
CREATE TABLE IF NOT EXISTS pricing_config (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
metric_type VARCHAR(50) 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,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Default config data
|
||||
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', 'auth'),
|
||||
('system.initialized', 'false', 'System initialization flag', 'system')
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
2
database/migrations/.gitkeep
Executable file
2
database/migrations/.gitkeep
Executable file
@@ -0,0 +1,2 @@
|
||||
# Placeholder for database migrations
|
||||
# This directory can contain additional SQL migration files
|
||||
2
database/seeds/.gitkeep
Executable file
2
database/seeds/.gitkeep
Executable file
@@ -0,0 +1,2 @@
|
||||
# This file keeps the directory in Git
|
||||
# Seed data can be added here for initial database population
|
||||
Reference in New Issue
Block a user