Multi DB support

This commit is contained in:
JSH32 2022-08-11 11:02:12 -05:00
parent af3500df1c
commit b596b4bd1b
28 changed files with 1042 additions and 186 deletions

7
.gitignore vendored
View File

@ -4,4 +4,9 @@ target
/uploads
test
*_archive
*.code-workspace
*.code-workspace
# SQLite files
*.db*
*.sqlite*
*.sqlite3*

430
Cargo.lock generated
View File

@ -309,6 +309,15 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.57"
@ -338,6 +347,113 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "async-attributes"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "async-channel"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"once_cell",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940"
dependencies = [
"async-channel",
"async-executor",
"async-io",
"async-lock",
"blocking",
"futures-lite",
"num_cpus",
"once_cell",
"tokio",
]
[[package]]
name = "async-io"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07"
dependencies = [
"concurrent-queue",
"futures-lite",
"libc",
"log",
"once_cell",
"parking",
"polling",
"slab",
"socket2",
"waker-fn",
"winapi",
]
[[package]]
name = "async-lock"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
dependencies = [
"event-listener",
]
[[package]]
name = "async-std"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-attributes",
"async-channel",
"async-global-executor",
"async-io",
"async-lock",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-stream"
version = "0.3.3"
@ -359,6 +475,12 @@ dependencies = [
"syn",
]
[[package]]
name = "async-task"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
[[package]]
name = "async-trait"
version = "0.1.53"
@ -379,6 +501,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "atomic-waker"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "atty"
version = "0.2.14"
@ -425,6 +553,7 @@ dependencies = [
"lazy_static",
"lettre",
"log",
"migration",
"nanoid",
"num_cpus",
"paste",
@ -508,6 +637,20 @@ dependencies = [
"generic-array",
]
[[package]]
name = "blocking"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc"
dependencies = [
"async-channel",
"async-task",
"atomic-waker",
"fastrand",
"futures-lite",
"once_cell",
]
[[package]]
name = "brotli"
version = "3.3.4"
@ -562,6 +705,12 @@ dependencies = [
"bytes",
]
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]]
name = "cc"
version = "1.0.73"
@ -604,16 +753,16 @@ dependencies = [
[[package]]
name = "clap"
version = "3.1.18"
version = "3.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"lazy_static",
"once_cell",
"strsim",
"termcolor",
"textwrap",
@ -621,9 +770,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "3.1.18"
version = "3.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4"
dependencies = [
"heck 0.4.0",
"proc-macro-error",
@ -634,9 +783,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.2.0"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
@ -658,6 +807,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "concurrent-queue"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c"
dependencies = [
"cache-padded",
]
[[package]]
name = "console"
version = "0.15.0"
@ -814,6 +972,16 @@ dependencies = [
"subtle",
]
[[package]]
name = "ctor"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "deflate"
version = "1.0.0"
@ -1110,6 +1278,21 @@ version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-lite"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-macro"
version = "0.3.21"
@ -1206,6 +1389,18 @@ dependencies = [
"syn",
]
[[package]]
name = "gloo-timers"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "h2"
version = "0.3.13"
@ -1545,6 +1740,15 @@ dependencies = [
"simple_asn1",
]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]]
name = "language-tags"
version = "0.3.2"
@ -1594,6 +1798,17 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "libsqlite3-sys"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "local-channel"
version = "0.1.3"
@ -1629,6 +1844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
"value-bag",
]
[[package]]
@ -1637,6 +1853,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata",
]
[[package]]
name = "matches"
version = "0.1.9"
@ -1678,6 +1903,14 @@ dependencies = [
"autocfg",
]
[[package]]
name = "migration"
version = "0.1.0"
dependencies = [
"async-std",
"sea-orm-migration",
]
[[package]]
name = "mime"
version = "0.3.16"
@ -1846,9 +2079,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "once_cell"
version = "1.10.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "opaque-debug"
@ -1931,6 +2164,12 @@ dependencies = [
"syn",
]
[[package]]
name = "parking"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -2067,6 +2306,19 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "polling"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
dependencies = [
"cfg-if",
"libc",
"log",
"wepoll-ffi",
"winapi",
]
[[package]]
name = "postgres-protocol"
version = "0.6.4"
@ -2246,6 +2498,15 @@ dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
@ -2470,6 +2731,23 @@ dependencies = [
"uuid",
]
[[package]]
name = "sea-orm-cli"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f527a5c69fc9b9cd8d243fa45f5bcf14e5e2a3e94b003267be1b075221c5b04b"
dependencies = [
"async-std",
"chrono",
"clap",
"dotenv",
"regex",
"sea-schema",
"tracing",
"tracing-subscriber",
"url",
]
[[package]]
name = "sea-orm-macros"
version = "0.9.1"
@ -2483,6 +2761,22 @@ dependencies = [
"syn",
]
[[package]]
name = "sea-orm-migration"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697d983cc6fd0ec497934cbfd16b3872b7e95be369e8851bdc65770c3b5ec57d"
dependencies = [
"async-trait",
"clap",
"dotenv",
"sea-orm",
"sea-orm-cli",
"sea-schema",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "sea-query"
version = "0.26.2"
@ -2523,6 +2817,29 @@ dependencies = [
"syn",
]
[[package]]
name = "sea-schema"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94d070aba647637b533bd669a8e430bdc8f7d5249c9b53402da34347563bbfca"
dependencies = [
"futures",
"sea-query",
"sea-schema-derive",
]
[[package]]
name = "sea-schema-derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56821b7076f5096b8f726e2791ad255a99c82498e08ec477a65a96c461ff1927"
dependencies = [
"heck 0.3.3",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sea-strum"
version = "0.23.0"
@ -2664,6 +2981,15 @@ dependencies = [
"digest 0.10.3",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.1.0"
@ -2767,8 +3093,10 @@ dependencies = [
"dirs",
"either",
"event-listener",
"flume",
"futures-channel",
"futures-core",
"futures-executor",
"futures-intrusive",
"futures-util",
"hashlink",
@ -2778,6 +3106,7 @@ dependencies = [
"indexmap",
"itoa",
"libc",
"libsqlite3-sys",
"log",
"md-5 0.10.1",
"memchr",
@ -2832,7 +3161,6 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874e93a365a598dc3dadb197565952cb143ae4aa716f7bcc933a8d836f6bf89f"
dependencies = [
"actix-rt",
"once_cell",
"tokio",
"tokio-rustls",
@ -2936,6 +3264,15 @@ dependencies = [
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]]
name = "threadpool"
version = "1.8.1"
@ -3113,6 +3450,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
dependencies = [
"lazy_static",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596"
dependencies = [
"ansi_term",
"lazy_static",
"matchers",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
@ -3244,6 +3611,22 @@ dependencies = [
"serde",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.0.0-alpha.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55"
dependencies = [
"ctor",
"version_check",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
@ -3256,6 +3639,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "want"
version = "0.3.0"
@ -3303,6 +3692,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.80"
@ -3367,6 +3768,15 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c97e489d8f836838d497091de568cf16b117486d529ec5579233521065bd5e4"
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
dependencies = [
"cc",
]
[[package]]
name = "whoami"
version = "1.2.1"

View File

@ -1,3 +1,6 @@
[workspace]
members = [".", "migration"]
[package]
name = "backpack"
version = "0.1.0"
@ -7,16 +10,18 @@ edition = "2018"
[dependencies.sea-orm]
version = "0.9"
features = [
"sqlx-sqlite",
"sqlx-postgres",
"runtime-actix-rustls",
"runtime-tokio-rustls",
"macros"
]
[dependencies]
migration = { path = "migration" }
tokio = { version = "1.10.0", features = ["full"] }
sqlx = { version = "0.6.0", features = [ "runtime-actix-rustls", "postgres", "chrono" ] }
clap = { version = "3.1.0", features = ["derive"] }
clap = { version = "3.2.16", features = ["derive"] }
serde = "1.0.126"
sqlx = { version = "0.6.0", features = [ "runtime-tokio-rustls", "postgres", "chrono", "sqlite" ] }
serde_json = { version = "1.0.64", features = [ "preserve_order" ] }
jsonwebtoken = "8"
dotenv = "0.15.0"

23
migration/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
async-std = { version = "^1", features = ["attributes", "tokio1"] }
[dependencies.sea-orm-migration]
version = "^0.9.0"
features = [
# Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
# View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
# e.g.
"runtime-tokio-rustls",
"sqlx-postgres",
"sqlx-sqlite"
]

41
migration/README.md Normal file
View File

@ -0,0 +1,41 @@
# Running Migrator CLI
- Generate a new migration file
```sh
cargo run -- migrate generate MIGRATION_NAME
```
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

View File

@ -0,0 +1,13 @@
use sea_orm_migration::sea_query::ColumnDef;
/// Adds extra implementation utilities for migrations.
pub trait ColumnExtension {
fn sonyflake(&mut self) -> &mut Self;
}
impl ColumnExtension for ColumnDef {
/// Alias for `string_len(20)`, a sonyflake is 20 chars long.
fn sonyflake(&mut self) -> &mut Self {
self.string_len(20)
}
}

17
migration/src/lib.rs Normal file
View File

@ -0,0 +1,17 @@
pub use sea_orm_migration::prelude::*;
mod extensions;
mod m20220101_000001_initial_structure;
mod m20220810_114915_settings_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20220101_000001_initial_structure::Migration),
Box::new(m20220810_114915_settings_table::Migration),
]
}
}

View File

@ -0,0 +1,313 @@
use crate::extensions::*;
use sea_orm_migration::{prelude::*, sea_orm::DbBackend, sea_query::extension::postgres::Type};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Create the actual enum types if using postgres.
if manager.get_database_backend() == DbBackend::Postgres {
manager
.create_type(
Type::create()
.as_enum(Role::Type)
.values(vec![Role::User, Role::Admin])
.to_owned(),
)
.await?;
}
// User table
manager
.create_table(
Table::create()
.table(Users::Table)
.col(
ColumnDef::new(Users::Id)
.sonyflake()
.primary_key()
.not_null(),
)
.col(
ColumnDef::new(Users::Email)
.string_len(320)
.unique_key()
.not_null(),
)
.col(
ColumnDef::new(Users::Username)
.string_len(32)
.unique_key()
.not_null(),
)
.col(ColumnDef::new(Users::Password).string_len(128).not_null())
.col(
ColumnDef::new(Users::Verified)
.boolean()
.default(false)
.not_null(),
)
.col(
ColumnDef::new(Users::Role)
.enumeration("role", ["user", "admin"])
.default("user")
.not_null(),
)
.to_owned(),
)
.await?;
// Applications table
manager
.create_table(
Table::create()
.table(Applications::Table)
.col(
ColumnDef::new(Applications::Id)
.sonyflake()
.primary_key()
.not_null(),
)
.col(ColumnDef::new(Applications::UserId).string().not_null())
.col(ColumnDef::new(Applications::Name).string_len(16).not_null())
.col(
ColumnDef::new(Applications::LastAccessed)
.timestamp_with_time_zone()
.not_null()
.extra("DEFAULT CURRENT_TIMESTAMP".into()),
)
.foreign_key(
ForeignKey::create()
.from(Applications::Table, Applications::UserId)
.to(Users::Table, Users::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
// A user may not have two duplicate application names
manager
.create_index(
Index::create()
.unique()
.name("applications_name_uindex")
.table(Applications::Table)
.col(Applications::UserId)
.col(Applications::Name)
.to_owned(),
)
.await?;
// User verification
// Only one verification may exist per user
manager
.create_table(
Table::create()
.table(Verifications::Table)
.col(
ColumnDef::new(Verifications::Id)
.sonyflake()
.primary_key()
.not_null(),
)
.col(
ColumnDef::new(Verifications::Code)
.string_len(72)
.not_null()
.unique_key(),
)
.col(
ColumnDef::new(Verifications::UserId)
.sonyflake()
.not_null()
.unique_key(),
)
.foreign_key(
ForeignKey::create()
.from(Verifications::Table, Verifications::UserId)
.to(Users::Table, Users::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
// File table
manager
.create_table(
Table::create()
.table(Files::Table)
.col(
ColumnDef::new(Files::Id)
.sonyflake()
.primary_key()
.not_null(),
)
.col(
ColumnDef::new(Files::Name)
.string_len(32)
.not_null()
.unique_key(),
)
.col(
ColumnDef::new(Files::OriginalName)
.string_len(256)
.not_null(),
)
.col(ColumnDef::new(Files::Uploader).sonyflake().not_null())
.col(ColumnDef::new(Files::Hash).string_len(64).not_null())
.col(
ColumnDef::new(Files::Uploaded)
.timestamp_with_time_zone()
.not_null()
.extra("DEFAULT CURRENT_TIMESTAMP".into()),
)
.col(ColumnDef::new(Files::Size).big_integer().not_null())
.foreign_key(
ForeignKey::create()
.from(Files::Table, Files::Uploader)
.to(Users::Table, Users::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
// Two identical files by hash can not exist if owned by the same user
manager
.create_index(
Index::create()
.unique()
.name("files_user_hash_uindex")
.table(Files::Table)
.col(Files::Uploader)
.col(Files::Hash)
.to_owned(),
)
.await?;
// User registration keys
manager
.create_table(
Table::create()
.table(RegistrationKeys::Table)
.col(
ColumnDef::new(RegistrationKeys::Id)
.sonyflake()
.primary_key()
.not_null(),
)
.col(
ColumnDef::new(RegistrationKeys::Issuer)
.sonyflake()
.not_null(),
)
.col(
ColumnDef::new(RegistrationKeys::Code)
.uuid()
.not_null()
.unique_key(),
)
.col(
ColumnDef::new(RegistrationKeys::UsesLeft)
.integer()
.not_null()
.default(1),
)
.col(ColumnDef::new(RegistrationKeys::ExpiryDate).timestamp_with_time_zone())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Applications::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Verifications::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Files::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(RegistrationKeys::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Users::Table).to_owned())
.await?;
if manager.get_database_backend() == DbBackend::Postgres {
manager
.drop_type(Type::drop().name(Role::Type).to_owned())
.await?;
}
Ok(())
}
}
#[derive(Iden)]
enum Users {
Table,
Id,
Email,
Username,
Password,
Verified,
Role,
}
#[derive(Iden)]
enum Applications {
Table,
Id,
UserId,
Name,
LastAccessed,
}
#[derive(Iden)]
enum Verifications {
Table,
Id,
Code,
UserId,
}
#[derive(Iden)]
enum Files {
Table,
Id,
Name,
OriginalName,
Uploader,
Hash,
Uploaded,
Size,
}
#[derive(Iden)]
enum RegistrationKeys {
Table,
Id,
Issuer,
Code,
UsesLeft,
ExpiryDate,
}
#[derive(Iden)]
enum Role {
#[iden = "role"]
Type,
User,
Admin,
}

View File

@ -0,0 +1,120 @@
use sea_orm_migration::{prelude::*, sea_orm::DbBackend, sea_query::extension::postgres::Type};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Create the actual enum types if using postgres.
if manager.get_database_backend() == DbBackend::Postgres {
manager
.create_type(
Type::create()
.as_enum(ThemeColor::Type)
.values(vec![
ThemeColor::Gray,
ThemeColor::Red,
ThemeColor::Orange,
ThemeColor::Yellow,
ThemeColor::Green,
ThemeColor::Teal,
ThemeColor::Blue,
ThemeColor::Cyan,
ThemeColor::Purple,
ThemeColor::Pink,
])
.to_owned(),
)
.await?;
}
manager
.create_table(
Table::create()
.table(Settings::Table)
.col(
ColumnDef::new(Settings::OneRowEnforce)
.primary_key()
.not_null()
.boolean()
.default(true),
)
.col(
ColumnDef::new(Settings::AppName)
.string_len(64)
.not_null()
.default("Backpack"),
)
.col(
ColumnDef::new(Settings::AppDescription)
.text()
.not_null()
.default("A file host for all your needs"),
)
.col(
ColumnDef::new(Settings::Color)
.enumeration(
"theme_color",
[
"gray", "red", "orange", "yellow", "green", "teal", "blue",
"cyan", "purple", "pink",
],
)
.default("purple")
.not_null(),
)
.to_owned(),
)
.await?;
// Insert default settings at first, configuration done through web UI
manager
.exec_stmt(
Query::insert()
.into_table(Settings::Table)
.or_default_values()
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Settings::Table).to_owned())
.await?;
if manager.get_database_backend() == DbBackend::Postgres {
manager
.drop_type(Type::drop().name(ThemeColor::Type).to_owned())
.await?;
}
Ok(())
}
}
#[derive(Iden)]
enum Settings {
Table,
OneRowEnforce,
AppName,
AppDescription,
Color,
}
#[derive(Iden)]
enum ThemeColor {
#[iden = "theme_color"]
Type,
Gray,
Red,
Orange,
Yellow,
Green,
Teal,
Blue,
Cyan,
Purple,
Pink,
}

6
migration/src/main.rs Normal file
View File

@ -0,0 +1,6 @@
use sea_orm_migration::prelude::*;
#[async_std::main]
async fn main() {
cli::run_cli(migration::Migrator).await;
}

View File

@ -1,7 +0,0 @@
DROP TABLE applications;
DROP TABLE verifications;
DROP TABLE files;
DROP TABLE registration_keys;
DROP TABLE users;
DROP TYPE role;
DROP DOMAIN sonyflake;

View File

@ -1,71 +0,0 @@
-- Role enum
CREATE TYPE role AS ENUM ('user', 'admin');
-- Sonyflake type
CREATE DOMAIN sonyflake AS VARCHAR(20) NOT NULL;
-- Users table
CREATE TABLE users
(
id sonyflake PRIMARY KEY NOT NULL UNIQUE,
email VARCHAR(320) NOT NULL UNIQUE,
username VARCHAR(32) NOT NULL UNIQUE,
password VARCHAR(128) NOT NULL,
verified BOOLEAN DEFAULT false NOT NULL,
role role DEFAULT 'user'::role NOT NULL
);
-- API token table for applications
CREATE TABLE applications
(
id sonyflake PRIMARY KEY NOT NULL UNIQUE,
user_id sonyflake NOT NULL,
name VARCHAR(16) NOT NULL,
last_accessed timestamptz DEFAULT now() NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
-- A user may not have two duplicate application names
CREATE UNIQUE INDEX applications_name_uindex
ON applications (user_id, name);
-- Only one verification may exist per user
CREATE TABLE verifications
(
id SERIAL PRIMARY KEY NOT NULL UNIQUE,
code VARCHAR(72) NOT NULL UNIQUE,
user_id sonyflake NOT NULL UNIQUE,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
CREATE TABLE files
(
id sonyflake PRIMARY KEY NOT NULL UNIQUE,
name VARCHAR(32) NOT NULL UNIQUE,
original_name VARCHAR(256) NOT NULL,
uploader sonyflake NOT NULL,
hash VARCHAR(64) NOT NULL,
uploaded timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
size BIGINT NOT NULL,
-- Application needs to delete the files from the S3 container. This is precautionary for database
FOREIGN KEY (uploader) REFERENCES users (id) ON DELETE CASCADE
);
-- Two identical files can not exist if owned by the same user
CREATE UNIQUE INDEX files_user_hash_uindex
ON files (uploader, hash);
-- User registration keys
CREATE TABLE registration_keys
(
id sonyflake NOT NULL PRIMARY KEY,
iss_user sonyflake NOT NULL,
code uuid NOT NULL UNIQUE,
uses_left INTEGER NOT NULL DEFAULT 1,
expiry_date timestamptz,
FOREIGN KEY (iss_user) REFERENCES users (id) ON DELETE CASCADE
);

View File

@ -1,2 +0,0 @@
DROP TABLE settings;
DROP TYPE theme_color;

View File

@ -1,26 +0,0 @@
CREATE TYPE theme_color AS ENUM (
'gray',
'red',
'orange',
'yellow',
'green',
'teal',
'blue',
'cyan',
'purple',
'pink'
);
CREATE TABLE settings
(
-- Only true is allowed and this is set to unique. Prevents multiple settings rows
one_row_enforce BOOLEAN PRIMARY KEY DEFAULT TRUE NOT NULL UNIQUE,
app_name VARCHAR(64) DEFAULT 'Backpack' NOT NULL,
app_description TEXT DEFAULT 'A file host for all your needs' NOT NULL,
color theme_color DEFAULT 'purple'::theme_color NOT NULL,
CONSTRAINT one_row_unique CHECK (one_row_enforce)
);
-- Insert default settings at first, configuration done through web UI
INSERT INTO settings (one_row_enforce) VALUES (true);

View File

@ -1,2 +1,10 @@
## SeaORM Database models
These are automatically generated with some manual edits for sonyflake. Be careful when regenerating and make sure to generate to a new directory and cross reference the changes.
These are automatically generated with some manual edits for runtime defaults. Be careful when regenerating and make sure to generate to a new directory and cross reference the changes. Guidelines on how to do this are below and some steps can be skipped as long as you pay attention to what you are doing (eg. model has not changed at all).
## How to generate
1. Set `DATABASE_URL` to a proper database URI.
* Use postgres for this process. We support multiple databases but enum types can only be generated when using Postgres.
2. Either run the application once to automatically run migrations or run `sea-orm-cli migrate up`.
3. Run `sea-orm-cli generate entity -o entities`.
4. Cross reference changes in the `entities` folder and the `database/entity` folder and make sure you keep `ActiveModelBehavior`s on each file as-is since they specify the ID generator and applied defaults on each model.
5. Delete `entities` and make sure everything works properly (tests coming soon).

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
use sea_orm::{entity::prelude::*, Set};

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
use sea_orm::{entity::prelude::*, Set};

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.6.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
use crate::database::sonyflake::Sonyflake;

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
pub use super::applications::Entity as Applications;
pub use super::files::Entity as Files;

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
use sea_orm::{entity::prelude::*, Set};
use uuid::Uuid;
@ -10,7 +10,7 @@ use super::DB_SONYFLAKE;
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub iss_user: String,
pub issuer: String,
#[sea_orm(unique)]
pub code: Uuid,
pub uses_left: i32,
@ -21,7 +21,7 @@ pub struct Model {
pub enum Relation {
#[sea_orm(
belongs_to = "super::users::Entity",
from = "Column::IssUser",
from = "Column::Issuer",
to = "super::users::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
use sea_orm::entity::prelude::*;
use serde::Serialize;

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
use super::sea_orm_active_enums::ThemeColor;
use sea_orm::entity::prelude::*;

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
use super::DB_SONYFLAKE;
@ -21,14 +21,12 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::files::Entity")]
Files,
#[sea_orm(has_many = "super::applications::Entity")]
Applications,
#[sea_orm(has_one = "super::verifications::Entity")]
Verifications,
#[sea_orm(has_many = "super::files::Entity")]
Files,
#[sea_orm(has_many = "super::registration_keys::Entity")]
RegistrationKeys,
}
impl Related<super::applications::Entity> for Entity {
@ -49,12 +47,6 @@ impl Related<super::files::Entity> for Entity {
}
}
impl Related<super::registration_keys::Entity> for Entity {
fn to() -> RelationDef {
Relation::RegistrationKeys.def()
}
}
impl ActiveModelBehavior for ActiveModel {
fn new() -> Self {
Self {

View File

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.1
use sea_orm::entity::prelude::*;
@ -6,7 +6,7 @@ use sea_orm::entity::prelude::*;
#[sea_orm(table_name = "verifications")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub id: String,
#[sea_orm(unique)]
pub code: String,
#[sea_orm(unique)]

View File

@ -7,8 +7,7 @@ use config::StorageConfig;
use figlet_rs::FIGfont;
use indicatif::{ProgressBar, ProgressStyle};
use models::MessageResponse;
use sea_orm::{EntityTrait, SqlxPostgresConnector};
use sqlx::{migrate::Migrator, postgres::PgPoolOptions, Row};
use sea_orm::{ConnectionTrait, DatabaseConnection, DbBackend, EntityTrait, Statement};
use state::State;
use tokio::{
fs,
@ -19,7 +18,8 @@ use tokio::{
use internal::file::IMAGE_EXTS;
use utoipa::OpenApi;
use std::{convert::TryInto, ffi::OsStr, path::Path, time::Duration};
use migration::{Migrator, MigratorTrait};
use std::{convert::TryInto, ffi::OsStr, path::Path};
use actix_web::{
http::StatusCode,
@ -75,37 +75,20 @@ async fn main() -> std::io::Result<()> {
let config = config::Config::new();
let args = Args::parse();
// Create a SQLx pool for running migrations
let sqlx_pool = PgPoolOptions::new()
.max_connections(100)
.min_connections(1)
.acquire_timeout(Duration::from_secs(8))
.idle_timeout(Duration::from_secs(8))
.max_lifetime(Duration::from_secs(8))
.connect(&config.database_url)
let database = sea_orm::Database::connect(&config.database_url)
.await
.expect("Could not initialize database connection");
.unwrap();
let pg_version = sqlx::query("SELECT version()")
.fetch_one(&sqlx_pool)
.await
.unwrap()
.try_get("version")
.unwrap_or("unknown".to_string());
log::info!("Connected to the database ({})", pg_version);
log::info!(
"Connected to the database ({})",
get_db_version(&database).await.unwrap()
);
// Apply all pending migrations
if config.run_migrations {
let migrator = Migrator::new(Path::new("migrations")).await.unwrap();
migrator
.run(&sqlx_pool)
.await
.map_err(|e| anyhow::anyhow!(e.to_string()))
.unwrap();
Migrator::up(&database, None).await.unwrap();
}
let database = SqlxPostgresConnector::from_sqlx_postgres_pool(sqlx_pool);
let storage: Box<dyn StorageProvider> = match &config.storage_provider {
StorageConfig::Local(v) => {
if !v.path.exists() {
@ -249,6 +232,31 @@ async fn main() -> std::io::Result<()> {
.await
}
/// Get database version.
async fn get_db_version(database: &DatabaseConnection) -> Result<String, anyhow::Error> {
let version: String = database
.query_one(Statement::from_string(
database.get_database_backend(),
format!(
"select {}() as version;",
match database.get_database_backend() {
DbBackend::Sqlite => "sqlite_version",
_ => "version",
}
)
.to_string(),
))
.await?
.unwrap()
.try_get("", "version")?;
// SQLite version function is just a version number.
Ok(match database.get_database_backend() {
DbBackend::Sqlite => format!("SQLite {}", version),
_ => version,
})
}
/// Regenerate all thumbnails.
/// This is a multithreaded blocking operation used in the CLI.
async fn generate_thumbnails(state: &Data<State>) -> anyhow::Result<()> {

View File

@ -9,7 +9,7 @@ pub struct RegistrationKeyData {
pub id: String,
/// Admin which issued this registration key.
pub iss_user: String,
pub issuer: String,
/// Registration key.
#[component(value_type = String)]
@ -30,7 +30,7 @@ impl From<registration_keys::Model> for RegistrationKeyData {
id: model.id.to_string(),
code: model.code,
expiry_date: model.expiry_date,
iss_user: model.iss_user,
issuer: model.issuer,
uses_left: model.uses_left,
}
}

View File

@ -47,7 +47,7 @@ async fn create(
) -> Response<impl Responder> {
Ok(HttpResponse::Ok().json(RegistrationKeyData::from(
registration_keys::ActiveModel {
issueru: Set(user.id.to_owned()),
issuer: Set(user.id.to_owned()),
uses_left: Set(query.max_uses.unwrap_or(1)),
..Default::default()
}

View File

@ -8,8 +8,8 @@ use actix_multipart_extract::Multipart;
use actix_web::{delete, get, http::StatusCode, post, web, HttpResponse, Responder, Scope};
use nanoid::nanoid;
use sea_orm::{
sea_query::SimpleExpr, ActiveModelTrait, ColumnTrait, ConnectionTrait, DbBackend, EntityTrait,
ModelTrait, PaginatorTrait, QueryFilter, QueryOrder, Set, Statement,
sea_query::SimpleExpr, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, ModelTrait,
PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, QueryTrait, Set,
};
use serde_json::json;
use sha2::{Digest, Sha256};
@ -167,19 +167,20 @@ async fn stats(
state: web::Data<State>,
user: Auth<auth_role::User, DenyUnverified, AllowApplication>,
) -> Response<impl Responder> {
// Im not using an ORM for this query
let usage = state
.database
.query_one(Statement::from_sql_and_values(
DbBackend::Postgres,
r#"SELECT COALESCE(CAST(SUM(size) AS BIGINT), 0) FROM files WHERE uploader = $1"#,
vec![user.id.clone().into()],
))
.await?;
let expr = files::Entity::find()
.select_only()
.filter(files::Column::Uploader.eq(user.id.clone()))
.column_as(files::Column::Size.sum(), "sum")
.build(state.database.get_database_backend())
.to_owned();
println!("{}", expr);
let usage = state.database.query_one(expr).await?;
Ok(HttpResponse::Ok().json(FileStats {
usage: match usage {
Some(v) => v.try_get("", "coalesce")?,
Some(v) => v.try_get("", "sum")?,
None => 0,
},
}))