supabase-plus 0.7.2

An extra set of tools for managing Supabase projects going beyond the possibilities of regular Supabase CLI
use std::io::Stderr;

use anyhow::Context;
use promptuity::{
    Promptuity,
    prompts::{MultiSelect, MultiSelectOption},
};

use crate::abstraction::Migration;

pub struct RealtimeChange {
    schema: String,
    to_add: Vec<String>,
    to_remove: Vec<String>,
}

impl Migration for RealtimeChange {
    fn sql(&self) -> String {
        let mut lines = Vec::new();

        if !self.to_add.is_empty() {
            let line = format!(
                r#"alter publication supabase_realtime add table {};"#,
                self.to_add
                    .iter()
                    .map(|item| format!("{:?}.{item:?}", self.schema))
                    .collect::<Vec<String>>()
                    .join(", ")
            );

            lines.push(line);
        }

        if !self.to_remove.is_empty() {
            let line = format!(
                r#"alter publication supabase_realtime drop table {};"#,
                self.to_remove
                    .iter()
                    .map(|item| format!("{:?}.{item:?}", self.schema))
                    .collect::<Vec<String>>()
                    .join(", ")
            );

            lines.push(line);
        }

        lines.join("\n")
    }

    fn migration_name(&self) -> String {
        format!("change_realtime")
    }
}

impl RealtimeChange {
    pub fn new_interactively(
        promptuity: &mut Promptuity<'_, Stderr>,
        schema: &str,
        tables: Vec<String>,
        enabled_for: Vec<String>,
        project_id: &str,
    ) -> anyhow::Result<Self> {
        promptuity
            .with_intro(format!("Realtime ({})", project_id))
            .begin()
            .expect("Failed to start interactive mode");

        let matrix = promptuity
            .prompt(
                MultiSelect::new(
                    "Which tables do you want to have realtime enabled for?",
                    tables
                        .iter()
                        .map(|table| MultiSelectOption {
                            label: table.clone(),
                            value: table.clone(),
                            selected: enabled_for.contains(table),
                            hint: None,
                        })
                        .collect(),
                )
                .with_required(false)
                .with_hint("current state is reflected")
                .as_mut(),
            )
            .context("Stopped")?;

        let mut to_add = Vec::new();
        let mut to_remove = Vec::new();

        for item in &matrix {
            if !enabled_for.contains(&item) {
                to_add.push(item.clone());
            }
        }

        for item in enabled_for {
            if !matrix.contains(&item) {
                to_remove.push(item.clone());
            }
        }

        if to_add.is_empty() && to_remove.is_empty() {
            crate::styled_bail!("No changes to apply")
        }

        Ok(Self {
            schema: schema.to_string(),
            to_add,
            to_remove,
        })
    }
}