add basic migrations

This commit is contained in:
sam 2024-01-15 20:45:39 +01:00
parent 00eca2801f
commit 97d089c284
10 changed files with 251 additions and 29 deletions

125
Cargo.lock generated
View file

@ -725,6 +725,12 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "hermit-abi"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]] [[package]]
name = "hex" name = "hex"
version = "0.4.3" version = "0.4.3"
@ -879,6 +885,10 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sqlx", "sqlx",
"tokio",
"toml",
"tracing",
"tracing-subscriber",
"ulid", "ulid",
"uuid", "uuid",
] ]
@ -1062,6 +1072,16 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "num-bigint-dig" name = "num-bigint-dig"
version = "0.8.4" version = "0.8.4"
@ -1111,6 +1131,16 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.32.2" version = "0.32.2"
@ -1126,6 +1156,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "owo-colors" name = "owo-colors"
version = "3.5.0" version = "3.5.0"
@ -1451,6 +1487,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -1892,6 +1937,7 @@ dependencies = [
"bytes", "bytes",
"libc", "libc",
"mio", "mio",
"num_cpus",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
@ -1946,6 +1992,40 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "toml"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.4.13" version = "0.4.13"
@ -2017,15 +2097,29 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
] ]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.18" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [ dependencies = [
"nu-ansi-term",
"sharded-slab", "sharded-slab",
"smallvec",
"thread_local", "thread_local",
"tracing-core", "tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@ -2231,6 +2325,28 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.52.0" version = "0.52.0"
@ -2372,6 +2488,15 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.32" version = "0.7.32"

3
config.identity.toml Normal file
View file

@ -0,0 +1,3 @@
database_url = "postgresql://foxchat:password@localhost/foxchat_ident_dev"
port = 3000
log_level = "DEBUG"

View file

@ -10,7 +10,9 @@ pub enum Payload {
recipients: Vec<String>, recipients: Vec<String>,
}, },
Hello, Hello,
Identify { token: String }, Identify {
token: String,
},
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]

View file

@ -1,3 +1,3 @@
mod event; mod event;
pub use event::{Payload, DispatchEvent}; pub use event::{DispatchEvent, Payload};

View file

@ -19,3 +19,7 @@ eyre = "0.6.11"
color-eyre = "0.6.2" color-eyre = "0.6.2"
rsa = { version = "0.9.6", features = ["serde"] } rsa = { version = "0.9.6", features = ["serde"] }
rand = "0.8.5" rand = "0.8.5"
toml = "0.8.8"
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] }
tracing-subscriber = "0.3.18"
tracing = "0.1.40"

54
identity/src/config.rs Normal file
View file

@ -0,0 +1,54 @@
use eyre::Result;
use serde::Deserialize;
use std::path::Path;
use std::{env, fs};
use tracing::Level;
pub const CONFIG_FILE: &str = "config.identity.toml";
#[derive(Deserialize)]
pub struct Config {
pub database_url: String,
pub port: u16,
pub auto_migrate: Option<bool>,
pub log_level: Option<String>,
}
impl Config {
pub fn load() -> Result<Self> {
let cwd = env::current_dir()?;
let config_file = Path::join(cwd.as_path(), Path::new(CONFIG_FILE));
println!("config file: {}", config_file.display());
let s = fs::read_to_string(config_file)?;
let config = toml::from_str(s.as_str())?;
Ok(config)
}
pub fn tracing_level(&self) -> Option<Level> {
match self
.log_level
.as_deref()
.unwrap_or("INFO")
{
"TRACE" => Some(Level::TRACE),
"DEBUG" => Some(Level::DEBUG),
"INFO" => Some(Level::INFO),
"WARN" => Some(Level::WARN),
"ERROR" => Some(Level::ERROR),
_ => None
}
}
}
impl Default for Config {
fn default() -> Self {
Config {
database_url: env::var("DATABASE_URL").unwrap_or("".into()),
port: 3000,
auto_migrate: None,
log_level: None,
}
}
}

View file

