"""Virtual Boxes e Notifiche Multi-canale – Fase 2 Revision ID: 0003 Revises: 0002 Create Date: 2026-03-19 00:00:00.000000 Aggiunge le tabelle: - virtual_boxes - virtual_box_rules - virtual_box_assignments - notification_channels (+ ENUM notification_channel_type) - notification_rules - notification_log (+ ENUM notification_status) """ from alembic import op revision = "0003" down_revision = "0002" branch_labels = None depends_on = None def upgrade() -> None: # ── ENUM types ──────────────────────────────────────────────────────────── op.execute( "CREATE TYPE notification_channel_type AS ENUM " "('webhook', 'email', 'telegram', 'whatsapp')" ) op.execute( "CREATE TYPE notification_status AS ENUM " "('pending', 'sent', 'failed', 'skipped')" ) # ── VIRTUAL BOXES ───────────────────────────────────────────────────────── op.execute(""" CREATE TABLE virtual_boxes ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, description TEXT, label VARCHAR(100), is_active BOOLEAN NOT NULL DEFAULT TRUE, created_by UUID REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_vbox_name_tenant UNIQUE (tenant_id, name) ) """) op.execute("CREATE INDEX idx_vbox_tenant ON virtual_boxes (tenant_id)") op.execute(""" CREATE TRIGGER trg_virtual_boxes_updated_at BEFORE UPDATE ON virtual_boxes FOR EACH ROW EXECUTE FUNCTION set_updated_at() """) op.execute(""" CREATE TABLE virtual_box_rules ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), virtual_box_id UUID NOT NULL REFERENCES virtual_boxes(id) ON DELETE CASCADE, field VARCHAR(50) NOT NULL, operator VARCHAR(20) NOT NULL DEFAULT 'contains', value TEXT NOT NULL, date_from VARCHAR(20), date_to VARCHAR(20), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) """) op.execute("CREATE INDEX idx_vbox_rule_vbox ON virtual_box_rules (virtual_box_id)") op.execute(""" CREATE TABLE virtual_box_assignments ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), virtual_box_id UUID NOT NULL REFERENCES virtual_boxes(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, assigned_by UUID REFERENCES users(id), assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_vbox_assignment UNIQUE (virtual_box_id, user_id) ) """) op.execute("CREATE INDEX idx_vbox_assign_user ON virtual_box_assignments (user_id)") op.execute("CREATE INDEX idx_vbox_assign_vbox ON virtual_box_assignments (virtual_box_id)") # ── NOTIFICATION CHANNELS ───────────────────────────────────────────────── op.execute(""" CREATE TABLE notification_channels ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, channel_type notification_channel_type NOT NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, config JSONB, config_enc TEXT, consecutive_failures INT NOT NULL DEFAULT 0, circuit_open_until TIMESTAMPTZ, created_by UUID REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) """) op.execute("CREATE INDEX idx_notif_channel_tenant ON notification_channels (tenant_id)") op.execute(""" CREATE TRIGGER trg_notification_channels_updated_at BEFORE UPDATE ON notification_channels FOR EACH ROW EXECUTE FUNCTION set_updated_at() """) op.execute(""" CREATE TABLE notification_rules ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, channel_id UUID NOT NULL REFERENCES notification_channels(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, event_type VARCHAR(100) NOT NULL, filter JSONB, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) """) op.execute("CREATE INDEX idx_notif_rule_tenant ON notification_rules (tenant_id)") op.execute("CREATE INDEX idx_notif_rule_channel ON notification_rules (channel_id)") op.execute("CREATE INDEX idx_notif_rule_event ON notification_rules (event_type)") op.execute(""" CREATE TABLE notification_log ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, channel_id UUID NOT NULL REFERENCES notification_channels(id) ON DELETE CASCADE, rule_id UUID REFERENCES notification_rules(id) ON DELETE SET NULL, event_type VARCHAR(100) NOT NULL, event_payload JSONB, status notification_status NOT NULL DEFAULT 'pending', attempt_count INT NOT NULL DEFAULT 0, max_attempts INT NOT NULL DEFAULT 3, next_retry_at TIMESTAMPTZ, last_error TEXT, http_status INT, sent_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) """) op.execute("CREATE INDEX idx_notif_log_tenant ON notification_log (tenant_id)") op.execute("CREATE INDEX idx_notif_log_channel ON notification_log (channel_id)") op.execute( "CREATE INDEX idx_notif_log_status ON notification_log (status, next_retry_at) " "WHERE status IN ('pending', 'failed')" ) def downgrade() -> None: for table in [ "notification_log", "notification_rules", "notification_channels", "virtual_box_assignments", "virtual_box_rules", "virtual_boxes", ]: op.execute(f"DROP TABLE IF EXISTS {table} CASCADE") op.execute("DROP TYPE IF EXISTS notification_status") op.execute("DROP TYPE IF EXISTS notification_channel_type")