diff --git a/Cargo.lock b/Cargo.lock index a08b063c..46e036ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,6 +511,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.118", +] + [[package]] name = "bitflags" version = "2.13.0" @@ -596,6 +616,24 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +[[package]] +name = "cbindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" +dependencies = [ + "heck 0.4.1", + "indexmap 2.14.0", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.118", + "tempfile", + "toml", +] + [[package]] name = "cc" version = "1.2.65" @@ -605,7 +643,16 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", ] [[package]] @@ -655,6 +702,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.6.1" @@ -683,7 +741,7 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.118", @@ -1304,6 +1362,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -1726,6 +1795,12 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -2197,6 +2272,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + [[package]] name = "ip" version = "0.1.0" @@ -2388,6 +2469,16 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.16" @@ -2486,6 +2577,14 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "migrate" +version = "0.1.0" +dependencies = [ + "rust2go", + "tracing", +] + [[package]] name = "mime" version = "0.3.17" @@ -3148,6 +3247,7 @@ dependencies = [ "hex", "jwt", "md5 0.7.0", + "migrate", "oauth", "opentelemetry", "opentelemetry-otlp", @@ -3240,7 +3340,7 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ - "heck", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -3735,6 +3835,62 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust2go" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021695cb0d56dbef5f29b24d2dd9a23552464500574260a41c198fe17c78a3fe" +dependencies = [ + "bindgen", + "fs-set-times", + "rust2go-cli", + "rust2go-convert", + "rust2go-macro", + "syn 2.0.118", +] + +[[package]] +name = "rust2go-cli" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8490df1c618176bfd681c0e27b46fa98f42739c441c12a8f7bc85eb6550e54fe" +dependencies = [ + "cbindgen", + "clap", + "itertools 0.14.0", + "rust2go-common", +] + +[[package]] +name = "rust2go-common" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27eb858d3df960a07bc7f7e43f642d6a8368cc32110d7daacb6aa069eb7f9c5" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "rust2go-convert" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ee4d6efac91720550e564892b1fddfe103683f7f0b19196f59dae1621e6e83" + +[[package]] +name = "rust2go-macro" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a694d2aa8c7265126b0f3c712bd8411e5415d5e5f906fa2996a10beb81b5efca" +dependencies = [ + "proc-macro2", + "quote", + "rust2go-common", + "syn 2.0.118", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -4027,6 +4183,15 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4143,6 +4308,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" @@ -4382,7 +4553,7 @@ dependencies = [ "cfg-if", "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "proc-macro2", "quote", @@ -4777,6 +4948,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tonic" version = "0.12.3" diff --git a/Cargo.toml b/Cargo.toml index c07eecc3..ed9c2cd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,3 +46,4 @@ sms = { path = "crates/sms" } email = { path = "crates/email" } turnstile = { path = "crates/turnstile" } reqwest = { version = "0.12", features = ["json"] } +migrate = { path = "crates/migrate" } diff --git a/crates/migrate/Cargo.toml b/crates/migrate/Cargo.toml new file mode 100644 index 00000000..b4164787 --- /dev/null +++ b/crates/migrate/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "migrate" +version = "0.1.0" +edition = "2021" + +[lib] +# The name of the lib target. We expose `ppanel_migrate` so the main +# binary can `use ppanel_migrate::*`. The actual library name in +# Cargo's eyes is "migrate" (matches [package].name), but the +# `crate-type = ["staticlib"]` is what rust2go::Builder compiles the +# Go code into for FFI. +crate-type = ["staticlib", "rlib"] + +[features] +# Default: build with the FFI bridge compiled in. The Go side is compiled +# by build.rs via rust2go::Builder. +default = [] + +[dependencies] +rust2go = "0.4" +tracing = "0.1" + +[build-dependencies] +rust2go = { version = "0.4", features = ["build"] } diff --git a/crates/migrate/build.rs b/crates/migrate/build.rs new file mode 100644 index 00000000..4f635492 --- /dev/null +++ b/crates/migrate/build.rs @@ -0,0 +1,19 @@ +// rust2go build script. +// +// Wires up the Go source tree (./go/r2g) into the Rust build: +// - Embeds the Go source path so the Builder can compile it via CGO. +// - Wires `src/idl.rs` (Rust IDL) → `./go/r2g/gen.go` +// (auto-generated Go glue). +// +// On every `cargo build` the Builder regenerates gen.go if the IDL +// changed, then compiles the Go code into a static library that the +// Rust crate is linked against. +fn main() { + rust2go::Builder::new() + .with_go_src("./go/r2g") + // gen.go is written relative to the build script's CWD (the + // crate root), so we spell out the path under go/r2g/ where + // the Go package lives. + .with_regen("src/idl.rs", "go/r2g/gen.go") + .build(); +} diff --git a/crates/migrate/examples/test_migrate.rs b/crates/migrate/examples/test_migrate.rs new file mode 100644 index 00000000..bff84930 --- /dev/null +++ b/crates/migrate/examples/test_migrate.rs @@ -0,0 +1,98 @@ +//! End-to-end test for the rust2go FFI migration path. +//! +//! Usage: +//! GODEBUG=invalidptr=0,cgocheck=0 cargo run --example test_migrate -- \ +//! postgres://user:pass@host:5432/dbname?sslmode=disable +//! +//! Resets the database, runs `migrate::up()` via the FFI bridge, and +//! verifies the resulting schema. Exits with non-zero status on any +//! failure. + +use std::env; + +fn main() { + let dsn = env::args() + .nth(1) + .expect("usage: test_migrate "); + + // Make sure the schema is empty so we can verify migration works + // from a fresh state. We do this with a side connection in pure + // SQL — we don't want to import the sqlx stack just for the test. + // The drop must run as a superuser since `ppanel_test` does not + // own the public schema. + println!("== dropping schema public (as postgres superuser) =="); + let admin_dsn = env::var("PPANEL_ADMIN_DSN") + .unwrap_or_else(|_| "postgres://postgres@localhost:5432/postgres".to_string()); + let drop_sql = format!( + "DROP SCHEMA public CASCADE; CREATE SCHEMA public; \ + GRANT ALL ON SCHEMA public TO public; \ + ALTER DATABASE ppanel_rust2go_test OWNER TO ppanel_test;", + ); + exec_via_psql(&admin_dsn, &drop_sql); + + println!("== calling migrate::up() via FFI =="); + let outcome = migrate::up("postgres", &dsn); + println!( + " -> version={}, dirty={}, error={:?}", + outcome.version, outcome.dirty, outcome.error + ); + if !outcome.error.is_empty() { + eprintln!("MIGRATION FAILED: {}", outcome.error); + std::process::exit(1); + } + + println!("== verifying schema =="); + let count_sql = "SELECT count(*) FROM information_schema.tables \ + WHERE table_schema = 'public' AND table_type = 'BASE TABLE';"; + let n: u32 = query_via_psql(&dsn, count_sql) + .parse() + .expect("count should parse as u32"); + println!(" -> {} public tables", n); + if n < 20 { + eprintln!("expected at least 20 tables, got {}", n); + std::process::exit(1); + } + + println!("== verifying schema_migrations state =="); + let ver_sql = "SELECT version::text || '|' || dirty::text FROM schema_migrations;"; + let state = query_via_psql(&dsn, ver_sql); + println!(" -> version,dirty = {}", state); + if !state.starts_with("2131|") { + eprintln!("expected version 2131, got {}", state); + std::process::exit(1); + } + + println!("== testing idempotency: re-run up() =="); + let outcome2 = migrate::up("postgres", &dsn); + println!( + " -> version={}, dirty={}, error={:?}", + outcome2.version, outcome2.dirty, outcome2.error + ); + if !outcome2.error.is_empty() { + eprintln!("IDEMPOTENT RUN FAILED: {}", outcome2.error); + std::process::exit(1); + } + + println!("\n✅ ALL CHECKS PASSED"); +} + +/// Run a SQL statement via `psql` and return its trimmed stdout. +fn exec_via_psql(dsn: &str, sql: &str) -> String { + let out = std::process::Command::new("psql") + .arg(dsn) + .arg("-c") + .arg(sql) + .arg("-A") + .arg("-t") + .output() + .expect("failed to invoke psql (is it installed and on PATH?)"); + if !out.status.success() { + let stderr = String::from_utf8_lossy(&out.stderr); + panic!("psql failed: {}\n{}", out.status, stderr); + } + String::from_utf8_lossy(&out.stdout).trim().to_string() +} + +fn query_via_psql(dsn: &str, sql: &str) -> String { + exec_via_psql(dsn, sql) +} diff --git a/tools/migrate/cmd/migrate/main.go b/crates/migrate/go/cmd/migrate/main.go similarity index 96% rename from tools/migrate/cmd/migrate/main.go rename to crates/migrate/go/cmd/migrate/main.go index 25857e4d..9e3491e8 100644 --- a/tools/migrate/cmd/migrate/main.go +++ b/crates/migrate/go/cmd/migrate/main.go @@ -22,7 +22,7 @@ import ( gomigrate "github.com/golang-migrate/migrate/v4" - ppmigrate "github.com/perfect-panel/ppanel-backend/tools/migrate/migrate" + ppmigrate "github.com/perfect-panel/ppanel-backend/crates/migrate/go/migrate" ) func main() { @@ -53,7 +53,7 @@ func main() { } cmd := flag.Arg(0) - sess := ppmigrate.Migrate(*driver, *dsn) + sess := ppmigrate.MigrateOrPanic(*driver, *dsn) m := sess.Migrate switch cmd { diff --git a/crates/migrate/go/go.mod b/crates/migrate/go/go.mod new file mode 100644 index 00000000..5590e602 --- /dev/null +++ b/crates/migrate/go/go.mod @@ -0,0 +1,16 @@ +module github.com/perfect-panel/ppanel-backend/crates/migrate/go + +go 1.25.0 + +require ( + github.com/golang-migrate/migrate/v4 v4.19.1 + // rust2go has no tagged releases; pin to the latest commit on master. + // Bump by re-running `go get github.com/ihciah/rust2go@master`. + github.com/ihciah/rust2go v0.0.0-20260706074211-1c851bd436aa +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/lib/pq v1.10.9 // indirect +) diff --git a/tools/migrate/go.sum b/crates/migrate/go/go.sum similarity index 96% rename from tools/migrate/go.sum rename to crates/migrate/go/go.sum index ee74e716..4fb2e343 100644 --- a/tools/migrate/go.sum +++ b/crates/migrate/go/go.sum @@ -32,6 +32,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= +github.com/ihciah/rust2go v0.0.0-20260706074211-1c851bd436aa h1:u8Mzn+6v64BdSV6ry7OGXt2lbSjHQh4711+0whU/sYE= +github.com/ihciah/rust2go v0.0.0-20260706074211-1c851bd436aa/go.mod h1:SpcZZoVYDXYhx373JZ1onR8dnEcalKA0H61x/FJ9O0s= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= diff --git a/tools/migrate/migrate/migrate.go b/crates/migrate/go/migrate/migrate.go similarity index 80% rename from tools/migrate/migrate/migrate.go rename to crates/migrate/go/migrate/migrate.go index fd86d066..6321cab8 100644 --- a/tools/migrate/migrate/migrate.go +++ b/crates/migrate/go/migrate/migrate.go @@ -23,6 +23,10 @@ var sqlFiles embed.FS // NoChange is re-exported so callers don't have to import golang-migrate directly. var NoChange = migrate.ErrNoChange +// ErrNilVersion is re-exported for the same reason — it is the sentinel +// returned by Version() when the schema_migrations table is empty. +var ErrNilVersion = migrate.ErrNilVersion + // Session bundles a configured golang-migrate instance alongside the parsed // source-version list so callers can introspect what migrations exist. type Session struct { @@ -37,7 +41,10 @@ type Session struct { // dsn: golang-migrate URL (e.g. postgres://user:pass@host:port/db?sslmode=disable). // // If dsn does not include a URL scheme, the driver is prepended automatically. -func Migrate(driver, dsn string) *Session { +// +// Returns an error (no panics) so the function is safe to call across an FFI +// boundary. +func Migrate(driver, dsn string) (*Session, error) { sourcePath := "sql/postgres" databaseURL := dsn switch driver { @@ -47,20 +54,30 @@ func Migrate(driver, dsn string) *Session { case "postgres": databaseURL = ensureScheme("postgres://", dsn) default: - panic(fmt.Errorf("[Migrate] unsupported database driver: %s", driver)) + return nil, fmt.Errorf("[Migrate] unsupported database driver: %s", driver) } d, err := iofs.New(sqlFiles, sourcePath) if err != nil { - panic(fmt.Errorf("[Migrate] iofs.New error: %v", err)) + return nil, fmt.Errorf("[Migrate] iofs.New error: %v", err) } client, err := migrate.NewWithSourceInstance("iofs", d, databaseURL) if err != nil { - panic(fmt.Errorf("[Migrate] NewWithSourceInstance error: %v", err)) + return nil, fmt.Errorf("[Migrate] NewWithSourceInstance error: %v", err) } return &Session{ Migrate: client, Versions: scanVersions(sourcePath), + }, nil +} + +// MigrateOrPanic is a panic-on-error variant retained for the standalone CLI +// (`cmd/migrate/main.go`). Production code (FFI callers) must use Migrate. +func MigrateOrPanic(driver, dsn string) *Session { + s, err := Migrate(driver, dsn) + if err != nil { + panic(err) } + return s } // sourceVersionRe matches migration filenames like "02131_xxx.up.sql" / ".down.sql". @@ -130,4 +147,4 @@ func ensureScheme(scheme, dsn string) string { // keep imports referenced (io/fs and os are used elsewhere via errors.Is) var _ = fs.ErrNotExist -var _ = os.ErrNotExist \ No newline at end of file +var _ = os.ErrNotExist diff --git a/tools/migrate/migrate/sql/mysql/00001_init_schema.down.sql b/crates/migrate/go/migrate/sql/mysql/00001_init_schema.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/00001_init_schema.down.sql rename to crates/migrate/go/migrate/sql/mysql/00001_init_schema.down.sql diff --git a/tools/migrate/migrate/sql/mysql/00001_init_schema.up.sql b/crates/migrate/go/migrate/sql/mysql/00001_init_schema.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/00001_init_schema.up.sql rename to crates/migrate/go/migrate/sql/mysql/00001_init_schema.up.sql diff --git a/tools/migrate/migrate/sql/mysql/00002_init_basic_data.down.sql b/crates/migrate/go/migrate/sql/mysql/00002_init_basic_data.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/00002_init_basic_data.down.sql rename to crates/migrate/go/migrate/sql/mysql/00002_init_basic_data.down.sql diff --git a/tools/migrate/migrate/sql/mysql/00002_init_basic_data.up.sql b/crates/migrate/go/migrate/sql/mysql/00002_init_basic_data.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/00002_init_basic_data.up.sql rename to crates/migrate/go/migrate/sql/mysql/00002_init_basic_data.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02003_update_payment.down.sql b/crates/migrate/go/migrate/sql/mysql/02003_update_payment.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02003_update_payment.down.sql rename to crates/migrate/go/migrate/sql/mysql/02003_update_payment.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02003_update_payment.up.sql b/crates/migrate/go/migrate/sql/mysql/02003_update_payment.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02003_update_payment.up.sql rename to crates/migrate/go/migrate/sql/mysql/02003_update_payment.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02004_rebuild_rule.down.sql b/crates/migrate/go/migrate/sql/mysql/02004_rebuild_rule.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02004_rebuild_rule.down.sql rename to crates/migrate/go/migrate/sql/mysql/02004_rebuild_rule.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02004_rebuild_rule.up.sql b/crates/migrate/go/migrate/sql/mysql/02004_rebuild_rule.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02004_rebuild_rule.up.sql rename to crates/migrate/go/migrate/sql/mysql/02004_rebuild_rule.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02005_device_online_record.down.sql b/crates/migrate/go/migrate/sql/mysql/02005_device_online_record.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02005_device_online_record.down.sql rename to crates/migrate/go/migrate/sql/mysql/02005_device_online_record.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02005_device_online_record.up.sql b/crates/migrate/go/migrate/sql/mysql/02005_device_online_record.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02005_device_online_record.up.sql rename to crates/migrate/go/migrate/sql/mysql/02005_device_online_record.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.down.sql b/crates/migrate/go/migrate/sql/mysql/02006_reset_subscribe_record.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.down.sql rename to crates/migrate/go/migrate/sql/mysql/02006_reset_subscribe_record.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.up.sql b/crates/migrate/go/migrate/sql/mysql/02006_reset_subscribe_record.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.up.sql rename to crates/migrate/go/migrate/sql/mysql/02006_reset_subscribe_record.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02007_adapte_rule.down.sql b/crates/migrate/go/migrate/sql/mysql/02007_adapte_rule.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02007_adapte_rule.down.sql rename to crates/migrate/go/migrate/sql/mysql/02007_adapte_rule.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02007_adapte_rule.up.sql b/crates/migrate/go/migrate/sql/mysql/02007_adapte_rule.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02007_adapte_rule.up.sql rename to crates/migrate/go/migrate/sql/mysql/02007_adapte_rule.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02100_task.down.sql b/crates/migrate/go/migrate/sql/mysql/02100_task.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02100_task.down.sql rename to crates/migrate/go/migrate/sql/mysql/02100_task.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02100_task.up.sql b/crates/migrate/go/migrate/sql/mysql/02100_task.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02100_task.up.sql rename to crates/migrate/go/migrate/sql/mysql/02100_task.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02101_subscribe_application.down.sql b/crates/migrate/go/migrate/sql/mysql/02101_subscribe_application.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02101_subscribe_application.down.sql rename to crates/migrate/go/migrate/sql/mysql/02101_subscribe_application.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02101_subscribe_application.up.sql b/crates/migrate/go/migrate/sql/mysql/02101_subscribe_application.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02101_subscribe_application.up.sql rename to crates/migrate/go/migrate/sql/mysql/02101_subscribe_application.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02102_subscribe_config.down.sql b/crates/migrate/go/migrate/sql/mysql/02102_subscribe_config.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02102_subscribe_config.down.sql rename to crates/migrate/go/migrate/sql/mysql/02102_subscribe_config.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02102_subscribe_config.up.sql b/crates/migrate/go/migrate/sql/mysql/02102_subscribe_config.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02102_subscribe_config.up.sql rename to crates/migrate/go/migrate/sql/mysql/02102_subscribe_config.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02103_delete_application.down.sql b/crates/migrate/go/migrate/sql/mysql/02103_delete_application.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02103_delete_application.down.sql rename to crates/migrate/go/migrate/sql/mysql/02103_delete_application.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02103_delete_application.up.sql b/crates/migrate/go/migrate/sql/mysql/02103_delete_application.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02103_delete_application.up.sql rename to crates/migrate/go/migrate/sql/mysql/02103_delete_application.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02104_system_log.down.sql b/crates/migrate/go/migrate/sql/mysql/02104_system_log.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02104_system_log.down.sql rename to crates/migrate/go/migrate/sql/mysql/02104_system_log.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02104_system_log.up.sql b/crates/migrate/go/migrate/sql/mysql/02104_system_log.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02104_system_log.up.sql rename to crates/migrate/go/migrate/sql/mysql/02104_system_log.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02105_node.down.sql b/crates/migrate/go/migrate/sql/mysql/02105_node.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02105_node.down.sql rename to crates/migrate/go/migrate/sql/mysql/02105_node.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02105_node.up.sql b/crates/migrate/go/migrate/sql/mysql/02105_node.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02105_node.up.sql rename to crates/migrate/go/migrate/sql/mysql/02105_node.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02106_subscribe.down.sql b/crates/migrate/go/migrate/sql/mysql/02106_subscribe.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02106_subscribe.down.sql rename to crates/migrate/go/migrate/sql/mysql/02106_subscribe.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02106_subscribe.up.sql b/crates/migrate/go/migrate/sql/mysql/02106_subscribe.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02106_subscribe.up.sql rename to crates/migrate/go/migrate/sql/mysql/02106_subscribe.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02107_log_setting.down.sql b/crates/migrate/go/migrate/sql/mysql/02107_log_setting.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02107_log_setting.down.sql rename to crates/migrate/go/migrate/sql/mysql/02107_log_setting.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02107_log_setting.up.sql b/crates/migrate/go/migrate/sql/mysql/02107_log_setting.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02107_log_setting.up.sql rename to crates/migrate/go/migrate/sql/mysql/02107_log_setting.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02108_user_referral.down.sql b/crates/migrate/go/migrate/sql/mysql/02108_user_referral.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02108_user_referral.down.sql rename to crates/migrate/go/migrate/sql/mysql/02108_user_referral.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02108_user_referral.up.sql b/crates/migrate/go/migrate/sql/mysql/02108_user_referral.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02108_user_referral.up.sql rename to crates/migrate/go/migrate/sql/mysql/02108_user_referral.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02109_node_sort.down.sql b/crates/migrate/go/migrate/sql/mysql/02109_node_sort.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02109_node_sort.down.sql rename to crates/migrate/go/migrate/sql/mysql/02109_node_sort.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02109_node_sort.up.sql b/crates/migrate/go/migrate/sql/mysql/02109_node_sort.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02109_node_sort.up.sql rename to crates/migrate/go/migrate/sql/mysql/02109_node_sort.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02110_traffic_log_index.down.sql b/crates/migrate/go/migrate/sql/mysql/02110_traffic_log_index.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02110_traffic_log_index.down.sql rename to crates/migrate/go/migrate/sql/mysql/02110_traffic_log_index.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02110_traffic_log_index.up.sql b/crates/migrate/go/migrate/sql/mysql/02110_traffic_log_index.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02110_traffic_log_index.up.sql rename to crates/migrate/go/migrate/sql/mysql/02110_traffic_log_index.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02111_clear_table.down.sql b/crates/migrate/go/migrate/sql/mysql/02111_clear_table.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02111_clear_table.down.sql rename to crates/migrate/go/migrate/sql/mysql/02111_clear_table.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02111_clear_table.up.sql b/crates/migrate/go/migrate/sql/mysql/02111_clear_table.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02111_clear_table.up.sql rename to crates/migrate/go/migrate/sql/mysql/02111_clear_table.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02112_subscribe.down.sql b/crates/migrate/go/migrate/sql/mysql/02112_subscribe.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02112_subscribe.down.sql rename to crates/migrate/go/migrate/sql/mysql/02112_subscribe.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02112_subscribe.up.sql b/crates/migrate/go/migrate/sql/mysql/02112_subscribe.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02112_subscribe.up.sql rename to crates/migrate/go/migrate/sql/mysql/02112_subscribe.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02113_task.down.sql b/crates/migrate/go/migrate/sql/mysql/02113_task.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02113_task.down.sql rename to crates/migrate/go/migrate/sql/mysql/02113_task.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02113_task.up.sql b/crates/migrate/go/migrate/sql/mysql/02113_task.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02113_task.up.sql rename to crates/migrate/go/migrate/sql/mysql/02113_task.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02114_node_config.down.sql b/crates/migrate/go/migrate/sql/mysql/02114_node_config.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02114_node_config.down.sql rename to crates/migrate/go/migrate/sql/mysql/02114_node_config.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02114_node_config.up.sql b/crates/migrate/go/migrate/sql/mysql/02114_node_config.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02114_node_config.up.sql rename to crates/migrate/go/migrate/sql/mysql/02114_node_config.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02115_ads.down.sql b/crates/migrate/go/migrate/sql/mysql/02115_ads.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02115_ads.down.sql rename to crates/migrate/go/migrate/sql/mysql/02115_ads.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02115_ads.up.sql b/crates/migrate/go/migrate/sql/mysql/02115_ads.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02115_ads.up.sql rename to crates/migrate/go/migrate/sql/mysql/02115_ads.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02116_user_algo.down.sql b/crates/migrate/go/migrate/sql/mysql/02116_user_algo.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02116_user_algo.down.sql rename to crates/migrate/go/migrate/sql/mysql/02116_user_algo.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02116_user_algo.up.sql b/crates/migrate/go/migrate/sql/mysql/02116_user_algo.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02116_user_algo.up.sql rename to crates/migrate/go/migrate/sql/mysql/02116_user_algo.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02117_site_custom_data.down.sql b/crates/migrate/go/migrate/sql/mysql/02117_site_custom_data.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02117_site_custom_data.down.sql rename to crates/migrate/go/migrate/sql/mysql/02117_site_custom_data.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02117_site_custom_data.up.sql b/crates/migrate/go/migrate/sql/mysql/02117_site_custom_data.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02117_site_custom_data.up.sql rename to crates/migrate/go/migrate/sql/mysql/02117_site_custom_data.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.down.sql b/crates/migrate/go/migrate/sql/mysql/02118_traffic_log_idx.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.down.sql rename to crates/migrate/go/migrate/sql/mysql/02118_traffic_log_idx.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.up.sql b/crates/migrate/go/migrate/sql/mysql/02118_traffic_log_idx.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.up.sql rename to crates/migrate/go/migrate/sql/mysql/02118_traffic_log_idx.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.down.sql b/crates/migrate/go/migrate/sql/mysql/02119_user_subscribe_note.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.down.sql rename to crates/migrate/go/migrate/sql/mysql/02119_user_subscribe_note.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.up.sql b/crates/migrate/go/migrate/sql/mysql/02119_user_subscribe_note.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.up.sql rename to crates/migrate/go/migrate/sql/mysql/02119_user_subscribe_note.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02120_user_rules.down.sql b/crates/migrate/go/migrate/sql/mysql/02120_user_rules.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02120_user_rules.down.sql rename to crates/migrate/go/migrate/sql/mysql/02120_user_rules.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02120_user_rules.up.sql b/crates/migrate/go/migrate/sql/mysql/02120_user_rules.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02120_user_rules.up.sql rename to crates/migrate/go/migrate/sql/mysql/02120_user_rules.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02121_user_withdrawal.down.sql b/crates/migrate/go/migrate/sql/mysql/02121_user_withdrawal.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02121_user_withdrawal.down.sql rename to crates/migrate/go/migrate/sql/mysql/02121_user_withdrawal.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02121_user_withdrawal.up.sql b/crates/migrate/go/migrate/sql/mysql/02121_user_withdrawal.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02121_user_withdrawal.up.sql rename to crates/migrate/go/migrate/sql/mysql/02121_user_withdrawal.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02122_server.down.sql b/crates/migrate/go/migrate/sql/mysql/02122_server.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02122_server.down.sql rename to crates/migrate/go/migrate/sql/mysql/02122_server.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02122_server.up.sql b/crates/migrate/go/migrate/sql/mysql/02122_server.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02122_server.up.sql rename to crates/migrate/go/migrate/sql/mysql/02122_server.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02123_subscribe_original.down.sql b/crates/migrate/go/migrate/sql/mysql/02123_subscribe_original.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02123_subscribe_original.down.sql rename to crates/migrate/go/migrate/sql/mysql/02123_subscribe_original.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02123_subscribe_original.up.sql b/crates/migrate/go/migrate/sql/mysql/02123_subscribe_original.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02123_subscribe_original.up.sql rename to crates/migrate/go/migrate/sql/mysql/02123_subscribe_original.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02124_server_group_delete.down.sql b/crates/migrate/go/migrate/sql/mysql/02124_server_group_delete.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02124_server_group_delete.down.sql rename to crates/migrate/go/migrate/sql/mysql/02124_server_group_delete.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02124_server_group_delete.up.sql b/crates/migrate/go/migrate/sql/mysql/02124_server_group_delete.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02124_server_group_delete.up.sql rename to crates/migrate/go/migrate/sql/mysql/02124_server_group_delete.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02125_subscribe_stock.down.sql b/crates/migrate/go/migrate/sql/mysql/02125_subscribe_stock.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02125_subscribe_stock.down.sql rename to crates/migrate/go/migrate/sql/mysql/02125_subscribe_stock.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02125_subscribe_stock.up.sql b/crates/migrate/go/migrate/sql/mysql/02125_subscribe_stock.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02125_subscribe_stock.up.sql rename to crates/migrate/go/migrate/sql/mysql/02125_subscribe_stock.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02126_system_log_idx.down.sql b/crates/migrate/go/migrate/sql/mysql/02126_system_log_idx.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02126_system_log_idx.down.sql rename to crates/migrate/go/migrate/sql/mysql/02126_system_log_idx.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02126_system_log_idx.up.sql b/crates/migrate/go/migrate/sql/mysql/02126_system_log_idx.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02126_system_log_idx.up.sql rename to crates/migrate/go/migrate/sql/mysql/02126_system_log_idx.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02127_search_indexes.down.sql b/crates/migrate/go/migrate/sql/mysql/02127_search_indexes.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02127_search_indexes.down.sql rename to crates/migrate/go/migrate/sql/mysql/02127_search_indexes.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02127_search_indexes.up.sql b/crates/migrate/go/migrate/sql/mysql/02127_search_indexes.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02127_search_indexes.up.sql rename to crates/migrate/go/migrate/sql/mysql/02127_search_indexes.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02128_server_config_override.down.sql b/crates/migrate/go/migrate/sql/mysql/02128_server_config_override.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02128_server_config_override.down.sql rename to crates/migrate/go/migrate/sql/mysql/02128_server_config_override.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02128_server_config_override.up.sql b/crates/migrate/go/migrate/sql/mysql/02128_server_config_override.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02128_server_config_override.up.sql rename to crates/migrate/go/migrate/sql/mysql/02128_server_config_override.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02129_payment_sort.down.sql b/crates/migrate/go/migrate/sql/mysql/02129_payment_sort.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02129_payment_sort.down.sql rename to crates/migrate/go/migrate/sql/mysql/02129_payment_sort.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02129_payment_sort.up.sql b/crates/migrate/go/migrate/sql/mysql/02129_payment_sort.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02129_payment_sort.up.sql rename to crates/migrate/go/migrate/sql/mysql/02129_payment_sort.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.down.sql b/crates/migrate/go/migrate/sql/mysql/02130_subscribe_tutorial.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.down.sql rename to crates/migrate/go/migrate/sql/mysql/02130_subscribe_tutorial.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.up.sql b/crates/migrate/go/migrate/sql/mysql/02130_subscribe_tutorial.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.up.sql rename to crates/migrate/go/migrate/sql/mysql/02130_subscribe_tutorial.up.sql diff --git a/tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.down.sql b/crates/migrate/go/migrate/sql/mysql/02131_timestamptz_last_reported_at.down.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.down.sql rename to crates/migrate/go/migrate/sql/mysql/02131_timestamptz_last_reported_at.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.up.sql b/crates/migrate/go/migrate/sql/mysql/02131_timestamptz_last_reported_at.up.sql similarity index 100% rename from tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.up.sql rename to crates/migrate/go/migrate/sql/mysql/02131_timestamptz_last_reported_at.up.sql diff --git a/tools/migrate/migrate/sql/postgres/00001_init_schema.down.sql b/crates/migrate/go/migrate/sql/postgres/00001_init_schema.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/00001_init_schema.down.sql rename to crates/migrate/go/migrate/sql/postgres/00001_init_schema.down.sql diff --git a/tools/migrate/migrate/sql/postgres/00001_init_schema.up.sql b/crates/migrate/go/migrate/sql/postgres/00001_init_schema.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/00001_init_schema.up.sql rename to crates/migrate/go/migrate/sql/postgres/00001_init_schema.up.sql diff --git a/tools/migrate/migrate/sql/postgres/00002_init_basic_data.down.sql b/crates/migrate/go/migrate/sql/postgres/00002_init_basic_data.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/00002_init_basic_data.down.sql rename to crates/migrate/go/migrate/sql/postgres/00002_init_basic_data.down.sql diff --git a/tools/migrate/migrate/sql/postgres/00002_init_basic_data.up.sql b/crates/migrate/go/migrate/sql/postgres/00002_init_basic_data.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/00002_init_basic_data.up.sql rename to crates/migrate/go/migrate/sql/postgres/00002_init_basic_data.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02003_update_payment.down.sql b/crates/migrate/go/migrate/sql/postgres/02003_update_payment.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02003_update_payment.down.sql rename to crates/migrate/go/migrate/sql/postgres/02003_update_payment.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02003_update_payment.up.sql b/crates/migrate/go/migrate/sql/postgres/02003_update_payment.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02003_update_payment.up.sql rename to crates/migrate/go/migrate/sql/postgres/02003_update_payment.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02004_rebuild_rule.down.sql b/crates/migrate/go/migrate/sql/postgres/02004_rebuild_rule.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02004_rebuild_rule.down.sql rename to crates/migrate/go/migrate/sql/postgres/02004_rebuild_rule.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02004_rebuild_rule.up.sql b/crates/migrate/go/migrate/sql/postgres/02004_rebuild_rule.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02004_rebuild_rule.up.sql rename to crates/migrate/go/migrate/sql/postgres/02004_rebuild_rule.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02005_device_online_record.down.sql b/crates/migrate/go/migrate/sql/postgres/02005_device_online_record.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02005_device_online_record.down.sql rename to crates/migrate/go/migrate/sql/postgres/02005_device_online_record.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02005_device_online_record.up.sql b/crates/migrate/go/migrate/sql/postgres/02005_device_online_record.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02005_device_online_record.up.sql rename to crates/migrate/go/migrate/sql/postgres/02005_device_online_record.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.down.sql b/crates/migrate/go/migrate/sql/postgres/02006_reset_subscribe_record.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.down.sql rename to crates/migrate/go/migrate/sql/postgres/02006_reset_subscribe_record.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.up.sql b/crates/migrate/go/migrate/sql/postgres/02006_reset_subscribe_record.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.up.sql rename to crates/migrate/go/migrate/sql/postgres/02006_reset_subscribe_record.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02007_adapte_rule.down.sql b/crates/migrate/go/migrate/sql/postgres/02007_adapte_rule.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02007_adapte_rule.down.sql rename to crates/migrate/go/migrate/sql/postgres/02007_adapte_rule.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02007_adapte_rule.up.sql b/crates/migrate/go/migrate/sql/postgres/02007_adapte_rule.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02007_adapte_rule.up.sql rename to crates/migrate/go/migrate/sql/postgres/02007_adapte_rule.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02100_task.down.sql b/crates/migrate/go/migrate/sql/postgres/02100_task.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02100_task.down.sql rename to crates/migrate/go/migrate/sql/postgres/02100_task.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02100_task.up.sql b/crates/migrate/go/migrate/sql/postgres/02100_task.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02100_task.up.sql rename to crates/migrate/go/migrate/sql/postgres/02100_task.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02101_subscribe_application.down.sql b/crates/migrate/go/migrate/sql/postgres/02101_subscribe_application.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02101_subscribe_application.down.sql rename to crates/migrate/go/migrate/sql/postgres/02101_subscribe_application.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02101_subscribe_application.up.sql b/crates/migrate/go/migrate/sql/postgres/02101_subscribe_application.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02101_subscribe_application.up.sql rename to crates/migrate/go/migrate/sql/postgres/02101_subscribe_application.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02102_subscribe_config.down.sql b/crates/migrate/go/migrate/sql/postgres/02102_subscribe_config.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02102_subscribe_config.down.sql rename to crates/migrate/go/migrate/sql/postgres/02102_subscribe_config.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02102_subscribe_config.up.sql b/crates/migrate/go/migrate/sql/postgres/02102_subscribe_config.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02102_subscribe_config.up.sql rename to crates/migrate/go/migrate/sql/postgres/02102_subscribe_config.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02103_delete_application.down.sql b/crates/migrate/go/migrate/sql/postgres/02103_delete_application.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02103_delete_application.down.sql rename to crates/migrate/go/migrate/sql/postgres/02103_delete_application.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02103_delete_application.up.sql b/crates/migrate/go/migrate/sql/postgres/02103_delete_application.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02103_delete_application.up.sql rename to crates/migrate/go/migrate/sql/postgres/02103_delete_application.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02104_system_log.down.sql b/crates/migrate/go/migrate/sql/postgres/02104_system_log.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02104_system_log.down.sql rename to crates/migrate/go/migrate/sql/postgres/02104_system_log.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02104_system_log.up.sql b/crates/migrate/go/migrate/sql/postgres/02104_system_log.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02104_system_log.up.sql rename to crates/migrate/go/migrate/sql/postgres/02104_system_log.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02105_node.down.sql b/crates/migrate/go/migrate/sql/postgres/02105_node.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02105_node.down.sql rename to crates/migrate/go/migrate/sql/postgres/02105_node.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02105_node.up.sql b/crates/migrate/go/migrate/sql/postgres/02105_node.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02105_node.up.sql rename to crates/migrate/go/migrate/sql/postgres/02105_node.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02106_subscribe.down.sql b/crates/migrate/go/migrate/sql/postgres/02106_subscribe.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02106_subscribe.down.sql rename to crates/migrate/go/migrate/sql/postgres/02106_subscribe.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02106_subscribe.up.sql b/crates/migrate/go/migrate/sql/postgres/02106_subscribe.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02106_subscribe.up.sql rename to crates/migrate/go/migrate/sql/postgres/02106_subscribe.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02107_log_setting.down.sql b/crates/migrate/go/migrate/sql/postgres/02107_log_setting.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02107_log_setting.down.sql rename to crates/migrate/go/migrate/sql/postgres/02107_log_setting.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02107_log_setting.up.sql b/crates/migrate/go/migrate/sql/postgres/02107_log_setting.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02107_log_setting.up.sql rename to crates/migrate/go/migrate/sql/postgres/02107_log_setting.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02108_user_referral.down.sql b/crates/migrate/go/migrate/sql/postgres/02108_user_referral.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02108_user_referral.down.sql rename to crates/migrate/go/migrate/sql/postgres/02108_user_referral.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02108_user_referral.up.sql b/crates/migrate/go/migrate/sql/postgres/02108_user_referral.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02108_user_referral.up.sql rename to crates/migrate/go/migrate/sql/postgres/02108_user_referral.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02109_node_sort.down.sql b/crates/migrate/go/migrate/sql/postgres/02109_node_sort.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02109_node_sort.down.sql rename to crates/migrate/go/migrate/sql/postgres/02109_node_sort.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02109_node_sort.up.sql b/crates/migrate/go/migrate/sql/postgres/02109_node_sort.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02109_node_sort.up.sql rename to crates/migrate/go/migrate/sql/postgres/02109_node_sort.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02110_traffic_log_index.down.sql b/crates/migrate/go/migrate/sql/postgres/02110_traffic_log_index.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02110_traffic_log_index.down.sql rename to crates/migrate/go/migrate/sql/postgres/02110_traffic_log_index.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02110_traffic_log_index.up.sql b/crates/migrate/go/migrate/sql/postgres/02110_traffic_log_index.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02110_traffic_log_index.up.sql rename to crates/migrate/go/migrate/sql/postgres/02110_traffic_log_index.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02111_clear_table.down.sql b/crates/migrate/go/migrate/sql/postgres/02111_clear_table.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02111_clear_table.down.sql rename to crates/migrate/go/migrate/sql/postgres/02111_clear_table.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02111_clear_table.up.sql b/crates/migrate/go/migrate/sql/postgres/02111_clear_table.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02111_clear_table.up.sql rename to crates/migrate/go/migrate/sql/postgres/02111_clear_table.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02112_subscribe.down.sql b/crates/migrate/go/migrate/sql/postgres/02112_subscribe.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02112_subscribe.down.sql rename to crates/migrate/go/migrate/sql/postgres/02112_subscribe.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02112_subscribe.up.sql b/crates/migrate/go/migrate/sql/postgres/02112_subscribe.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02112_subscribe.up.sql rename to crates/migrate/go/migrate/sql/postgres/02112_subscribe.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02113_task.down.sql b/crates/migrate/go/migrate/sql/postgres/02113_task.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02113_task.down.sql rename to crates/migrate/go/migrate/sql/postgres/02113_task.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02113_task.up.sql b/crates/migrate/go/migrate/sql/postgres/02113_task.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02113_task.up.sql rename to crates/migrate/go/migrate/sql/postgres/02113_task.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02114_node_config.down.sql b/crates/migrate/go/migrate/sql/postgres/02114_node_config.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02114_node_config.down.sql rename to crates/migrate/go/migrate/sql/postgres/02114_node_config.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02114_node_config.up.sql b/crates/migrate/go/migrate/sql/postgres/02114_node_config.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02114_node_config.up.sql rename to crates/migrate/go/migrate/sql/postgres/02114_node_config.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02115_ads.down.sql b/crates/migrate/go/migrate/sql/postgres/02115_ads.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02115_ads.down.sql rename to crates/migrate/go/migrate/sql/postgres/02115_ads.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02115_ads.up.sql b/crates/migrate/go/migrate/sql/postgres/02115_ads.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02115_ads.up.sql rename to crates/migrate/go/migrate/sql/postgres/02115_ads.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02116_user_algo.down.sql b/crates/migrate/go/migrate/sql/postgres/02116_user_algo.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02116_user_algo.down.sql rename to crates/migrate/go/migrate/sql/postgres/02116_user_algo.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02116_user_algo.up.sql b/crates/migrate/go/migrate/sql/postgres/02116_user_algo.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02116_user_algo.up.sql rename to crates/migrate/go/migrate/sql/postgres/02116_user_algo.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02117_site_custom_data.down.sql b/crates/migrate/go/migrate/sql/postgres/02117_site_custom_data.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02117_site_custom_data.down.sql rename to crates/migrate/go/migrate/sql/postgres/02117_site_custom_data.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02117_site_custom_data.up.sql b/crates/migrate/go/migrate/sql/postgres/02117_site_custom_data.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02117_site_custom_data.up.sql rename to crates/migrate/go/migrate/sql/postgres/02117_site_custom_data.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.down.sql b/crates/migrate/go/migrate/sql/postgres/02118_traffic_log_idx.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.down.sql rename to crates/migrate/go/migrate/sql/postgres/02118_traffic_log_idx.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.up.sql b/crates/migrate/go/migrate/sql/postgres/02118_traffic_log_idx.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.up.sql rename to crates/migrate/go/migrate/sql/postgres/02118_traffic_log_idx.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.down.sql b/crates/migrate/go/migrate/sql/postgres/02119_user_subscribe_note.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.down.sql rename to crates/migrate/go/migrate/sql/postgres/02119_user_subscribe_note.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.up.sql b/crates/migrate/go/migrate/sql/postgres/02119_user_subscribe_note.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.up.sql rename to crates/migrate/go/migrate/sql/postgres/02119_user_subscribe_note.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02120_user_rules.down.sql b/crates/migrate/go/migrate/sql/postgres/02120_user_rules.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02120_user_rules.down.sql rename to crates/migrate/go/migrate/sql/postgres/02120_user_rules.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02120_user_rules.up.sql b/crates/migrate/go/migrate/sql/postgres/02120_user_rules.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02120_user_rules.up.sql rename to crates/migrate/go/migrate/sql/postgres/02120_user_rules.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02121_user_withdrawal.down.sql b/crates/migrate/go/migrate/sql/postgres/02121_user_withdrawal.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02121_user_withdrawal.down.sql rename to crates/migrate/go/migrate/sql/postgres/02121_user_withdrawal.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02121_user_withdrawal.up.sql b/crates/migrate/go/migrate/sql/postgres/02121_user_withdrawal.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02121_user_withdrawal.up.sql rename to crates/migrate/go/migrate/sql/postgres/02121_user_withdrawal.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02122_server.down.sql b/crates/migrate/go/migrate/sql/postgres/02122_server.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02122_server.down.sql rename to crates/migrate/go/migrate/sql/postgres/02122_server.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02122_server.up.sql b/crates/migrate/go/migrate/sql/postgres/02122_server.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02122_server.up.sql rename to crates/migrate/go/migrate/sql/postgres/02122_server.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02123_subscribe_original.down.sql b/crates/migrate/go/migrate/sql/postgres/02123_subscribe_original.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02123_subscribe_original.down.sql rename to crates/migrate/go/migrate/sql/postgres/02123_subscribe_original.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02123_subscribe_original.up.sql b/crates/migrate/go/migrate/sql/postgres/02123_subscribe_original.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02123_subscribe_original.up.sql rename to crates/migrate/go/migrate/sql/postgres/02123_subscribe_original.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02124_server_group_delete.down.sql b/crates/migrate/go/migrate/sql/postgres/02124_server_group_delete.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02124_server_group_delete.down.sql rename to crates/migrate/go/migrate/sql/postgres/02124_server_group_delete.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02124_server_group_delete.up.sql b/crates/migrate/go/migrate/sql/postgres/02124_server_group_delete.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02124_server_group_delete.up.sql rename to crates/migrate/go/migrate/sql/postgres/02124_server_group_delete.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02125_subscribe_stock.down.sql b/crates/migrate/go/migrate/sql/postgres/02125_subscribe_stock.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02125_subscribe_stock.down.sql rename to crates/migrate/go/migrate/sql/postgres/02125_subscribe_stock.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02125_subscribe_stock.up.sql b/crates/migrate/go/migrate/sql/postgres/02125_subscribe_stock.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02125_subscribe_stock.up.sql rename to crates/migrate/go/migrate/sql/postgres/02125_subscribe_stock.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02126_system_log_idx.down.sql b/crates/migrate/go/migrate/sql/postgres/02126_system_log_idx.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02126_system_log_idx.down.sql rename to crates/migrate/go/migrate/sql/postgres/02126_system_log_idx.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02126_system_log_idx.up.sql b/crates/migrate/go/migrate/sql/postgres/02126_system_log_idx.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02126_system_log_idx.up.sql rename to crates/migrate/go/migrate/sql/postgres/02126_system_log_idx.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02127_search_indexes.down.sql b/crates/migrate/go/migrate/sql/postgres/02127_search_indexes.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02127_search_indexes.down.sql rename to crates/migrate/go/migrate/sql/postgres/02127_search_indexes.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02127_search_indexes.up.sql b/crates/migrate/go/migrate/sql/postgres/02127_search_indexes.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02127_search_indexes.up.sql rename to crates/migrate/go/migrate/sql/postgres/02127_search_indexes.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02128_server_config_override.down.sql b/crates/migrate/go/migrate/sql/postgres/02128_server_config_override.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02128_server_config_override.down.sql rename to crates/migrate/go/migrate/sql/postgres/02128_server_config_override.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02128_server_config_override.up.sql b/crates/migrate/go/migrate/sql/postgres/02128_server_config_override.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02128_server_config_override.up.sql rename to crates/migrate/go/migrate/sql/postgres/02128_server_config_override.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02129_payment_sort.down.sql b/crates/migrate/go/migrate/sql/postgres/02129_payment_sort.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02129_payment_sort.down.sql rename to crates/migrate/go/migrate/sql/postgres/02129_payment_sort.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02129_payment_sort.up.sql b/crates/migrate/go/migrate/sql/postgres/02129_payment_sort.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02129_payment_sort.up.sql rename to crates/migrate/go/migrate/sql/postgres/02129_payment_sort.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.down.sql b/crates/migrate/go/migrate/sql/postgres/02130_subscribe_tutorial.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.down.sql rename to crates/migrate/go/migrate/sql/postgres/02130_subscribe_tutorial.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.up.sql b/crates/migrate/go/migrate/sql/postgres/02130_subscribe_tutorial.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.up.sql rename to crates/migrate/go/migrate/sql/postgres/02130_subscribe_tutorial.up.sql diff --git a/tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.down.sql b/crates/migrate/go/migrate/sql/postgres/02131_timestamptz_last_reported_at.down.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.down.sql rename to crates/migrate/go/migrate/sql/postgres/02131_timestamptz_last_reported_at.down.sql diff --git a/tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.up.sql b/crates/migrate/go/migrate/sql/postgres/02131_timestamptz_last_reported_at.up.sql similarity index 100% rename from tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.up.sql rename to crates/migrate/go/migrate/sql/postgres/02131_timestamptz_last_reported_at.up.sql diff --git a/crates/migrate/go/r2g/gen.go b/crates/migrate/go/r2g/gen.go new file mode 100644 index 00000000..54ff7445 --- /dev/null +++ b/crates/migrate/go/r2g/gen.go @@ -0,0 +1,280 @@ +package main + +/* +// Generated by rust2go. Please DO NOT edit this C part manually. + +#include +#include +#include +#include + +typedef struct ListRef { + const void *ptr; + uintptr_t len; +} ListRef; + +typedef struct StringRef { + const uint8_t *ptr; + uintptr_t len; +} StringRef; + +typedef struct MigrateConfigRef { + struct StringRef driver; + struct StringRef dsn; +} MigrateConfigRef; + +typedef struct MigrateOutcomeRef { + uint32_t version; + bool dirty; + struct StringRef error; +} MigrateOutcomeRef; +*/ +import "C" +import ( + "runtime" + "unsafe" + + "github.com/ihciah/rust2go/asmcall" +) + +var MigrateServiceImpl MigrateService + +type MigrateService interface { + up(cfg MigrateConfig) MigrateOutcome + version(cfg MigrateConfig) MigrateOutcome +} + +//export CMigrateService_up +func CMigrateService_up(cfg C.MigrateConfigRef, slot *C.void, cb *C.void) { + _new_cfg := newMigrateConfig(cfg) + resp := MigrateServiceImpl.up(_new_cfg) + resp_ref, buffer := cvt_ref(cntMigrateOutcome, refMigrateOutcome)(&resp) + asmcall.CallFuncG0P2(unsafe.Pointer(cb), unsafe.Pointer(&resp_ref), unsafe.Pointer(slot)) + runtime.KeepAlive(resp_ref) + runtime.KeepAlive(resp) + runtime.KeepAlive(buffer) +} + +//export CMigrateService_version +func CMigrateService_version(cfg C.MigrateConfigRef, slot *C.void, cb *C.void) { + _new_cfg := newMigrateConfig(cfg) + resp := MigrateServiceImpl.version(_new_cfg) + resp_ref, buffer := cvt_ref(cntMigrateOutcome, refMigrateOutcome)(&resp) + asmcall.CallFuncG0P2(unsafe.Pointer(cb), unsafe.Pointer(&resp_ref), unsafe.Pointer(slot)) + runtime.KeepAlive(resp_ref) + runtime.KeepAlive(resp) + runtime.KeepAlive(buffer) +} + +func newString(s_ref C.StringRef) string { + return unsafe.String((*byte)(unsafe.Pointer(s_ref.ptr)), s_ref.len) +} +func refString(s *string, _ *[]byte) C.StringRef { + return C.StringRef{ + ptr: (*C.uint8_t)(unsafe.StringData(*s)), + len: C.uintptr_t(len(*s)), + } +} + +func ownString(s_ref C.StringRef) string { + return string(unsafe.Slice((*byte)(unsafe.Pointer(s_ref.ptr)), int(s_ref.len))) +} +func cntString(_ *string, _ *uint) [0]C.StringRef { return [0]C.StringRef{} } +func new_list_mapper[T1, T2 any](f func(T1) T2) func(C.ListRef) []T2 { + return func(x C.ListRef) []T2 { + input := unsafe.Slice((*T1)(unsafe.Pointer(x.ptr)), x.len) + output := make([]T2, len(input)) + for i, v := range input { + output[i] = f(v) + } + return output + } +} +func new_list_mapper_primitive[T1, T2 any](_ func(T1) T2) func(C.ListRef) []T2 { + return func(x C.ListRef) []T2 { + return unsafe.Slice((*T2)(unsafe.Pointer(x.ptr)), x.len) + } +} + +// only handle non-primitive type T +func cnt_list_mapper[T, R any](f func(s *T, cnt *uint) [0]R) func(s *[]T, cnt *uint) [0]C.ListRef { + return func(s *[]T, cnt *uint) [0]C.ListRef { + for _, v := range *s { + f(&v, cnt) + } + *cnt += uint(len(*s)) * size_of[R]() + return [0]C.ListRef{} + } +} + +// only handle primitive type T +func cnt_list_mapper_primitive[T, R any](_ func(s *T, cnt *uint) [0]R) func(s *[]T, cnt *uint) [0]C.ListRef { + return func(s *[]T, cnt *uint) [0]C.ListRef { return [0]C.ListRef{} } +} + +// only handle non-primitive type T +func ref_list_mapper[T, R any](f func(s *T, buffer *[]byte) R) func(s *[]T, buffer *[]byte) C.ListRef { + return func(s *[]T, buffer *[]byte) C.ListRef { + if len(*buffer) == 0 { + return C.ListRef{ + ptr: unsafe.Pointer(nil), + len: C.uintptr_t(len(*s)), + } + } + ret := C.ListRef{ + ptr: unsafe.Pointer(&(*buffer)[0]), + len: C.uintptr_t(len(*s)), + } + children_bytes := int(size_of[R]()) * len(*s) + children := (*buffer)[:children_bytes] + *buffer = (*buffer)[children_bytes:] + for _, v := range *s { + child := f(&v, buffer) + len := unsafe.Sizeof(child) + copy(children, unsafe.Slice((*byte)(unsafe.Pointer(&child)), len)) + children = children[len:] + } + return ret + } +} + +// only handle primitive type T +func ref_list_mapper_primitive[T, R any](_ func(s *T, buffer *[]byte) R) func(s *[]T, buffer *[]byte) C.ListRef { + return func(s *[]T, buffer *[]byte) C.ListRef { + if len(*s) == 0 { + return C.ListRef{ + ptr: unsafe.Pointer(nil), + len: C.uintptr_t(0), + } + } + return C.ListRef{ + ptr: unsafe.Pointer(&(*s)[0]), + len: C.uintptr_t(len(*s)), + } + } +} +func size_of[T any]() uint { + var t T + return uint(unsafe.Sizeof(t)) +} +func cvt_ref[R, CR any](cnt_f func(s *R, cnt *uint) [0]CR, ref_f func(p *R, buffer *[]byte) CR) func(p *R) (CR, []byte) { + return func(p *R) (CR, []byte) { + var cnt uint + cnt_f(p, &cnt) + buffer := make([]byte, cnt) + return ref_f(p, &buffer), buffer + } +} +func cvt_ref_cap[R, CR any](cnt_f func(s *R, cnt *uint) [0]CR, ref_f func(p *R, buffer *[]byte) CR, add_cap uint) func(p *R) (CR, []byte) { + return func(p *R) (CR, []byte) { + var cnt uint + cnt_f(p, &cnt) + buffer := make([]byte, cnt, cnt+add_cap) + return ref_f(p, &buffer), buffer + } +} + +func newC_uint8_t(n C.uint8_t) uint8 { return uint8(n) } +func newC_uint16_t(n C.uint16_t) uint16 { return uint16(n) } +func newC_uint32_t(n C.uint32_t) uint32 { return uint32(n) } +func newC_uint64_t(n C.uint64_t) uint64 { return uint64(n) } +func newC_int8_t(n C.int8_t) int8 { return int8(n) } +func newC_int16_t(n C.int16_t) int16 { return int16(n) } +func newC_int32_t(n C.int32_t) int32 { return int32(n) } +func newC_int64_t(n C.int64_t) int64 { return int64(n) } +func newC_bool(n C.bool) bool { return bool(n) } +func newC_uintptr_t(n C.uintptr_t) uint { return uint(n) } +func newC_intptr_t(n C.intptr_t) int { return int(n) } +func newC_float(n C.float) float32 { return float32(n) } +func newC_double(n C.double) float64 { return float64(n) } + +func cntC_uint8_t(_ *uint8, _ *uint) [0]C.uint8_t { return [0]C.uint8_t{} } +func cntC_uint16_t(_ *uint16, _ *uint) [0]C.uint16_t { return [0]C.uint16_t{} } +func cntC_uint32_t(_ *uint32, _ *uint) [0]C.uint32_t { return [0]C.uint32_t{} } +func cntC_uint64_t(_ *uint64, _ *uint) [0]C.uint64_t { return [0]C.uint64_t{} } +func cntC_int8_t(_ *int8, _ *uint) [0]C.int8_t { return [0]C.int8_t{} } +func cntC_int16_t(_ *int16, _ *uint) [0]C.int16_t { return [0]C.int16_t{} } +func cntC_int32_t(_ *int32, _ *uint) [0]C.int32_t { return [0]C.int32_t{} } +func cntC_int64_t(_ *int64, _ *uint) [0]C.int64_t { return [0]C.int64_t{} } +func cntC_bool(_ *bool, _ *uint) [0]C.bool { return [0]C.bool{} } +func cntC_uintptr_t(_ *uint, _ *uint) [0]C.uintptr_t { return [0]C.uintptr_t{} } +func cntC_intptr_t(_ *int, _ *uint) [0]C.intptr_t { return [0]C.intptr_t{} } +func cntC_float(_ *float32, _ *uint) [0]C.float { return [0]C.float{} } +func cntC_double(_ *float64, _ *uint) [0]C.double { return [0]C.double{} } + +func refC_uint8_t(p *uint8, _ *[]byte) C.uint8_t { return C.uint8_t(*p) } +func refC_uint16_t(p *uint16, _ *[]byte) C.uint16_t { return C.uint16_t(*p) } +func refC_uint32_t(p *uint32, _ *[]byte) C.uint32_t { return C.uint32_t(*p) } +func refC_uint64_t(p *uint64, _ *[]byte) C.uint64_t { return C.uint64_t(*p) } +func refC_int8_t(p *int8, _ *[]byte) C.int8_t { return C.int8_t(*p) } +func refC_int16_t(p *int16, _ *[]byte) C.int16_t { return C.int16_t(*p) } +func refC_int32_t(p *int32, _ *[]byte) C.int32_t { return C.int32_t(*p) } +func refC_int64_t(p *int64, _ *[]byte) C.int64_t { return C.int64_t(*p) } +func refC_bool(p *bool, _ *[]byte) C.bool { return C.bool(*p) } +func refC_uintptr_t(p *uint, _ *[]byte) C.uintptr_t { return C.uintptr_t(*p) } +func refC_intptr_t(p *int, _ *[]byte) C.intptr_t { return C.intptr_t(*p) } +func refC_float(p *float32, _ *[]byte) C.float { return C.float(*p) } +func refC_double(p *float64, _ *[]byte) C.double { return C.double(*p) } + +type MigrateConfig struct { + driver string + dsn string +} + +func newMigrateConfig(p C.MigrateConfigRef) MigrateConfig { + return MigrateConfig{ + driver: newString(p.driver), + dsn: newString(p.dsn), + } +} +func ownMigrateConfig(p C.MigrateConfigRef) MigrateConfig { + return MigrateConfig{ + driver: ownString(p.driver), + dsn: ownString(p.dsn), + } +} +func cntMigrateConfig(s *MigrateConfig, cnt *uint) [0]C.MigrateConfigRef { + _ = s + _ = cnt + return [0]C.MigrateConfigRef{} +} +func refMigrateConfig(p *MigrateConfig, buffer *[]byte) C.MigrateConfigRef { + return C.MigrateConfigRef{ + driver: refString(&p.driver, buffer), + dsn: refString(&p.dsn, buffer), + } +} + +type MigrateOutcome struct { + version uint32 + dirty bool + error string +} + +func newMigrateOutcome(p C.MigrateOutcomeRef) MigrateOutcome { + return MigrateOutcome{ + version: newC_uint32_t(p.version), + dirty: newC_bool(p.dirty), + error: newString(p.error), + } +} +func ownMigrateOutcome(p C.MigrateOutcomeRef) MigrateOutcome { + return MigrateOutcome{ + version: newC_uint32_t(p.version), + dirty: newC_bool(p.dirty), + error: ownString(p.error), + } +} +func cntMigrateOutcome(s *MigrateOutcome, cnt *uint) [0]C.MigrateOutcomeRef { + _ = s + _ = cnt + return [0]C.MigrateOutcomeRef{} +} +func refMigrateOutcome(p *MigrateOutcome, buffer *[]byte) C.MigrateOutcomeRef { + return C.MigrateOutcomeRef{ + version: refC_uint32_t(&p.version, buffer), + dirty: refC_bool(&p.dirty, buffer), + error: refString(&p.error, buffer), + } +} +func main() {} diff --git a/crates/migrate/go/r2g/impl.go b/crates/migrate/go/r2g/impl.go new file mode 100644 index 00000000..7a27cc64 --- /dev/null +++ b/crates/migrate/go/r2g/impl.go @@ -0,0 +1,77 @@ +package main + +import ( + "errors" + + ppmigrate "github.com/perfect-panel/ppanel-backend/crates/migrate/go/migrate" +) + +// migrateService is the rust2go implementation of the MigrateService trait +// declared in crates/migrate/src/idl.rs. The struct name is unexported to +// avoid colliding with the auto-generated `MigrateService` interface symbol +// in gen.go (per rust2go convention: impl must be unexported, the global +// registration variable `MigrateServiceImpl` must be exported). +type migrateService struct{} + +func init() { + // Register the impl. The variable name MUST be exactly + // `MigrateServiceImpl` (matches the trait name + "Impl" suffix that + // gen.go expects). + MigrateServiceImpl = migrateService{} +} + +// up is a sync, no-return-blocking call. We pre-allocate the result with +// sensible defaults; on success we set version+dirty; on failure we +// populate `error` with the Go-side message so Rust can panic/format it. +func (migrateService) up(cfg MigrateConfig) MigrateOutcome { + sess, err := ppmigrate.Migrate(cfg.driver, cfg.dsn) + if err != nil { + return MigrateOutcome{error: err.Error()} + } + // Always close the migrate client so we don't leak the DB connection + // pool it opens. ErrNoChange is fine — the migrator returns no error + // if everything is up to date and we still want a final version + // report below. + if runErr := ppmigrate.RunUp(sess); runErr != nil && !errors.Is(runErr, ppmigrate.NoChange) { + return MigrateOutcome{error: runErr.Error()} + } + // After RunUp, the version we read may be ErrNilVersion only if the + // DB was empty AND the migration source was also empty (caught by + // RunUp's own length check). Otherwise we get a concrete version. + v, dirty, vErr := sess.Migrate.Version() + out := MigrateOutcome{} + if vErr != nil { + // ErrNilVersion is a valid end state: schema_migrations has no + // row because the source has no up.sql files. Surface as + // version=0 dirty=false with no error so Rust can log "DB is + // empty" without panicking. + if !errors.Is(vErr, ppmigrate.ErrNilVersion) { + out.error = vErr.Error() + } + return out + } + out.version = uint32(v) + out.dirty = dirty + return out +} + +// version returns the current schema_migrations state without applying +// anything. Useful for ops/debug. +func (migrateService) version(cfg MigrateConfig) MigrateOutcome { + sess, err := ppmigrate.Migrate(cfg.driver, cfg.dsn) + if err != nil { + return MigrateOutcome{error: err.Error()} + } + v, dirty, err := sess.Migrate.Version() + out := MigrateOutcome{} + if err != nil { + if errors.Is(err, ppmigrate.ErrNilVersion) { + return out // version=0, dirty=false, no error + } + out.error = err.Error() + return out + } + out.version = uint32(v) + out.dirty = dirty + return out +} diff --git a/crates/migrate/src/idl.rs b/crates/migrate/src/idl.rs new file mode 100644 index 00000000..bd392b6d --- /dev/null +++ b/crates/migrate/src/idl.rs @@ -0,0 +1,81 @@ +//! Cross-language interface definition for the migration tool. +//! +//! This file is the **single source of truth** for the Rust↔Go boundary. +//! rust2go reads it and auto-generates: +//! - `gen.go` (Go-side interface + CGO exports) at +//! `tools/migrate/migrate/gen.go` +//! - The C binding file (`_go_bindings.rs` by default) used by the +//! Rust crate to call into the Go static library. +//! +//! The hand-written Go side lives in `tools/migrate/migrate/impl.go`. + +pub mod binding { + #![allow(warnings)] + rust2go::r2g_include_binding!(); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Wire types — must be `#[derive(R2G, Clone)]` and use only path-local +// primitive types. See the rust2go skill notes for the supported type +// matrix. +// ═══════════════════════════════════════════════════════════════════════════ + +/// Configuration for a single migration invocation. Maps 1:1 to the +/// `-driver` + `-dsn` flags on the standalone CLI. +#[derive(rust2go::R2G, Clone)] +pub struct MigrateConfig { + /// Either `"postgres"` or `"mysql"`. Any other value causes the Go + /// side to return an error in `MigrateOutcome.error`. + pub driver: String, + + /// Full DSN understood by golang-migrate. The Go side will + /// auto-prepend `postgres://` or `mysql://` if missing, mirroring + /// the CLI behaviour. + pub dsn: String, +} + +/// Result of a migration call. Errors are signalled by populating +/// `error` with a non-empty message — we deliberately use a plain +/// `String` (not a sum type) so the type marshalling stays trivial and +/// panic-free across the FFI boundary. +#[derive(rust2go::R2G, Clone)] +pub struct MigrateOutcome { + /// Final schema version after the call. `0` if the + /// `schema_migrations` table is empty (fresh database). + pub version: u32, + + /// Whether golang-migrate flagged the migration as dirty. A dirty + /// state means a previous migration failed mid-way; the caller + /// should refuse to serve traffic and request human intervention. + pub dirty: bool, + + /// Empty string on success. Populated with the Go-side error + /// message on failure. Rust converts this to a panic with the + /// same semantics as the previous subprocess call. + pub error: String, +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Trait — the cross-language service definition. +// ═══════════════════════════════════════════════════════════════════════════ + +/// Schema migration service exposed from Go to Rust. +/// +/// Each method is a **synchronous** call: Rust blocks the calling +/// thread until Go returns. This is intentional — these are called +/// once at startup before any traffic is served, so we don't need +/// async / shm / drop-safe machinery (which would add complexity for +/// no runtime benefit). The Go side is a single goroutine per call, +/// gated by a mutex on the `init` registration. +#[rust2go::r2g] +pub trait MigrateService { + /// Apply all pending migrations. Equivalent to `ppanel-migrate up`. + /// Idempotent: returns success with `version = latest` if the + /// database is already at the latest source version (matches the + /// `ErrNoChange` semantics of the standalone CLI). + fn up(cfg: MigrateConfig) -> MigrateOutcome; + + /// Read the current `schema_migrations.version` and `dirty` flag + /// without applying anything. Useful for ops/debug. + fn version(cfg: MigrateConfig) -> MigrateOutcome; +} diff --git a/crates/migrate/src/lib.rs b/crates/migrate/src/lib.rs new file mode 100644 index 00000000..bf1ae81a --- /dev/null +++ b/crates/migrate/src/lib.rs @@ -0,0 +1,92 @@ +//! Rust binding to the Go migration tool (`tools/migrate/migrate`). +//! +//! This crate wraps the Go-based schema migrator behind a Rust API +//! that can be called directly from the main binary — no subprocess, +//! no out-of-band `ppanel-migrate` binary, no `PPANEL_MIGRATE_BIN` +//! resolution. +//! +//! # Architecture +//! +//! ```text +//! ┌────────────────────────┐ FFI ┌──────────────────────┐ +//! │ ppanel-backend (Rust) │ ───────────────► │ Go staticlib │ +//! │ - main.rs │ │ - golang-migrate │ +//! │ - migration.rs │ │ - SQL files │ +//! │ │ │ (embedded via │ +//! │ │ │ //go:embed) │ +//! └────────────────────────┘ └──────────────────────┘ +//! ``` +//! +//! The Go side opens its own short-lived database connection per +//! call. The connection is closed when the call returns. We do not +//! share sqlx pools across the FFI boundary — that would require +//! additional plumbing for no real benefit (the migrator only runs +//! at startup). +//! +//! # Build +//! +//! The build is driven by `build.rs`, which calls +//! `rust2go::Builder`. The Builder compiles the Go code as CGO and +//! produces `_go_bindings.rs` containing the C declarations used by +//! the Rust side. The Go glue (`gen.go`) is regenerated automatically +//! whenever `src/idl.rs` changes. +//! +//! # Runtime +//! +//! The user must set the env vars documented by rust2go when the +//! binary is launched: +//! +//! ```text +//! GODEBUG=invalidptr=0,cgocheck=0 +//! ``` +//! +//! Without these, Go's conservative GC pointer checks will flag the +//! FFI references as invalid and abort. This is a property of +//! rust2go, not specific to this crate. + +pub mod idl; + +pub use idl::{MigrateConfig, MigrateOutcome, MigrateService, MigrateServiceImpl}; + +/// Apply all pending schema migrations. +/// +/// Mirrors the behaviour of the previous `ppanel-migrate up` +/// subprocess call: +/// - **Idempotent**: if the database is already at the latest +/// version, returns silently with the current version. +/// - **Fail-fast**: on any Go-side error, panics with the Go error +/// message. The migrator is called at startup before any traffic +/// is served, so failing the process is the correct behaviour. +/// +/// # Arguments +/// - `driver`: `"postgres"` or `"mysql"`. +/// - `dsn`: connection string in golang-migrate URL form. The Go +/// side will auto-prepend the URL scheme if missing. +pub fn up(driver: &str, dsn: &str) -> MigrateOutcome { + let cfg = MigrateConfig { + driver: driver.to_string(), + dsn: dsn.to_string(), + }; + let outcome = MigrateServiceImpl::up(cfg); + if !outcome.error.is_empty() { + panic!( + "ppanel-migrate up failed: {}\n\ + Build via: `cd tools/migrate && go build -o ppanel-migrate ./cmd/migrate`\n\ + (or rely on the rust2go FFI path embedded at build time).", + outcome.error + ); + } + outcome +} + +/// Read the current schema version without applying anything. +/// +/// Returns `MigrateOutcome { version: 0, dirty: false, error: "" }` +/// for a fresh database (no `schema_migrations` rows). +pub fn version(driver: &str, dsn: &str) -> MigrateOutcome { + let cfg = MigrateConfig { + driver: driver.to_string(), + dsn: dsn.to_string(), + }; + MigrateServiceImpl::version(cfg) +} diff --git a/src/migration.rs b/src/migration.rs index b5825d6e..ebedbdc4 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -1,104 +1,59 @@ //! Database bootstrap utilities. //! -//! Schema migrations are **not** owned by this Rust binary. They are managed by -//! the standalone Go tool at `tools/migrate/` (built with `go build -o ppanel-migrate ./cmd/migrate`), -//! which uses golang-migrate and tracks state in the `schema_migrations` table -//! — the same table the Go server uses. That makes a single source of truth +//! Schema migrations are owned by the Go static library linked in via +//! the `migrate` crate (which uses `rust2go` FFI). The Go code embeds +//! the same SQL migration files and applies them via `golang-migrate`, +//! tracking state in the `schema_migrations` table — the same table +//! the original Go server used. That makes a single source of truth //! for schema across the Go and Rust backends. //! -//! On startup, this module unconditionally invokes the `ppanel-migrate up` command. -//! The tool's own logic is idempotent — if the DB is already at the latest -//! version it returns ErrNoChange with no side effects. +//! On startup, this module invokes the FFI-bound `migrate::up` call. +//! The Go-side logic is idempotent — if the DB is already at the +//! latest version it returns success with no side effects. //! -//! **NOTE**: This is the Rust rewrite of the Go backend. The Go version is -//! deprecated and will be replaced by this Rust implementation. +//! **NOTE**: This is the Rust rewrite of the Go backend. The Go +//! version is deprecated and will be replaced by this Rust +//! implementation. The Go migration code is retained only as an +//! in-process FFI dependency, not as a separately-spawned binary. pub use crate::repository::Dialect; use crate::config::DatabaseConfig; use crate::db::build_dsn; use crate::repository::Db; -use std::path::PathBuf; -use std::process::Command; -/// Run all pending schema migrations by invoking the `ppanel-migrate up` command. -/// Always called on startup — the tool is idempotent and is a no-op if the DB -/// is already at the latest version. -pub async fn ensure_schema(cfg: &DatabaseConfig) { - tracing::info!("running ppanel-migrate up"); - run_migrate_tool(cfg).await; - tracing::info!("ppanel-migrate completed"); -} - -/// Resolve the path to the `ppanel-migrate` binary. +/// Apply all pending schema migrations via the rust2go FFI bridge. /// -/// Search order: -/// 1. `${PPANEL_MIGRATE_BIN}` if set. -/// 2. Next to the current executable (`./ppanel-migrate`). -/// 3. One level up from the executable (cargo puts the binary in target/release/; -/// the migrate tool often lives at tools/migrate/ppanel-migrate). -/// 4. `$PATH` (via Command's default lookup). -fn locate_migrate_bin() -> PathBuf { - if let Ok(p) = std::env::var("PPANEL_MIGRATE_BIN") { - return PathBuf::from(p); - } - if let Ok(exe) = std::env::current_exe() { - if let Some(dir) = exe.parent() { - let candidate = dir.join("ppanel-migrate"); - if candidate.is_file() { - return candidate; - } - if let Some(parent) = dir.parent() { - let candidate = parent.join("ppanel-migrate"); - if candidate.is_file() { - return candidate; - } - } - } - } - PathBuf::from("ppanel-migrate") -} - -async fn run_migrate_tool(cfg: &DatabaseConfig) { - let bin = locate_migrate_bin(); +/// Always called on startup — the migrator is idempotent and a no-op +/// if the database is already at the latest version. Failures are +/// fatal: the Go side reports the error in `MigrateOutcome.error` and +/// we `panic!` with that message, aborting startup. This matches the +/// fail-fast behaviour of the previous subprocess-based +/// implementation. +pub async fn ensure_schema(cfg: &DatabaseConfig) { let driver = match cfg.driver.as_str() { "mysql" => "mysql", _ => "postgres", }; let dsn = build_dsn(cfg); - tracing::info!(driver, bin = %bin.display(), "running ppanel-migrate up"); + tracing::info!(driver, "running schema migrations via rust2go FFI"); - // Run the migrate tool synchronously — it must complete before we serve - // any traffic. Failures are fatal. - let output = Command::new(&bin) - .arg("-driver") - .arg(driver) - .arg("-dsn") - .arg(&dsn) - .arg("up") - .output() - .unwrap_or_else(|e| { - panic!( - "failed to execute ppanel-migrate at {}: {e}. \ - Build it with `go build -o ppanel-migrate ./cmd/migrate` \ - from ppanel-backend/tools/migrate, or set $PPANEL_MIGRATE_BIN", - bin.display(), - ) - }); + // The FFI call is synchronous and may take seconds (open conn, + // apply all pending migrations, close conn). Run it on a blocking + // thread so we don't stall the tokio reactor if other startup + // tasks were racing us. + let outcome = tokio::task::spawn_blocking(move || { + migrate::up(driver, &dsn) + }) + .await + .expect("migration task panicked"); - if !output.status.success() { - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - panic!( - "ppanel-migrate exited with status {}: {}{}{}", - output.status, - stdout, - if !stdout.is_empty() && !stderr.is_empty() { "\n" } else { "" }, - stderr, - ); - } - tracing::info!("ppanel-migrate completed"); + tracing::info!( + version = outcome.version, + dirty = outcome.dirty, + "schema migrations complete" + ); } // ═══════════════════════════════════════════════════════════════════════════ @@ -258,4 +213,4 @@ fn hash_password_pbkdf2(password: &str) -> String { fn generate_invite_code() -> String { format!("u{}", &uuid::Uuid::new_v4().to_string().replace('-', "")[..12]) -} \ No newline at end of file +} diff --git a/tools/migrate/go.mod b/tools/migrate/go.mod deleted file mode 100644 index 68dec01e..00000000 --- a/tools/migrate/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/perfect-panel/ppanel-backend/tools/migrate - -go 1.25.0 - -require github.com/golang-migrate/migrate/v4 v4.19.1 - -require ( - filippo.io/edwards25519 v1.1.0 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/lib/pq v1.10.9 // indirect -) diff --git a/tools/migrate/ppanel-migrate b/tools/migrate/ppanel-migrate deleted file mode 100755 index 044e4443..00000000 Binary files a/tools/migrate/ppanel-migrate and /dev/null differ