@ -1,30 +1,30 @@
use std::time::Duration;
use eyre::{OptionExt, Result}; use eyre::{OptionExt, Result};
use rsa::{RsaPrivateKey, RsaPublicKey};
use rsa::pkcs1::LineEnding; use rsa::pkcs1::LineEnding;
use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey}; use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey};
use sqlx::{Pool, Postgres}; use rsa::{RsaPrivateKey, RsaPublicKey};
use sqlx::postgres::PgPoolOptions; use sqlx::postgres::PgPoolOptions;
use sqlx::{Pool, Postgres};
use std::time::Duration;
pub async fn init_db(dsn: &str) -> Result<Pool<Postgres>> { pub async fn init(dsn: &str) -> Result<Pool<Postgres>> {
let pool = PgPoolOptions::new() let pool = PgPoolOptions::new()
.acquire_timeout(Duration::from_secs(2)) // Fail fast and don't hang .acquire_timeout(Duration::from_secs(2)) // Fail fast and don't hang
.max_connections(100) .max_connections(100)
.connect(dsn) .connect(dsn)
.await?; .await?;
init_instance(&pool)?;
Ok(pool) Ok(pool)
} }
const PRIVATE_KEY_BITS: usize = 2048; const PRIVATE_KEY_BITS: usize = 2048;
async fn init_instance(pool: &Pool<Postgres>) -> Result<()> { pub async fn init_instance(pool: &Pool<Postgres>) -> Result<()> {
let mut tx = pool.begin().await?; let mut tx = pool.begin().await?;
// Check if we already have an instance configuration // Check if we already have an instance configuration
let row = sqlx::query!("select exists(select * from instance)").fetch_one(&mut *tx).await?; let row = sqlx::query!("select exists(select * from instance)")
.fetch_one(&mut *tx)
.await?;
if row.exists.ok_or_eyre("exists was null")? { if row.exists.ok_or_eyre("exists was null")? {
return Ok(()); return Ok(());
} }
@ -34,11 +34,16 @@ async fn init_instance(pool: &Pool<Postgres>) -> Result<()> {
let priv_key = RsaPrivateKey::new(&mut rng, PRIVATE_KEY_BITS)?; let priv_key = RsaPrivateKey::new(&mut rng, PRIVATE_KEY_BITS)?;
let pub_key = RsaPublicKey::from(&priv_key); let pub_key = RsaPublicKey::from(&priv_key);
let priv_key_string = priv_key.to_pkcs8_pem(LineEnding::default())?; let priv_key_string = priv_key.to_pkcs8_pem(LineEnding::LF)?;
let pub_key_string = pub_key.to_public_key_pem(LineEnding::default())?; let pub_key_string = pub_key.to_public_key_pem(LineEnding::LF)?;
sqlx::query!("insert into instance (public_key, private_key) values ($1, $2)", sqlx::query!(
priv_key_string.to_string(), pub_key_string).execute(&mut *tx).await?; "insert into instance (public_key, private_key) values ($1, $2)",
priv_key_string.to_string(),
pub_key_string
)
.execute(&mut *tx)
.await?;
tx.commit().await?; tx.commit().await?;

View file

@ -1,14 +1,16 @@
mod model; mod config;
mod db; mod db;
mod model;
use color_eyre::eyre::Result; use crate::config::Config;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use ulid::Ulid; use color_eyre::eyre::Result;
use tracing::info;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
struct Cli { struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Command, command: Option<Command>,
} }
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
@ -17,18 +19,45 @@ enum Command {
Migrate, Migrate,
} }
fn main() -> Result<()> { #[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
let config = Config::load()?;
let args = Cli::parse(); let args = Cli::parse();
println!("{args:#?}");
println!("{}", match args.command { tracing_subscriber::fmt()
Command::Serve => "serving!", .with_max_level(config.tracing_level().unwrap_or(tracing::Level::INFO))
Command::Migrate => "migrating!" .init();
});
println!("{}", Ulid::new()); match args.command.unwrap_or(Command::Serve) {
Command::Serve => main_web(config).await,
Command::Migrate => main_migrate(config).await,
}
}
async fn main_migrate(config: Config) -> Result<()> {
info!("Connecting to database");
let pool = db::init(&config.database_url).await?;
info!("Migrating database");
sqlx::migrate!().run(&pool).await?;
info!("Migrated database");
Ok(())
}
async fn main_web(config: Config) -> Result<()> {
info!("Connecting to database");
let pool = db::init(&config.database_url).await?;
if config.auto_migrate.unwrap_or(false) {
info!("Auto-migrate is enabled, migrating database");
sqlx::migrate!().run(&pool).await?;
info!("Migrated database");
}
info!("Initializing instance data");
db::init_instance(&pool).await?;
info!("Initialized instance data!");
Ok(()) Ok(())
} }