-- Migration 024: Backup System -- Adds tables for automated backup/restore with offsite upload and notifications -- Backup Jobs Table CREATE TABLE IF NOT EXISTS backup_jobs ( id SERIAL PRIMARY KEY, job_type VARCHAR(20) NOT NULL CHECK (job_type IN ('database', 'files', 'full')), status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'completed', 'failed')), backup_format VARCHAR(10) NOT NULL CHECK (backup_format IN ('dump', 'sql', 'tar.gz')), file_path VARCHAR(500), file_size_bytes BIGINT, checksum_sha256 VARCHAR(64), is_monthly BOOLEAN DEFAULT FALSE, includes_uploads BOOLEAN DEFAULT FALSE, includes_logs BOOLEAN DEFAULT FALSE, includes_data BOOLEAN DEFAULT FALSE, started_at TIMESTAMP, completed_at TIMESTAMP, error_message TEXT, retention_until DATE, offsite_uploaded_at TIMESTAMP, offsite_retry_count INTEGER DEFAULT 0, notification_sent BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Indexes for backup_jobs CREATE INDEX idx_backup_jobs_status ON backup_jobs(status); CREATE INDEX idx_backup_jobs_created ON backup_jobs(created_at DESC); CREATE INDEX idx_backup_jobs_type ON backup_jobs(job_type); CREATE INDEX idx_backup_jobs_is_monthly ON backup_jobs(is_monthly) WHERE is_monthly = TRUE; CREATE INDEX idx_backup_jobs_retention ON backup_jobs(retention_until) WHERE retention_until IS NOT NULL; CREATE INDEX idx_backup_jobs_offsite_pending ON backup_jobs(offsite_uploaded_at) WHERE offsite_uploaded_at IS NULL AND status = 'completed'; -- System Status Table (for maintenance mode) CREATE TABLE IF NOT EXISTS system_status ( id SERIAL PRIMARY KEY, maintenance_mode BOOLEAN DEFAULT FALSE, maintenance_message TEXT DEFAULT 'System under vedligeholdelse', maintenance_eta_minutes INTEGER, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Insert default system status INSERT INTO system_status (maintenance_mode, maintenance_message, maintenance_eta_minutes) VALUES (FALSE, 'System under vedligeholdelse', NULL) ON CONFLICT DO NOTHING; -- Backup Notifications Table CREATE TABLE IF NOT EXISTS backup_notifications ( id SERIAL PRIMARY KEY, backup_job_id INTEGER REFERENCES backup_jobs(id) ON DELETE SET NULL, event_type VARCHAR(30) NOT NULL CHECK (event_type IN ('backup_failed', 'offsite_failed', 'offsite_retry', 'storage_low', 'restore_started', 'backup_success')), message TEXT NOT NULL, mattermost_payload JSONB, sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, acknowledged BOOLEAN DEFAULT FALSE, acknowledged_at TIMESTAMP ); -- Indexes for backup_notifications CREATE INDEX idx_backup_notifications_event ON backup_notifications(event_type); CREATE INDEX idx_backup_notifications_sent ON backup_notifications(sent_at DESC); CREATE INDEX idx_backup_notifications_unacked ON backup_notifications(acknowledged) WHERE acknowledged = FALSE; CREATE INDEX idx_backup_notifications_job ON backup_notifications(backup_job_id) WHERE backup_job_id IS NOT NULL; -- Trigger to update updated_at on backup_jobs CREATE OR REPLACE FUNCTION update_backup_jobs_timestamp() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER backup_jobs_update_timestamp BEFORE UPDATE ON backup_jobs FOR EACH ROW EXECUTE FUNCTION update_backup_jobs_timestamp(); -- Comments for documentation COMMENT ON TABLE backup_jobs IS 'Tracks all backup operations (database, files, full) with retention and offsite upload status'; COMMENT ON TABLE system_status IS 'Global system maintenance mode flag for restore operations'; COMMENT ON TABLE backup_notifications IS 'Notification log for Mattermost alerts on backup events'; COMMENT ON COLUMN backup_jobs.job_type IS 'Type of backup: database (pg_dump), files (tar.gz), or full (both)'; COMMENT ON COLUMN backup_jobs.backup_format IS 'Format: dump (compressed), sql (plain text), or tar.gz (files archive)'; COMMENT ON COLUMN backup_jobs.is_monthly IS 'TRUE for monthly backups kept for 12 months, FALSE for daily 30-day retention'; COMMENT ON COLUMN backup_jobs.offsite_retry_count IS 'Number of times offsite upload has been retried (max 3)'; COMMENT ON COLUMN system_status.maintenance_eta_minutes IS 'Estimated time in minutes until maintenance is complete';