From dba1f662a7aa262bca9c1111d3ba5d1f22809404 Mon Sep 17 00:00:00 2001 From: realaravinth <realaravinth@batsense.net> Date: Sun, 8 May 2022 20:02:17 +0530 Subject: [PATCH] feat: init postgres implementation via sqlx --- Cargo.lock | 25 ++++ Cargo.toml | 16 ++- db/db-sqlx-postgres/.gitignore | 2 + db/db-sqlx-postgres/Cargo.toml | 20 +++ .../20210310122154_mcaptcha_users.sql | 8 ++ .../20210310122617_mcaptcha_config.sql | 7 + .../20210310122902_mcaptcha_levels.sql | 6 + ...10430032935_mcaptcha_pow_fetched_stats.sql | 4 + ...210509135118_mcaptcha_pow_solved_stats.sql | 4 + ...509135154_mcaptcha_pow_confirmed_stats.sql | 4 + .../20210509151150_mcaptcha_notifications.sql | 10 ++ ...tcha_sitekey_user_provided_avg_traffic.sql | 6 + ...user_provided_avg_traffic_col_datatype.sql | 3 + db/db-sqlx-postgres/src/errors.rs | 40 ++++++ db/db-sqlx-postgres/src/lib.rs | 129 ++++++++++++++++++ db/db-sqlx-postgres/src/tests.rs | 28 ++++ 16 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 db/db-sqlx-postgres/.gitignore create mode 100644 db/db-sqlx-postgres/Cargo.toml create mode 100644 db/db-sqlx-postgres/migrations/20210310122154_mcaptcha_users.sql create mode 100644 db/db-sqlx-postgres/migrations/20210310122617_mcaptcha_config.sql create mode 100644 db/db-sqlx-postgres/migrations/20210310122902_mcaptcha_levels.sql create mode 100644 db/db-sqlx-postgres/migrations/20210430032935_mcaptcha_pow_fetched_stats.sql create mode 100644 db/db-sqlx-postgres/migrations/20210509135118_mcaptcha_pow_solved_stats.sql create mode 100644 db/db-sqlx-postgres/migrations/20210509135154_mcaptcha_pow_confirmed_stats.sql create mode 100644 db/db-sqlx-postgres/migrations/20210509151150_mcaptcha_notifications.sql create mode 100644 db/db-sqlx-postgres/migrations/20211202141927_mcaptcha_sitekey_user_provided_avg_traffic.sql create mode 100644 db/db-sqlx-postgres/migrations/20211218133703_change_user_provided_avg_traffic_col_datatype.sql create mode 100644 db/db-sqlx-postgres/src/errors.rs create mode 100644 db/db-sqlx-postgres/src/lib.rs create mode 100644 db/db-sqlx-postgres/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 3e3eee7d..97cd4f59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -835,6 +835,28 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "db-core" +version = "0.1.0" +dependencies = [ + "async-trait", + "serde 1.0.137", + "serde_json", + "thiserror", + "url", +] + +[[package]] +name = "db-sqlx-postgres" +version = "0.1.0" +dependencies = [ + "actix-rt", + "async-trait", + "db-core", + "sqlx", + "url", +] + [[package]] name = "derive_builder" version = "0.10.2" @@ -1629,6 +1651,8 @@ dependencies = [ "awc", "cache-buster", "config", + "db-core", + "db-sqlx-postgres", "derive_builder 0.11.1", "derive_more", "futures", @@ -3195,6 +3219,7 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde 1.0.137", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index bf3b2945..710dfd89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,15 @@ build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[workspace] +exclude = ["db/db-migrations"] +memebers = [".", "db/db-core", "db/db-sqlx-postgres"] + [[bin]] name = "mcaptcha" path = "./src/main.rs" -[[bin]] -name = "tests-migrate" -path = "./src/tests-migrate.rs" - [dependencies] actix-web = "4.0.1" actix = "0.13" @@ -76,6 +77,13 @@ lettre = { version = "0.10.0-rc.3", features = [ openssl = { version = "0.10.29", features = ["vendored"] } + +[dependencies.db-core] +path = "./db/db-core" + +[dependencies.db-sqlx-postgres] +path = "./db/db-sqlx-postgres" + [dependencies.my-codegen] git = "https://github.com/realaravinth/actix-web" package = "actix-web-codegen" diff --git a/db/db-sqlx-postgres/.gitignore b/db/db-sqlx-postgres/.gitignore new file mode 100644 index 00000000..4fffb2f8 --- /dev/null +++ b/db/db-sqlx-postgres/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/db/db-sqlx-postgres/Cargo.toml b/db/db-sqlx-postgres/Cargo.toml new file mode 100644 index 00000000..350aac87 --- /dev/null +++ b/db/db-sqlx-postgres/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "db-sqlx-postgres" +version = "0.1.0" +edition = "2021" +homepage = "https://mcaptcha.org" +repository = "https://github.com/mCaptcha/mCaptcha" +documentation = "https://mcaptcha.org/docs/" +license = "AGPLv3 or later version" +authors = ["realaravinth <realaravinth@batsense.net>"] + +[dependencies] +sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] } +db-core = {path = "../db-core"} +async-trait = "0.1.51" + +[dev-dependencies] +actix-rt = "2" +sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] } +db-core = {path = "../db-core", features = ["test"]} +url = { version = "2.2.2", features = ["serde"] } diff --git a/db/db-sqlx-postgres/migrations/20210310122154_mcaptcha_users.sql b/db/db-sqlx-postgres/migrations/20210310122154_mcaptcha_users.sql new file mode 100644 index 00000000..79e230aa --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20210310122154_mcaptcha_users.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_users ( + name VARCHAR(100) NOT NULL UNIQUE, + email VARCHAR(100) UNIQUE DEFAULT NULL, + email_verified BOOLEAN DEFAULT NULL, + secret varchar(50) NOT NULL UNIQUE, + password TEXT NOT NULL, + ID SERIAL PRIMARY KEY NOT NULL +); diff --git a/db/db-sqlx-postgres/migrations/20210310122617_mcaptcha_config.sql b/db/db-sqlx-postgres/migrations/20210310122617_mcaptcha_config.sql new file mode 100644 index 00000000..ba2dc9d6 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20210310122617_mcaptcha_config.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_config ( + config_id SERIAL PRIMARY KEY NOT NULL, + user_id INTEGER NOT NULL references mcaptcha_users(ID) ON DELETE CASCADE, + key varchar(100) NOT NULL UNIQUE, + name varchar(100) NOT NULL, + duration integer NOT NULL DEFAULT 30 +); diff --git a/db/db-sqlx-postgres/migrations/20210310122902_mcaptcha_levels.sql b/db/db-sqlx-postgres/migrations/20210310122902_mcaptcha_levels.sql new file mode 100644 index 00000000..e04963ac --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20210310122902_mcaptcha_levels.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_levels ( + config_id INTEGER references mcaptcha_config(config_id) ON DELETE CASCADE, + difficulty_factor INTEGER NOT NULL, + visitor_threshold INTEGER NOT NULL, + level_id SERIAL PRIMARY KEY NOT NULL +); diff --git a/db/db-sqlx-postgres/migrations/20210430032935_mcaptcha_pow_fetched_stats.sql b/db/db-sqlx-postgres/migrations/20210430032935_mcaptcha_pow_fetched_stats.sql new file mode 100644 index 00000000..0a770f42 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20210430032935_mcaptcha_pow_fetched_stats.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_pow_fetched_stats ( + config_id INTEGER references mcaptcha_config(config_id) ON DELETE CASCADE, + time timestamptz NOT NULL DEFAULT now() +); diff --git a/db/db-sqlx-postgres/migrations/20210509135118_mcaptcha_pow_solved_stats.sql b/db/db-sqlx-postgres/migrations/20210509135118_mcaptcha_pow_solved_stats.sql new file mode 100644 index 00000000..1f9d7214 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20210509135118_mcaptcha_pow_solved_stats.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_pow_solved_stats ( + config_id INTEGER references mcaptcha_config(config_id) ON DELETE CASCADE, + time timestamptz NOT NULL DEFAULT now() +); diff --git a/db/db-sqlx-postgres/migrations/20210509135154_mcaptcha_pow_confirmed_stats.sql b/db/db-sqlx-postgres/migrations/20210509135154_mcaptcha_pow_confirmed_stats.sql new file mode 100644 index 00000000..348c7008 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20210509135154_mcaptcha_pow_confirmed_stats.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_pow_confirmed_stats ( + config_id INTEGER references mcaptcha_config(config_id) ON DELETE CASCADE, + time timestamptz NOT NULL DEFAULT now() +); diff --git a/db/db-sqlx-postgres/migrations/20210509151150_mcaptcha_notifications.sql b/db/db-sqlx-postgres/migrations/20210509151150_mcaptcha_notifications.sql new file mode 100644 index 00000000..56e5e733 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20210509151150_mcaptcha_notifications.sql @@ -0,0 +1,10 @@ +-- Add migration script here +CREATE TABLE IF NOT EXISTS mcaptcha_notifications ( + id SERIAL PRIMARY KEY NOT NULL, + tx INTEGER NOT NULL references mcaptcha_users(ID) ON DELETE CASCADE, + rx INTEGER NOT NULL references mcaptcha_users(ID) ON DELETE CASCADE, + heading varchar(30) NOT NULL, + message varchar(250) NOT NULL, + read BOOLEAN DEFAULT NULL, + received timestamptz NOT NULL DEFAULT now() +); diff --git a/db/db-sqlx-postgres/migrations/20211202141927_mcaptcha_sitekey_user_provided_avg_traffic.sql b/db/db-sqlx-postgres/migrations/20211202141927_mcaptcha_sitekey_user_provided_avg_traffic.sql new file mode 100644 index 00000000..0c8697e2 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20211202141927_mcaptcha_sitekey_user_provided_avg_traffic.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_sitekey_user_provided_avg_traffic ( + config_id INTEGER PRIMARY KEY UNIQUE NOT NULL references mcaptcha_config(config_id) ON DELETE CASCADE, + avg_traffic INTEGER DEFAULT NULL, + peak_sustainable_traffic INTEGER DEFAULT NULL, + broke_my_site_traffic INTEGER DEFAULT NULL +); diff --git a/db/db-sqlx-postgres/migrations/20211218133703_change_user_provided_avg_traffic_col_datatype.sql b/db/db-sqlx-postgres/migrations/20211218133703_change_user_provided_avg_traffic_col_datatype.sql new file mode 100644 index 00000000..4d2aa195 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20211218133703_change_user_provided_avg_traffic_col_datatype.sql @@ -0,0 +1,3 @@ +ALTER TABLE mcaptcha_sitekey_user_provided_avg_traffic + ALTER COLUMN avg_traffic SET NOT NULL, + ALTER COLUMN peak_sustainable_traffic SET NOT NULL; diff --git a/db/db-sqlx-postgres/src/errors.rs b/db/db-sqlx-postgres/src/errors.rs new file mode 100644 index 00000000..74ea9484 --- /dev/null +++ b/db/db-sqlx-postgres/src/errors.rs @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +//! Error-handling utilities +use std::borrow::Cow; + +use db_core::dev::*; +use sqlx::Error; + +/// map postgres errors to [DBError](DBError) types +pub fn map_register_err(e: Error) -> DBError { + if let Error::Database(err) = e { + if err.code() == Some(Cow::from("23505")) { + let msg = err.message(); + if msg.contains("mcaptcha_users_username_key") { + unimplemented!(); + } else { + DBError::DBError(Box::new(Error::Database(err))) + } + } else { + DBError::DBError(Box::new(Error::Database(err))) + } + } else { + DBError::DBError(Box::new(e)) + } +} diff --git a/db/db-sqlx-postgres/src/lib.rs b/db/db-sqlx-postgres/src/lib.rs new file mode 100644 index 00000000..00bfb6a9 --- /dev/null +++ b/db/db-sqlx-postgres/src/lib.rs @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +use db_core::dev::*; +use std::str::FromStr; + +use sqlx::postgres::PgPoolOptions; +use sqlx::PgPool; +use sqlx::types::time::OffsetDateTime; + +pub mod errors; +#[cfg(test)] +pub mod tests; + +#[derive(Clone)] +pub struct Database { + pub pool: PgPool, +} + +/// Use an existing database pool +pub struct Conn(pub PgPool); + +/// Connect to databse +pub enum ConnectionOptions { + /// fresh connection + Fresh(Fresh), + /// existing connection + Existing(Conn), +} + +pub struct Fresh { + pub pool_options: PgPoolOptions, + pub url: String, +} + +pub mod dev { + pub use super::errors::*; + pub use super::Database; + pub use db_core::dev::*; + pub use prelude::*; + pub use sqlx::Error; +} + +pub mod prelude { + pub use super::*; + pub use db_core::prelude::*; +} + +#[async_trait] +impl Connect for ConnectionOptions { + type Pool = Database; + async fn connect(self) -> DBResult<Self::Pool> { + let pool = match self { + Self::Fresh(fresh) => fresh + .pool_options + .connect(&fresh.url) + .await + .map_err(|e| DBError::DBError(Box::new(e)))?, + Self::Existing(conn) => conn.0, + }; + Ok(Database { pool }) + } +} + +use dev::*; + +#[async_trait] +impl Migrate for Database { + async fn migrate(&self) -> DBResult<()> { + sqlx::migrate!("./migrations/") + .run(&self.pool) + .await + .map_err(|e| DBError::DBError(Box::new(e)))?; + Ok(()) + } +} + +#[async_trait] +impl MCDatabase for Database { + /// ping DB + async fn ping(&self) -> bool { + use sqlx::Connection; + + if let Ok(mut con) = self.pool.acquire().await { + con.ping().await.is_ok() + } else { + false + } + } +} + +fn now_unix_time_stamp() -> i64 { + OffsetDateTime::now_utc().unix_timestamp() +} + +// +//#[allow(non_snake_case)] +//struct InnerGistComment { +// ID: i64, +// owner: String, +// comment: Option<String>, +// gist_public_id: String, +// created: i64, +//} +// +//impl From<InnerGistComment> for GistComment { +// fn from(g: InnerGistComment) -> Self { +// Self { +// id: g.ID, +// owner: g.owner, +// comment: g.comment.unwrap(), +// gist_public_id: g.gist_public_id, +// created: g.created, +// } +// } +//} diff --git a/db/db-sqlx-postgres/src/tests.rs b/db/db-sqlx-postgres/src/tests.rs new file mode 100644 index 00000000..6d1624db --- /dev/null +++ b/db/db-sqlx-postgres/src/tests.rs @@ -0,0 +1,28 @@ + +/* + * Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +//use sqlx::postgres::PgPoolOptions; +//use std::env; +// +//use crate::*; +// +//use db_core::tests::*; +// +//#[actix_rt::test] +//async fn everyting_works() { +// unimplemented!(); +//}