1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use clap::Parser;
use dotenvy::dotenv;
use std::{error::Error, fmt::Display, process::exit};
use tracing_subscriber::{prelude::*, EnvFilter};

use sea_orm::{ConnectOptions, Database, DbConn};
use sea_orm_cli::{run_migrate_generate, run_migrate_init, MigrateSubcommands};

use super::MigratorTrait;

const MIGRATION_DIR: &str = "./";

pub async fn run_cli<M>(migrator: M)
where
    M: MigratorTrait,
{
    dotenv().ok();
    let cli = Cli::parse();

    let url = cli
        .database_url
        .expect("Environment variable 'DATABASE_URL' not set");
    let schema = cli.database_schema.unwrap_or_else(|| "public".to_owned());

    let connect_options = ConnectOptions::new(url)
        .set_schema_search_path(schema)
        .to_owned();
    let db = &Database::connect(connect_options)
        .await
        .expect("Fail to acquire database connection");

    run_migrate(migrator, db, cli.command, cli.verbose)
        .await
        .unwrap_or_else(handle_error);
}

pub async fn run_migrate<M>(
    _: M,
    db: &DbConn,
    command: Option<MigrateSubcommands>,
    verbose: bool,
) -> Result<(), Box<dyn Error>>
where
    M: MigratorTrait,
{
    let filter = match verbose {
        true => "debug",
        false => "sea_orm_migration=info",
    };

    let filter_layer = EnvFilter::try_new(filter).unwrap();

    if verbose {
        let fmt_layer = tracing_subscriber::fmt::layer();
        tracing_subscriber::registry()
            .with(filter_layer)
            .with(fmt_layer)
            .init()
    } else {
        let fmt_layer = tracing_subscriber::fmt::layer()
            .with_target(false)
            .with_level(false)
            .without_time();
        tracing_subscriber::registry()
            .with(filter_layer)
            .with(fmt_layer)
            .init()
    };

    match command {
        Some(MigrateSubcommands::Fresh) => M::fresh(db).await?,
        Some(MigrateSubcommands::Refresh) => M::refresh(db).await?,
        Some(MigrateSubcommands::Reset) => M::reset(db).await?,
        Some(MigrateSubcommands::Status) => M::status(db).await?,
        Some(MigrateSubcommands::Up { num }) => M::up(db, num).await?,
        Some(MigrateSubcommands::Down { num }) => M::down(db, Some(num)).await?,
        Some(MigrateSubcommands::Init) => run_migrate_init(MIGRATION_DIR)?,
        Some(MigrateSubcommands::Generate {
            migration_name,
            universal_time: _,
            local_time,
        }) => run_migrate_generate(MIGRATION_DIR, &migration_name, !local_time)?,
        _ => M::up(db, None).await?,
    };

    Ok(())
}

#[derive(Parser)]
#[clap(version)]
pub struct Cli {
    #[clap(action, short = 'v', long, global = true, help = "Show debug messages")]
    verbose: bool,

    #[clap(
        value_parser,
        global = true,
        short = 's',
        long,
        env = "DATABASE_SCHEMA",
        long_help = "Database schema\n \
                    - For MySQL and SQLite, this argument is ignored.\n \
                    - For PostgreSQL, this argument is optional with default value 'public'.\n"
    )]
    database_schema: Option<String>,

    #[clap(
        value_parser,
        global = true,
        short = 'u',
        long,
        env = "DATABASE_URL",
        help = "Database URL"
    )]
    database_url: Option<String>,

    #[clap(subcommand)]
    command: Option<MigrateSubcommands>,
}

fn handle_error<E>(error: E)
where
    E: Display,
{
    eprintln!("{error}");
    exit(1);
}