From 68b59ade8c21736e65393fff6d2efd9f95a7cd27 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Fri, 30 Jun 2023 01:48:06 +0530 Subject: [PATCH] feat: add psuedo ID support to publish campaign IDs --- Cargo.lock | 12 +++ db/db-core/src/lib.rs | 18 ++++ db/db-core/src/tests.rs | 26 ++++++ db/db-sqlx-maria/Cargo.toml | 1 + ...0629185331_mcaptcha_psuedo_campaign_id.sql | 13 +++ db/db-sqlx-maria/sqlx-data.json | 60 ++++++++++++++ db/db-sqlx-maria/src/lib.rs | 81 ++++++++++++++++++ db/db-sqlx-postgres/Cargo.toml | 1 + ...0230629174617_mcaptcha_psuedo_campaign.sql | 5 ++ db/db-sqlx-postgres/sqlx-data.json | 53 ++++++++++++ db/db-sqlx-postgres/src/lib.rs | 82 +++++++++++++++++++ 11 files changed, 352 insertions(+) create mode 100644 db/db-sqlx-maria/migrations/20230629185331_mcaptcha_psuedo_campaign_id.sql create mode 100644 db/db-sqlx-postgres/migrations/20230629174617_mcaptcha_psuedo_campaign.sql diff --git a/Cargo.lock b/Cargo.lock index b37fda7c..6a6dc121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -885,6 +885,7 @@ dependencies = [ "futures", "sqlx", "url", + "uuid", ] [[package]] @@ -897,6 +898,7 @@ dependencies = [ "futures", "sqlx", "url", + "uuid", ] [[package]] @@ -3392,6 +3394,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" +dependencies = [ + "getrandom", + "serde 1.0.143", +] + [[package]] name = "validator" version = "0.15.0" diff --git a/db/db-core/src/lib.rs b/db/db-core/src/lib.rs index 4a3a0ccc..ae46f70a 100644 --- a/db/db-core/src/lib.rs +++ b/db/db-core/src/lib.rs @@ -265,6 +265,24 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase { limit: usize, offset: usize, ) -> DBResult>; + + /// Create psuedo ID against campaign ID to publish analytics + async fn analytics_create_psuedo_id_if_not_exists( + &self, + captcha_id: &str, + ) -> DBResult<()>; + + /// Get psuedo ID from campaign ID + async fn analytics_get_psuedo_id_from_capmaign_id( + &self, + captcha_id: &str, + ) -> DBResult; + + /// Get campaign ID from psuedo ID + async fn analytics_get_capmaign_id_from_psuedo_id( + &self, + psuedo_id: &str, + ) -> DBResult; } #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] diff --git a/db/db-core/src/tests.rs b/db/db-core/src/tests.rs index b8699596..777a6f09 100644 --- a/db/db-core/src/tests.rs +++ b/db/db-core/src/tests.rs @@ -260,6 +260,31 @@ pub async fn database_works<'a, T: MCDatabase>( db.record_solve(c.key).await.unwrap(); db.record_confirm(c.key).await.unwrap(); + // analytics start + + db.analytics_create_psuedo_id_if_not_exists(c.key) + .await + .unwrap(); + let psuedo_id = db + .analytics_get_psuedo_id_from_capmaign_id(c.key) + .await + .unwrap(); + db.analytics_create_psuedo_id_if_not_exists(c.key) + .await + .unwrap(); + assert_eq!( + psuedo_id, + db.analytics_get_psuedo_id_from_capmaign_id(c.key) + .await + .unwrap() + ); + assert_eq!( + c.key, + db.analytics_get_capmaign_id_from_psuedo_id(&psuedo_id) + .await + .unwrap() + ); + let analytics = CreatePerformanceAnalytics { time: 0, difficulty_factor: 0, @@ -289,6 +314,7 @@ pub async fn database_works<'a, T: MCDatabase>( ); assert_eq!(db.fetch_solve(p.username, c.key).await.unwrap().len(), 1); assert_eq!(db.fetch_confirm(p.username, c.key).await.unwrap().len(), 1); + // analytics end // update captcha key; set key = username; db.update_captcha_key(p.username, c.key, p.username) diff --git a/db/db-sqlx-maria/Cargo.toml b/db/db-sqlx-maria/Cargo.toml index 7f419404..ab1aa8a7 100644 --- a/db/db-sqlx-maria/Cargo.toml +++ b/db/db-sqlx-maria/Cargo.toml @@ -13,6 +13,7 @@ async-trait = "0.1.51" db-core = {path = "../db-core"} futures = "0.3.15" sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "mysql", "time", "offline" ] } +uuid = { version = "1.4.0", features = ["v4", "serde"] } [dev-dependencies] actix-rt = "2" diff --git a/db/db-sqlx-maria/migrations/20230629185331_mcaptcha_psuedo_campaign_id.sql b/db/db-sqlx-maria/migrations/20230629185331_mcaptcha_psuedo_campaign_id.sql new file mode 100644 index 00000000..37c89045 --- /dev/null +++ b/db/db-sqlx-maria/migrations/20230629185331_mcaptcha_psuedo_campaign_id.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_psuedo_campaign_id ( + ID INT auto_increment, + PRIMARY KEY(ID), + psuedo_id varchar(100) NOT NULL UNIQUE, + config_id INT NOT NULL, + + CONSTRAINT `fk_mcaptcha_psuedo_campaign_id_config_id` + FOREIGN KEY (config_id) + REFERENCES mcaptcha_config (config_id) + ON DELETE CASCADE + ON UPDATE CASCADE + +); diff --git a/db/db-sqlx-maria/sqlx-data.json b/db/db-sqlx-maria/sqlx-data.json index bb5ad114..7f82e8d5 100644 --- a/db/db-sqlx-maria/sqlx-data.json +++ b/db/db-sqlx-maria/sqlx-data.json @@ -25,6 +25,31 @@ }, "query": "SELECT time FROM mcaptcha_pow_confirmed_stats \n WHERE \n config_id = (\n SELECT config_id FROM mcaptcha_config \n WHERE \n captcha_key = ?\n AND\n user_id = (\n SELECT \n ID FROM mcaptcha_users WHERE name = ?))\n ORDER BY time DESC" }, + "14dc89b2988b221fd24e4f319b1d48f5e6c65c760c30d11c9c29087f09cee23a": { + "describe": { + "columns": [ + { + "name": "captcha_key", + "ordinal": 0, + "type_info": { + "char_set": 224, + "flags": { + "bits": 4101 + }, + "max_size": 400, + "type": "VarString" + } + } + ], + "nullable": [ + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT\n captcha_key\n FROM\n mcaptcha_config\n WHERE\n config_id = (\n SELECT\n config_id\n FROM\n mcaptcha_psuedo_campaign_id\n WHERE\n psuedo_id = ?\n );" + }, "22e697114c3ed5b0156cdceab11a398f1ef3a804f482e1cd948bc615ef95fc92": { "describe": { "columns": [], @@ -154,6 +179,31 @@ }, "query": "INSERT INTO mcaptcha_pow_fetched_stats \n (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)" }, + "5ad1ef722a961183228d851813b9f50284520bf8cc8118c765b72c108daaf6fb": { + "describe": { + "columns": [ + { + "name": "psuedo_id", + "ordinal": 0, + "type_info": { + "char_set": 224, + "flags": { + "bits": 4101 + }, + "max_size": 400, + "type": "VarString" + } + } + ], + "nullable": [ + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT psuedo_id FROM\n mcaptcha_psuedo_campaign_id\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?));\n " + }, "5d5a106981345e9f62bc2239c00cdc683d3aaaa820d63da300dc51e3f6f363d3": { "describe": { "columns": [], @@ -406,6 +456,16 @@ }, "query": "UPDATE mcaptcha_users set email = ?\n WHERE name = ?" }, + "9e45969a0f79eab8caba41b0d91e5e3b85a1a68a49136f89fc90793c38f00041": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 2 + } + }, + "query": "\n INSERT INTO\n mcaptcha_psuedo_campaign_id (config_id, psuedo_id)\n VALUES (\n (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?)),\n ?\n );" + }, "9f10afb0f242f11c58389803c5e85e244cc59102b8929a21e3fcaa852d57a52c": { "describe": { "columns": [ diff --git a/db/db-sqlx-maria/src/lib.rs b/db/db-sqlx-maria/src/lib.rs index b71cc1f3..ef839f8e 100644 --- a/db/db-sqlx-maria/src/lib.rs +++ b/db/db-sqlx-maria/src/lib.rs @@ -22,6 +22,7 @@ use sqlx::mysql::MySqlPoolOptions; use sqlx::types::time::OffsetDateTime; use sqlx::ConnectOptions; use sqlx::MySqlPool; +use uuid::Uuid; pub mod errors; #[cfg(test)] @@ -968,6 +969,86 @@ impl MCDatabase for Database { Ok(res) } + + /// Create psuedo ID against campaign ID to publish analytics + async fn analytics_create_psuedo_id_if_not_exists( + &self, + captcha_id: &str, + ) -> DBResult<()> { + let id = Uuid::new_v4(); + sqlx::query!( + " + INSERT INTO + mcaptcha_psuedo_campaign_id (config_id, psuedo_id) + VALUES ( + (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?)), + ? + );", + captcha_id, + &id.to_string(), + ) + .execute(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + Ok(()) + } + + /// Get psuedo ID from campaign ID + async fn analytics_get_psuedo_id_from_capmaign_id( + &self, + captcha_id: &str, + ) -> DBResult { + struct ID { + psuedo_id: String, + } + + let res = sqlx::query_as!( + ID, + "SELECT psuedo_id FROM + mcaptcha_psuedo_campaign_id + WHERE + config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?)); + ", + captcha_id + ).fetch_one(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + Ok(res.psuedo_id) + } + + /// Get campaign ID from psuedo ID + async fn analytics_get_capmaign_id_from_psuedo_id( + &self, + psuedo_id: &str, + ) -> DBResult { + struct ID { + captcha_key: String, + } + + let res = sqlx::query_as!( + ID, + "SELECT + captcha_key + FROM + mcaptcha_config + WHERE + config_id = ( + SELECT + config_id + FROM + mcaptcha_psuedo_campaign_id + WHERE + psuedo_id = ? + );", + psuedo_id + ) + .fetch_one(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + Ok(res.captcha_key) + } } #[derive(Clone)] diff --git a/db/db-sqlx-postgres/Cargo.toml b/db/db-sqlx-postgres/Cargo.toml index 027b7a0e..27461e7d 100644 --- a/db/db-sqlx-postgres/Cargo.toml +++ b/db/db-sqlx-postgres/Cargo.toml @@ -13,6 +13,7 @@ async-trait = "0.1.51" db-core = {path = "../db-core"} futures = "0.3.15" sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] } +uuid = { version = "1.4.0", features = ["v4", "serde"] } [dev-dependencies] actix-rt = "2" diff --git a/db/db-sqlx-postgres/migrations/20230629174617_mcaptcha_psuedo_campaign.sql b/db/db-sqlx-postgres/migrations/20230629174617_mcaptcha_psuedo_campaign.sql new file mode 100644 index 00000000..0d847dc8 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20230629174617_mcaptcha_psuedo_campaign.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS mcaptcha_psuedo_campaign_id ( + id SERIAL PRIMARY KEY NOT NULL, + config_id INTEGER NOT NULL references mcaptcha_config(config_id) ON DELETE CASCADE, + psuedo_id varchar(100) NOT NULL UNIQUE +); diff --git a/db/db-sqlx-postgres/sqlx-data.json b/db/db-sqlx-postgres/sqlx-data.json index ae93fcc8..7fd3dc72 100644 --- a/db/db-sqlx-postgres/sqlx-data.json +++ b/db/db-sqlx-postgres/sqlx-data.json @@ -172,6 +172,26 @@ }, "query": "UPDATE mcaptcha_users set name = $1\n WHERE name = $2" }, + "21cdf28d8962389d22c8ddefdad82780f5316737e3d833623512aa12a54a026a": { + "describe": { + "columns": [ + { + "name": "key", + "ordinal": 0, + "type_info": "Varchar" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "SELECT\n key\n FROM\n mcaptcha_config\n WHERE\n config_id = (\n SELECT\n config_id\n FROM\n mcaptcha_psuedo_campaign_id\n WHERE\n psuedo_id = $1\n );" + }, "2b319a202bb983d5f28979d1e371f399125da1122fbda36a5a55b75b9c743451": { "describe": { "columns": [], @@ -446,6 +466,26 @@ }, "query": "INSERT INTO mcaptcha_users \n (name , password, secret) VALUES ($1, $2, $3)" }, + "839dfdfc3543b12128cb2b44bf356cd81f3da380963e5684ec3624a0ea4f9547": { + "describe": { + "columns": [ + { + "name": "psuedo_id", + "ordinal": 0, + "type_info": "Varchar" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "SELECT psuedo_id FROM\n mcaptcha_psuedo_campaign_id\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1));\n " + }, "84484cb6892db29121816bc5bff5702b9e857e20aa14e79d080d78ae7593153b": { "describe": { "columns": [ @@ -600,6 +640,19 @@ }, "query": "SELECT name, password FROM mcaptcha_users WHERE email = ($1)" }, + "c1bb8e02d1f9dc28322309d055de3c40ed4e1a1b9453a7e5a93a70e5186d762d": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text", + "Varchar" + ] + } + }, + "query": "\n INSERT INTO\n mcaptcha_psuedo_campaign_id (config_id, psuedo_id)\n VALUES (\n (SELECT config_id FROM mcaptcha_config WHERE key = ($1)),\n $2\n );" + }, "c2e167e56242de7e0a835e25004b15ca8340545fa0ca7ac8f3293157d2d03d98": { "describe": { "columns": [ diff --git a/db/db-sqlx-postgres/src/lib.rs b/db/db-sqlx-postgres/src/lib.rs index 75fb4d26..319c2f28 100644 --- a/db/db-sqlx-postgres/src/lib.rs +++ b/db/db-sqlx-postgres/src/lib.rs @@ -22,6 +22,7 @@ use sqlx::postgres::PgPoolOptions; use sqlx::types::time::OffsetDateTime; use sqlx::ConnectOptions; use sqlx::PgPool; +use uuid::Uuid; pub mod errors; #[cfg(test)] @@ -975,6 +976,87 @@ impl MCDatabase for Database { Ok(res) } + + /// Create psuedo ID against campaign ID to publish analytics + async fn analytics_create_psuedo_id_if_not_exists( + &self, + captcha_id: &str, + ) -> DBResult<()> { + let id = Uuid::new_v4(); + sqlx::query!( + " + INSERT INTO + mcaptcha_psuedo_campaign_id (config_id, psuedo_id) + VALUES ( + (SELECT config_id FROM mcaptcha_config WHERE key = ($1)), + $2 + );", + captcha_id, + &id.to_string(), + ) + .execute(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + Ok(()) + } + + /// Get psuedo ID from campaign ID + async fn analytics_get_psuedo_id_from_capmaign_id( + &self, + captcha_id: &str, + ) -> DBResult { + struct ID { + psuedo_id: String, + } + + let res = sqlx::query_as!( + ID, + "SELECT psuedo_id FROM + mcaptcha_psuedo_campaign_id + WHERE + config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1)); + ", + captcha_id + ) + .fetch_one(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + Ok(res.psuedo_id) + } + + /// Get campaign ID from psuedo ID + async fn analytics_get_capmaign_id_from_psuedo_id( + &self, + psuedo_id: &str, + ) -> DBResult { + struct ID { + key: String, + } + + let res = sqlx::query_as!( + ID, + "SELECT + key + FROM + mcaptcha_config + WHERE + config_id = ( + SELECT + config_id + FROM + mcaptcha_psuedo_campaign_id + WHERE + psuedo_id = $1 + );", + psuedo_id + ) + .fetch_one(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + Ok(res.key) + } } #[derive(Clone)]