diff --git a/Cargo.lock b/Cargo.lock index 9e02df6..d8f0e51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,14 +430,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" [[package]] -name = "async-trait" -version = "0.1.66" +name = "async-compression" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" +checksum = "f658e2baef915ba0f26f1f7c42bfb8e12f532a01f449a090ded75ae7a07e9ba2" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -719,6 +732,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -1090,6 +1113,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1266,9 +1304,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -1389,6 +1427,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "http-range" version = "0.1.5" @@ -1419,6 +1468,43 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1522,6 +1608,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + [[package]] name = "is-terminal" version = "0.4.9" @@ -1772,6 +1864,24 @@ dependencies = [ "uuid", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -1858,6 +1968,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -2234,6 +2388,46 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "async-compression", + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "ring" version = "0.16.20" @@ -2405,6 +2599,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2427,6 +2630,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.16" @@ -2862,6 +3088,7 @@ dependencies = [ "actix-web", "actix-web-codegen-const-routes", "argon2-creds", + "async-trait", "cache-buster", "config", "csv-async", @@ -2875,6 +3102,7 @@ dependencies = [ "mktemp", "pretty_env_logger", "rand", + "reqwest", "rust-embed", "serde", "serde_json", @@ -2910,6 +3138,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -3055,6 +3304,16 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.12" @@ -3089,6 +3348,12 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -3122,6 +3387,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -3399,6 +3670,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3430,6 +3710,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -3647,6 +3939,16 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index a99259a..db88d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ name = "tests-migrate" path = "./src/tests-migrate.rs" [dependencies] -actix-web = "4.0.1" +actix-web = "4.3" actix-identity = "0.4.0" actix-session = { version = "0.6.1", features = ["cookie-session"]} actix-http = "3.0.4" @@ -72,6 +72,8 @@ tracing = { version = "0.1.37", features = ["log"] } tera = { version="1.17.1", features=["builtins"]} tokio = { version = "1.25.0", features = ["fs"] } csv-async = { version = "1.2.5", features = ["serde", "tokio"] } +async-trait = "0.1.68" +reqwest = { version = "0.11.18", features = ["json", "gzip"] } #tokio = "1.11.0" diff --git a/migrations/20230701082846_survey_mcaptcha_upload.sql b/migrations/20230701082846_survey_mcaptcha_upload.sql new file mode 100644 index 0000000..9d9287d --- /dev/null +++ b/migrations/20230701082846_survey_mcaptcha_upload.sql @@ -0,0 +1,22 @@ +CREATE TABLE IF NOT EXISTS survey_mcaptcha_hostname ( + url VARCHAR(3000) UNIQUE NOT NULL, + secret VARCHAR(100) UNIQUE NOT NULL, + ID SERIAL PRIMARY KEY NOT NULL +); + +CREATE TABLE IF NOT EXISTS survey_mcaptcha_campaign ( + campaign_id VARCHAR(100) NOT NULL, + public_id VARCHAR(100) NOT NULL, + url_id INTEGER NOT NULL references survey_mcaptcha_hostname(ID) ON DELETE CASCADE, + synced_till INTEGER NOT NULL DEFAULT 0, + ID SERIAL PRIMARY KEY NOT NULL +); + + +CREATE TABLE IF NOT EXISTS survey_mcaptcha_analytics ( + campaign_id INTEGER references survey_mcaptcha_campaign(ID) ON DELETE CASCADE, + time INTEGER NOT NULL, + difficulty_factor INTEGER NOT NULL, + worker_type VARCHAR(100) NOT NULL, + ID SERIAL PRIMARY KEY NOT NULL +); diff --git a/sqlx-data.json b/sqlx-data.json index dc5f264..95c8c85 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -1,616 +1,3 @@ { - "db": "PostgreSQL", - "0d22134cc5076304b7895827f006ee8269cc500f400114a7472b83f0f1c568b5": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Varchar", - "Text", - "Varchar" - ] - } - }, - "query": "INSERT INTO survey_admins \n (name , password, secret) VALUES ($1, $2, $3)" - }, - "10924f3726a45c3bc709118375d691f2867bbcd50dc47a000ac9bf3ff878c97c": { - "describe": { - "columns": [ - { - "name": "name", - "ordinal": 0, - "type_info": "Varchar" - }, - { - "name": "id", - "ordinal": 1, - "type_info": "Uuid" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "SELECT name, id FROM survey_campaigns ORDER BY id;" - }, - "117f1ae18f6a3936f27446b75b555951fe217d3a3cefe40a006fdd3cb31f0ac4": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int4" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Uuid", - "Uuid", - "Varchar", - "Varchar", - "Int4", - "Timestamptz", - "Text" - ] - } - }, - "query": "INSERT INTO survey_responses (\n user_id,\n campaign_id,\n device_user_provided,\n device_software_recognised,\n threads,\n submitted_at,\n submission_bench_type_id\n ) VALUES (\n $1, $2, $3, $4, $5, $6,\n (SELECT ID FROM survey_bench_type WHERE name = $7)\n )\n RETURNING ID;" - }, - "1373df097fa0e58b23a374753318ae53a44559aa0e7eb64680185baf1c481723": { - "describe": { - "columns": [ - { - "name": "password", - "ordinal": 0, - "type_info": "Text" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "SELECT password FROM survey_admins WHERE name = ($1)" - }, - "15a8484de6f035e56c34ce3f6979eadea81f125933f76261c8b3c8319d43bbe0": { - "describe": { - "columns": [ - { - "name": "name", - "ordinal": 0, - "type_info": "Varchar" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Uuid" - ] - } - }, - "query": "SELECT\n survey_admins.name\n FROM\n survey_admins\n INNER JOIN survey_campaigns ON\n survey_admins.ID = survey_campaigns.user_id\n WHERE\n survey_campaigns.ID = $1\n " - }, - "19686bfe8772cbc6831d46d18994e2b9aa40c7181eae9a31e51451cce95f04e8": { - "describe": { - "columns": [ - { - "name": "name", - "ordinal": 0, - "type_info": "Varchar" - }, - { - "name": "password", - "ordinal": 1, - "type_info": "Text" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "SELECT name, password FROM survey_admins WHERE email = ($1)" - }, - "1972be28a6bda2c3a3764a836e95c8cb0c5db277fc4c8a9b19951a03166c6492": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Text", - "Uuid" - ] - } - }, - "query": "DELETE \n FROM survey_campaigns \n WHERE \n user_id = (\n SELECT \n ID \n FROM \n survey_admins \n WHERE \n name = $1\n )\n AND\n id = ($2)" - }, - "1b7e17bfc949fa97e8dec1f95e35a02bcf3aa1aa72a1f6f6c8884e885fc3b953": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Varchar", - "Text", - "Varchar", - "Varchar" - ] - } - }, - "query": "insert into survey_admins \n (name , password, email, secret) values ($1, $2, $3, $4)" - }, - "2ccaecfee4d2f29ef5278188b304017719720aa986d680d4727a1facbb869c7a": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "DELETE FROM survey_admins WHERE name = ($1)" - }, - "43b3e771f38bf8059832169227705be06a28925af1b3799ffef5371d511fd138": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Timestamptz", - "Uuid" - ] - } - }, - "query": "\n INSERT INTO survey_users (created_at, id) VALUES($1, $2)" - }, - "536541ecf2e1c0403c74b6e2e09b42b73a7741ae4a348ff539ac410022e03ace": { - "describe": { - "columns": [ - { - "name": "exists", - "ordinal": 0, - "type_info": "Bool" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "SELECT EXISTS (SELECT 1 from survey_admins WHERE name = $1)" - }, - "55dde28998a6d12744806035f0a648494a403c7d09ea3caf91bf54869a81aa73": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Text", - "Text" - ] - } - }, - "query": "UPDATE survey_admins set password = $1\n WHERE name = $2" - }, - "57c673ad8529371d77aa305917cf680dd2273ead74c3583ef0322f472b1d33fd": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int4" - }, - { - "name": "device_software_recognised", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "threads", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "user_id", - "ordinal": 3, - "type_info": "Uuid" - }, - { - "name": "submitted_at", - "ordinal": 4, - "type_info": "Timestamptz" - }, - { - "name": "device_user_provided", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "name", - "ordinal": 6, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Uuid", - "Text", - "Int8", - "Int8" - ] - } - }, - "query": "SELECT\n survey_responses.ID,\n survey_responses.device_software_recognised,\n survey_responses.threads,\n survey_responses.user_id,\n survey_responses.submitted_at,\n survey_responses.device_user_provided,\n survey_bench_type.name\n FROM\n survey_responses\n INNER JOIN survey_bench_type ON\n survey_responses.submission_bench_type_id = survey_bench_type.ID\n WHERE\n survey_responses.campaign_id = (\n SELECT ID FROM survey_campaigns\n WHERE\n ID = $1\n AND\n user_id = (SELECT ID FROM survey_admins WHERE name = $2)\n )\n LIMIT $3 OFFSET $4" - }, - "58ec3b8f98c27e13ec2732f8ee23f6eb9845ac5d9fd97b1e5c9f2eed4b1f5693": { - "describe": { - "columns": [ - { - "name": "name", - "ordinal": 0, - "type_info": "Varchar" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - } - }, - "query": "SELECT name \n FROM survey_campaigns\n WHERE \n id = $1\n AND\n user_id = (SELECT ID from survey_admins WHERE name = $2)" - }, - "683707dbc847b37c58c29aaad0d1a978c9fe0657da13af99796e4461134b5a43": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - } - }, - "query": "UPDATE survey_admins set email = $1\n WHERE name = $2" - }, - "6a26daa84578aed2b2085697cb8358ed7c0a50ba9597fd387b4b09b0a8a154db": { - "describe": { - "columns": [ - { - "name": "exists", - "ordinal": 0, - "type_info": "Bool" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "SELECT EXISTS (SELECT 1 from survey_admins WHERE email = $1)" - }, - "70cc7bfc9b6ff5b68db70c069c0947d51bfc4a53cedc020016ee25ff98586c93": { - "describe": { - "columns": [ - { - "name": "name", - "ordinal": 0, - "type_info": "Varchar" - }, - { - "name": "id", - "ordinal": 1, - "type_info": "Uuid" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "SELECT \n name, id\n FROM \n survey_campaigns \n WHERE\n user_id = (\n SELECT \n ID\n FROM \n survey_admins\n WHERE\n name = $1\n )" - }, - "74c41e33f91cf31ea13582c8b3ca464544374842450d580517ca2bd01d67402e": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int4" - }, - { - "name": "device_software_recognised", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "threads", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "user_id", - "ordinal": 3, - "type_info": "Uuid" - }, - { - "name": "submitted_at", - "ordinal": 4, - "type_info": "Timestamptz" - }, - { - "name": "device_user_provided", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "name", - "ordinal": 6, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Uuid", - "Text", - "Text", - "Int8", - "Int8" - ] - } - }, - "query": "SELECT\n survey_responses.ID,\n survey_responses.device_software_recognised,\n survey_responses.threads,\n survey_responses.user_id,\n survey_responses.submitted_at,\n survey_responses.device_user_provided,\n survey_bench_type.name\n FROM\n survey_responses\n INNER JOIN survey_bench_type ON\n survey_responses.submission_bench_type_id = survey_bench_type.ID\n WHERE\n survey_bench_type.name = $3\n AND\n survey_responses.campaign_id = (\n SELECT ID FROM survey_campaigns\n WHERE\n ID = $1\n AND\n user_id = (SELECT ID FROM survey_admins WHERE name = $2)\n )\n LIMIT $4 OFFSET $5" - }, - "82feafc36533144e49ba374c8c47ca4aa0d6558a9803778ad28cfa7b62382c3e": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Text", - "Uuid", - "Varchar", - "Int4Array", - "Timestamptz" - ] - } - }, - "query": "\n INSERT INTO survey_campaigns (\n user_id, ID, name, difficulties, created_at\n ) VALUES(\n (SELECT id FROM survey_admins WHERE name = $1),\n $2, $3, $4, $5\n );" - }, - "858a4c06a5c1ba7adb79bcac7d42d106d09d0cbff10c197f2242dcb5c437a1df": { - "describe": { - "columns": [ - { - "name": "created_at", - "ordinal": 0, - "type_info": "Timestamptz" - }, - { - "name": "id", - "ordinal": 1, - "type_info": "Uuid" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [ - "Uuid" - ] - } - }, - "query": "SELECT\n created_at,\n ID\n FROM\n survey_users\n WHERE\n ID = $1\n " - }, - "9cdade613ce724631cc3f187510758ee0929e93ff3f8ce81fe35594756644246": { - "describe": { - "columns": [ - { - "name": "difficulties", - "ordinal": 0, - "type_info": "Int4Array" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Uuid" - ] - } - }, - "query": "SELECT difficulties FROM survey_campaigns WHERE id = $1;" - }, - "a721cfa249acf328c2f29c4cf8c2aeba1a635bcf49d18ced5474caa10b7cae4f": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int4", - "Int4", - "Float4" - ] - } - }, - "query": "INSERT INTO survey_benches \n (resp_id, difficulty, duration) \n VALUES ($1, $2, $3);" - }, - "ab951c5c318174c6538037947c2f52c61bcfe5e5be1901379b715e77f5214dd2": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - } - }, - "query": "UPDATE survey_admins set secret = $1\n WHERE name = $2" - }, - "b2619292aa6bd1ac38dca152cbe607b795a151ddc212361a3c6d8c70ea1c93eb": { - "describe": { - "columns": [ - { - "name": "duration", - "ordinal": 0, - "type_info": "Float4" - }, - { - "name": "difficulty", - "ordinal": 1, - "type_info": "Int4" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [ - "Int4" - ] - } - }, - "query": "SELECT\n duration,\n difficulty\n FROM\n survey_benches\n WHERE\n resp_id = $1\n " - }, - "c757589ef26a005e3285e7ab20d8a44c4f2e1cb125f8db061dd198cc380bf807": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - } - }, - "query": "UPDATE survey_admins set name = $1\n WHERE name = $2" - }, - "e9cf5d6d8c9e8327d5c809d47a14a933f324e267f1e7dbb48e1caf1c021adc3f": { - "describe": { - "columns": [ - { - "name": "secret", - "ordinal": 0, - "type_info": "Varchar" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "SELECT secret FROM survey_admins WHERE name = ($1)" - }, - "efa0e41910fa5bcb187ba9e2fc8f37bee5b25ffe9a2d175f39a69899bc559965": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Uuid" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "difficulties", - "ordinal": 2, - "type_info": "Int4Array" - }, - { - "name": "created_at", - "ordinal": 3, - "type_info": "Timestamptz" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "SELECT ID, name, difficulties, created_at FROM survey_campaigns" - }, - "fcdc5fe5d496eb516c805e64ec96d9626b74ab33cd6e75e5a08ae88967403b72": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int4", - "Uuid", - "Uuid" - ] - } - }, - "query": "INSERT INTO survey_response_tokens \n (resp_id, user_id, id)\n VALUES ($1, $2, $3);" - } + "db": "PostgreSQL" } \ No newline at end of file diff --git a/src/api/v1/mcaptcha/db.rs b/src/api/v1/mcaptcha/db.rs new file mode 100644 index 0000000..a6315bc --- /dev/null +++ b/src/api/v1/mcaptcha/db.rs @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2023 Aravinth Manivannan + * + * 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 . + */ +use url::Url; + +use crate::api::v1::get_random; +use crate::errors::*; +use crate::mcaptcha::PerformanceAnalytics; +use crate::Data; + +impl Data { + /// Check if an mCaptcha instance is registered on the database + pub async fn mcaptcha_url_exists(&self, url: &str) -> ServiceResult { + let res = sqlx::query!( + "SELECT EXISTS (SELECT 1 from survey_mcaptcha_hostname WHERE url = $1)", + url + ) + .fetch_one(&self.db) + .await?; + + let mut resp = false; + if let Some(x) = res.exists { + if x { + resp = true; + } + } + + Ok(resp) + } + + /// Register an mCaptcha instance + pub async fn mcaptcha_register_instance(&self, url: &str) -> ServiceResult { + let secret = get_random(32); + sqlx::query!( + "INSERT INTO survey_mcaptcha_hostname (url, secret) VALUES ($1, $2)", + url, + &secret, + ) + .execute(&self.db) + .await?; + Ok(secret) + } + + /// Update the secret of an mCaptcha instance + pub async fn mcaptcha_update_secret(&self, url: &str) -> ServiceResult { + let secret = get_random(32); + sqlx::query!( + "UPDATE survey_mcaptcha_hostname set secret = $1 WHERE url = $2", + &secret, + url + ) + .execute(&self.db) + .await?; + Ok(secret) + } + + /// Authenticate an mCaptcha instance and return its URL + pub async fn mcaptcha_authenticate_and_get_url( + &self, + secret: &str, + ) -> ServiceResult { + struct U { + url: String, + } + + let res = sqlx::query!( + "SELECT EXISTS ( + SELECT + url + FROM + survey_mcaptcha_hostname + WHERE secret = $1 + )", + secret + ) + .fetch_one(&self.db) + .await?; + + if !match res.exists { + Some(true) => true, + _ => false, + } { + return Err(ServiceError::WrongPassword); + } + + let url = sqlx::query_as!( + U, + "SELECT + url + FROM + survey_mcaptcha_hostname + WHERE + secret = $1; ", + secret + ) + .fetch_one(&self.db) + .await?; + + Ok(Url::parse(&url.url).unwrap()) + } + + /// Delete mCaptcha instance from database + pub async fn mcaptcha_delete_mcaptcha_instance( + &self, + url: &str, + secret: &str, + ) -> ServiceResult<()> { + sqlx::query!( + "DELETE FROM survey_mcaptcha_hostname WHERE secret = $1 AND url =$2", + secret, + url + ) + .execute(&self.db) + .await?; + Ok(()) + } + /// Delete mCaptcha camapign from database + pub async fn mcaptcha_delete_mcaptcha_campaign( + &self, + campaign_id: &uuid::Uuid, + secret: &str, + ) -> ServiceResult<()> { + let campaign_str = campaign_id.to_string(); + let res = sqlx::query!( + "DELETE FROM + survey_mcaptcha_campaign + WHERE + campaign_id = $1 + AND + url_id = ( + SELECT + ID + FROM + survey_mcaptcha_hostname + WHERE + secret = $2 + )", + &campaign_str, + secret + ) + .execute(&self.db) + .await?; + + Ok(()) + } + + /// Check if an mCaptcha instance campaign is registered on DB + pub async fn mcaptcha_campaign_is_registered( + &self, + campaign_id: &uuid::Uuid, + secret: &str, + ) -> ServiceResult { + let campaign_str = campaign_id.to_string(); + + let res = sqlx::query!( + "SELECT EXISTS ( + SELECT + ID + FROM + survey_mcaptcha_campaign + WHERE + campaign_id = $1 + AND + url_id = ( + SELECT + ID + FROM + survey_mcaptcha_hostname + WHERE + secret = $2 + ) + )", + &campaign_str, + secret + ) + .fetch_one(&self.db) + .await?; + + let mut resp = false; + if let Some(x) = res.exists { + if x { + resp = true; + } + } + Ok(resp) + } + + /// Register an mCaptcha instance campaign on DB + pub async fn mcaptcha_register_campaign( + &self, + campaign_id: &uuid::Uuid, + secret: &str, + ) -> ServiceResult<()> { + let campaign_str = campaign_id.to_string(); + let public_id = uuid::Uuid::new_v4(); + + sqlx::query!( + "INSERT INTO + survey_mcaptcha_campaign (campaign_id, public_id, url_id) + VALUES ($1, $2, (SELECT ID FROM survey_mcaptcha_hostname WHERE secret = $3));", + &campaign_str, + &public_id.to_string(), + secret, + ) + .execute(&self.db) + .await?; + Ok(()) + } + + /// Register an mCaptcha instance campaign on DB + pub async fn mcaptcha_get_campaign_public_id( + &self, + campaign_id: &uuid::Uuid, + secret: &str, + ) -> ServiceResult { + let campaign_str = campaign_id.to_string(); + struct S { + public_id: String, + } + + let res = sqlx::query_as!( + S, + "SELECT + public_id + FROM + survey_mcaptcha_campaign + WHERE + campaign_id = $1 + AND + url_id = (SELECT ID FROM survey_mcaptcha_hostname WHERE secret = $2);", + &campaign_str, + secret, + ) + .fetch_one(&self.db) + .await?; + + Ok(uuid::Uuid::parse_str(&res.public_id).unwrap()) + } + + /// Get an mCaptcha instance campaign checkpoint + pub async fn mcaptcha_get_checkpoint( + &self, + campaign_id: &uuid::Uuid, + secret: &str, + ) -> ServiceResult { + let campaign_str = campaign_id.to_string(); + + struct CheckPoint { + synced_till: i32, + } + + let checkpoint = sqlx::query_as!( + CheckPoint, + "SELECT + synced_till + FROM + survey_mcaptcha_campaign + WHERE + campaign_id = $1 + AND + url_id = ( + SELECT ID FROM survey_mcaptcha_hostname WHERE secret = $2 + );", + &campaign_str, + secret + ) + .fetch_one(&self.db) + .await?; + let checkpoint = checkpoint.synced_till as usize; + Ok(checkpoint) + } + + /// Set an mCaptcha instance campaign checkpoint + pub async fn mcaptcha_set_checkpoint( + &self, + campaign_id: &uuid::Uuid, + secret: &str, + checkpoint: usize, + ) -> ServiceResult<()> { + let campaign_str = campaign_id.to_string(); + sqlx::query!( + "UPDATE + survey_mcaptcha_campaign + SET + synced_till = $1 + WHERE + campaign_id = $2 + AND + url_id = ( + SELECT ID FROM survey_mcaptcha_hostname WHERE secret = $3 + ) + ", + checkpoint as i32, + &campaign_str, + secret + ) + .execute(&self.db) + .await?; + + Ok(()) + } + + /// Store mCaptcha instance campaign analytics + pub async fn mcaptcha_insert_analytics( + &self, + campaign_id: &uuid::Uuid, + secret: &str, + r: &PerformanceAnalytics, + ) -> ServiceResult<()> { + let campaign_str = campaign_id.to_string(); + sqlx::query!( + "INSERT INTO + survey_mcaptcha_analytics ( + campaign_id, time, difficulty_factor, worker_type + ) + VALUES (( + SELECT + ID + FROM + survey_mcaptcha_campaign + WHERE + campaign_id = $1 + AND + url_id = ( + SELECT ID FROM survey_mcaptcha_hostname WHERE secret = $2 + ) + ), $3, $4, $5 + );", + &campaign_str, + secret, + r.time as i32, + r.difficulty_factor as i32, + &r.worker_type, + ) + .execute(&self.db) + .await?; + Ok(()) + } + + /// fetch PoW analytics + pub async fn mcaptcha_analytics_fetch( + &self, + public_id: &uuid::Uuid, + limit: usize, + offset: usize, + ) -> ServiceResult> { + let public_id_str = public_id.to_string(); + struct P { + id: i32, + time: i32, + difficulty_factor: i32, + worker_type: String, + } + + impl From

for PerformanceAnalytics { + fn from(v: P) -> Self { + Self { + time: v.time as u32, + difficulty_factor: v.difficulty_factor as u32, + worker_type: v.worker_type, + id: v.id as usize, + } + } + } + + let mut c = sqlx::query_as!( + P, + "SELECT id, time, difficulty_factor, worker_type FROM survey_mcaptcha_analytics + WHERE + campaign_id = ( + SELECT + ID FROM survey_mcaptcha_campaign + WHERE + public_id = $1 + ) + ORDER BY ID + OFFSET $2 LIMIT $3 + ", + &public_id_str, + offset as i32, + limit as i32 + ) + .fetch_all(&self.db) + .await?; + let mut res = Vec::with_capacity(c.len()); + for i in c.drain(0..) { + res.push(i.into()) + } + + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use crate::{mcaptcha::PerformanceAnalytics, tests::*}; + + use url::Url; + + #[actix_rt::test] + async fn test_db_mcaptcha_works() { + let url = Url::parse("http://test_add_campaign.example").unwrap(); + let data = get_test_data().await; + let url_str = url.to_string(); + if data.mcaptcha_url_exists(&url_str).await.unwrap() { + let secret = data.mcaptcha_update_secret(&url_str).await.unwrap(); + data.mcaptcha_delete_mcaptcha_instance(&url_str, &secret) + .await + .unwrap(); + } + assert!(!data.mcaptcha_url_exists(&url_str).await.unwrap()); + + let secret = data.mcaptcha_register_instance(&url_str).await.unwrap(); + assert!(data.mcaptcha_url_exists(&url_str).await.unwrap()); + let secret2 = data.mcaptcha_update_secret(&url_str).await.unwrap(); + assert_ne!(secret2, secret); + let secret = secret2; + + assert_eq!( + data.mcaptcha_authenticate_and_get_url(&secret) + .await + .unwrap(), + url + ); + + let uuid = uuid::Uuid::new_v4(); + + if data + .mcaptcha_campaign_is_registered(&uuid, &secret) + .await + .unwrap() + { + data.mcaptcha_delete_mcaptcha_campaign(&uuid, &secret) + .await + .unwrap(); + } + + assert!(!data + .mcaptcha_campaign_is_registered(&uuid, &secret) + .await + .unwrap()); + data.mcaptcha_register_campaign(&uuid, &secret) + .await + .unwrap(); + assert!(data + .mcaptcha_campaign_is_registered(&uuid, &secret) + .await + .unwrap()); + + assert_eq!( + data.mcaptcha_get_checkpoint(&uuid, &secret).await.unwrap(), + 0 + ); + data.mcaptcha_set_checkpoint(&uuid, &secret, 1) + .await + .unwrap(); + assert_eq!( + data.mcaptcha_get_checkpoint(&uuid, &secret).await.unwrap(), + 1 + ); + + let analytics = PerformanceAnalytics { + id: 1, + time: 1, + difficulty_factor: 1, + worker_type: "foo".to_string(), + }; + data.mcaptcha_insert_analytics(&uuid, &secret, &analytics) + .await + .unwrap(); + + let public_id = data + .mcaptcha_get_campaign_public_id(&uuid, &secret) + .await + .unwrap(); + let db_analytics = data + .mcaptcha_analytics_fetch(&public_id, 50, 0) + .await + .unwrap(); + + assert_eq!(db_analytics.len(), 1); + assert_eq!(db_analytics[0].time, analytics.time); + assert_eq!( + db_analytics[0].difficulty_factor, + analytics.difficulty_factor + ); + assert_eq!(db_analytics[0].worker_type, analytics.worker_type); + + assert_eq!( + data.mcaptcha_analytics_fetch(&public_id, 50, 1) + .await + .unwrap(), + vec![] + ); + } +}