From ce4c250ba029fa4ec77f479ebed25fe6d11251a2 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Thu, 7 Mar 2024 17:40:08 -0800 Subject: [PATCH] add migration and config obj --- .gitignore | 2 + Cargo.toml | 4 +- build.rs | 5 +++ .../20240308005811_users_invitations.sql | 40 +++++++++++++++++++ src/main.rs | 37 +++++++++++++++-- 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 build.rs create mode 100644 migrations/20240308005811_users_invitations.sql diff --git a/.gitignore b/.gitignore index fedaa2b..1b3cb3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target .env +*.db +*.db-* diff --git a/Cargo.toml b/Cargo.toml index 5f241bf..19604fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ passwords = { version = "3", default-features = false } rand = { version = "0.8", default-features = false, features = ["getrandom"] } serde = { version = "1", default-features = false, features = ["derive"] } serde_json = { version = "1", default-features = false } -sqlx = { version = "0.7", default-features = false, features = ["runtime-tokio", "sqlite", "tls-none", "migrate"] } +sqlx = { version = "0.7", default-features = false, features = ["runtime-tokio", "sqlite", "tls-none", "migrate", "macros"] } thiserror = { version = "1" } time = { version = "0.3", default-features = false } tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] } tower-http = { version = "0.5", default-features = false, features = ["fs"] } tower-sessions = { version = "0.11", default-features = false, features = ["axum-core"] } -tower-sessions-sqlx-store = { version = "0.11.0", default-features = false, features = ["sqlite"] } +tower-sessions-sqlx-store = { version = "0.11", default-features = false, features = ["sqlite"] } unicode-segmentation = { version = "1", default-features = false } ureq = { version = "2", default-features = false, features = ["json", "tls"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7609593 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +// generated by `sqlx migrate build-script` +fn main() { + // trigger recompilation when a new migration is added + println!("cargo:rerun-if-changed=migrations"); +} \ No newline at end of file diff --git a/migrations/20240308005811_users_invitations.sql b/migrations/20240308005811_users_invitations.sql new file mode 100644 index 0000000..ef4c5ed --- /dev/null +++ b/migrations/20240308005811_users_invitations.sql @@ -0,0 +1,40 @@ +create table if not exists customers ( + id integer primary key, + username text not null unique, + receipt text not null unique, + billing_email text, + invitation id, + created_at int not null default (unixepoch()), + updated_at int not null default (unixepoch()), + foreign key (invitation) references invitations (id) +); +create index if not exists customers_username_dex on customers (lower(username)); +create index if not exists customers_email_dex on customers (lower(billing_email)); +create index if not exists customers_invitation_dex on customers (invitation); -- does this need to be created? it's already a foreign key + +create trigger if not exists update_last_updated_customers + after update on customers + when OLD.updated_at = NEW.updated_at or OLD.updated_at is null +BEGIN + update customers set updated_at = (select unixepoch()) where id=NEW.id; +END; + + +create table if not exists invitations ( + id integer primary key, + owner integer not null, + remaining integer not null default 1, + expires_at integer, + created_at integer not null default (unixepoch()), + updated_at integer not null default (unixepoch()), + foreign key (owner) references customers (id) +); +create index if not exists invitations_owner_dex on invitations (owner); + +create trigger if not exists update_updated_at_invitations + after update on invitations + when OLD.updated_at = NEW.updated_at or OLD.updated_at is null +BEGIN + update invitations set updated_at = (select unixepoch()) where id=NEW.id; +END; + diff --git a/src/main.rs b/src/main.rs index 94e160a..ee24175 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use axum::{ routing::{get, MethodRouter}, Router, }; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use tokio::net::TcpListener; use tower_http::services::ServeDir; use tower_sessions::{Expiry, SessionManagerLayer}; @@ -23,6 +24,14 @@ mod handlers; mod templates; mod user; +struct Config { + pub admin_token: String, + pub forgejo_url: String, + pub stripe_token: String, + pub annual_link: String, + pub monthly_link: String, +} + #[tokio::main] async fn main() { use handlers::handlers::{get_signup, payment_success, post_signup}; @@ -33,9 +42,10 @@ async fn main() { let assets_dir = std::env::current_dir().unwrap().join("assets"); let assets_svc = ServeDir::new(assets_dir.as_path()); - // just for signups - let pool = SqlitePool::connect("sqlite:memory:").await.unwrap(); - let session_store = SqliteStore::new(pool); + let pool = db().await; + sqlx::migrate!().run(&pool).await.unwrap(); + + let session_store = SqliteStore::new(pool.clone()); session_store.migrate().await.unwrap(); let session_layer = SessionManagerLayer::new(session_store) .with_secure(false) @@ -49,6 +59,7 @@ async fn main() { .stripped_clone("/payment_success/", get(payment_success)) .route("/payment_success/:receipt", get(payment_success)) .layer(session_layer) + .with_state(pool) .into_make_service(); let listener = mklistener().await; axum::serve(listener, app).await.unwrap(); @@ -57,7 +68,8 @@ async fn main() { //-************************************************************************ // li'l helpers //-************************************************************************ -fn init() { +fn init() -> Config { + use std::env::var; dotenvy::dotenv().expect("Could not read .env file."); env_logger::builder() .format(|buf, record| { @@ -65,6 +77,23 @@ fn init() { writeln!(buf, "{}: {}", ts, record.args()) }) .init(); + Config { + admin_token: var("ADMIN_TOKEN").unwrap(), + forgejo_url: var("FORGEJO_URL").unwrap(), + stripe_token: var("STRIPE_TOKEN").unwrap(), + annual_link: var("ANNUAL_LINK").unwrap(), + monthly_link: var("MONTHLY_LINK").unwrap(), + } +} + +async fn db() -> SqlitePool { + let dbfile = std::env::var("DATABASE_URL").unwrap(); + let opts = SqliteConnectOptions::new() + .foreign_keys(true) + .filename(dbfile) + .create_if_missing(true) + .optimize_on_close(true, None); + SqlitePoolOptions::new().connect_with(opts).await.unwrap() } async fn mklistener() -> TcpListener {