Compare commits

...

190 commits

Author SHA1 Message Date
Aravinth Manivannan 2b85036aa6 Merge pull request 'chore(deps): update jest monorepo to v29 (major)' (#73) from renovate/major-jest-monorepo into master
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is running
Reviewed-on: #73
2024-06-08 21:44:10 +05:30
Renovate Bot c1878ed382 chore(deps): update jest monorepo to v29
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 15:21:34 +00:00
Aravinth Manivannan 3115eeedee Merge pull request 'chore(deps): update node.js to v20' (#74) from renovate/node-20.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #74
2024-06-08 20:36:51 +05:30
Aravinth Manivannan c811e77bd7 Merge pull request 'chore(deps): update dependency jsdom to v24' (#67) from renovate/jsdom-24.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #67
2024-06-08 20:36:41 +05:30
Aravinth Manivannan 9524a9fd15 Merge pull request 'chore(deps): update typescript-eslint monorepo to v5.62.0' (#44) from renovate/typescript-eslint-monorepo into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #44
2024-06-08 20:36:36 +05:30
Renovate Bot 05f27518d4 chore(deps): update node.js to v20
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 13:05:35 +00:00
Renovate Bot 3e7de1d759 chore(deps): update dependency jsdom to v24
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 13:05:09 +00:00
Renovate Bot e12a802619 chore(deps): update typescript-eslint monorepo to v5.62.0
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 13:04:50 +00:00
Aravinth Manivannan a19795a340 Merge pull request 'chore(deps): update dependency webpack-dev-server to v5' (#71) from renovate/webpack-dev-server-5.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #71
2024-06-08 18:34:13 +05:30
Aravinth Manivannan 64554b057c Merge pull request 'chore(deps): update dependency @types/sinon to v17' (#65) from renovate/sinon-17.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #65
2024-06-08 18:34:04 +05:30
Aravinth Manivannan a83cc9b05e Merge pull request 'chore(deps): update dependency webpack to v5.91.0' (#41) from renovate/webpack-5.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #41
2024-06-08 18:33:55 +05:30
Aravinth Manivannan 3678fca26a Merge pull request 'fix(deps): update rust crate rust-embed to v8' (#77) from renovate/rust-embed-8.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #77
2024-06-08 16:10:52 +05:30
Renovate Bot e2df341f19 chore(deps): update dependency webpack-dev-server to v5
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 10:04:48 +00:00
Renovate Bot 49afa8c94b chore(deps): update dependency webpack to v5.91.0
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 10:04:13 +00:00
Aravinth Manivannan 98220fcbc8 Merge pull request 'chore(deps): update dependency sinon to v18' (#68) from renovate/sinon-18.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #68
2024-06-08 15:15:03 +05:30
Aravinth Manivannan 89e2f00382 Merge pull request 'chore(deps): update dependency eslint to v9' (#66) from renovate/major-eslint-monorepo into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #66
2024-06-08 15:14:57 +05:30
Aravinth Manivannan 518a5d58cb Merge pull request 'fix(deps): update rust crate validator to 0.18' (#60) from renovate/validator-0.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #60
2024-06-08 14:36:07 +05:30
Renovate Bot e0b777bc04 chore(deps): update dependency @types/sinon to v17
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 09:05:59 +00:00
Aravinth Manivannan b2a06a4883 Merge pull request 'fix(deps): update rust crate tokio to v1.38.0' (#58) from renovate/tokio-1.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #58
2024-06-08 14:35:38 +05:30
Aravinth Manivannan cd81262f69 Merge pull request 'fix(deps): update rust crate reqwest to 0.12.0' (#55) from renovate/reqwest-0.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #55
2024-06-08 14:35:32 +05:30
Aravinth Manivannan 316cc0589a Merge pull request 'fix(deps): update rust crate csv-async to v1.3.0' (#53) from renovate/csv-async-1.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #53
2024-06-08 14:35:25 +05:30
Aravinth Manivannan 3b3dc7b346 Merge pull request 'chore(deps): update dependency webpack-dev-server to v4.15.2' (#42) from renovate/webpack-dev-server-4.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #42
2024-06-08 14:09:02 +05:30
Renovate Bot cad1334fe4 fix(deps): update rust crate rust-embed to v8
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 07:38:17 +00:00
Renovate Bot 416fff2227 chore(deps): update dependency sinon to v18
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 07:37:39 +00:00
Renovate Bot 4b1e58456d chore(deps): update dependency eslint to v9
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 07:37:26 +00:00
Renovate Bot d937fe257f fix(deps): update rust crate validator to 0.18
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 07:37:10 +00:00
Renovate Bot b74b069727 fix(deps): update rust crate tokio to v1.38.0
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 07:37:07 +00:00
Renovate Bot ba89f7f378 fix(deps): update rust crate reqwest to 0.12.0
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 07:36:55 +00:00
Renovate Bot 3dec37e8b8 fix(deps): update rust crate csv-async to v1.3.0
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 07:36:52 +00:00
Renovate Bot 8da9c17714 chore(deps): update dependency webpack-dev-server to v4.15.2
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-08 07:36:26 +00:00
Aravinth Manivannan 648424fdf3 Merge pull request 'chore(deps): update postgres docker tag to v16' (#75) from renovate/postgres-16.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #75
2024-06-08 13:04:39 +05:30
Aravinth Manivannan c066d42272 Merge pull request 'chore(deps): update jamesives/github-pages-deploy-action action to v4' (#72) from renovate/jamesives-github-pages-deploy-action-4.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #72
2024-06-08 13:04:13 +05:30
Aravinth Manivannan bb9e8bae33 Merge pull request 'chore(deps): update dependency webpack-cli to v5' (#70) from renovate/webpack-cli-5.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #70
2024-06-08 13:04:06 +05:30
Aravinth Manivannan acd1dddcf4 Merge pull request 'chore(deps): update dependency typescript to v5' (#69) from renovate/typescript-5.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #69
2024-06-08 13:04:01 +05:30
Aravinth Manivannan 8e129c64d9 Merge pull request 'chore(deps): update dependency @types/jsdom to v21' (#64) from renovate/jsdom-21.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #64
2024-06-08 13:03:45 +05:30
Aravinth Manivannan 37fc8897af Merge pull request 'fix(deps): update rust crate url to v2.5.0' (#59) from renovate/url-2.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #59
2024-06-08 13:03:38 +05:30
Aravinth Manivannan 858fae15f6 Merge pull request 'fix(deps): update rust crate rust-embed to v6.8.1' (#56) from renovate/rust-embed-6.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #56
2024-06-08 13:01:49 +05:30
Aravinth Manivannan 8c3fe53071 Merge pull request 'fix(deps): update rust crate derive_builder to 0.20' (#54) from renovate/derive_builder-0.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #54
2024-06-08 13:01:39 +05:30
Aravinth Manivannan bacacdd192 Merge pull request 'fix(deps): update rust crate config to 0.14' (#52) from renovate/config-0.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #52
2024-06-08 13:01:28 +05:30
Aravinth Manivannan 1d7c8640de Merge pull request 'fix(deps): update rust crate actix-web to v4.6.0' (#51) from renovate/actix-web-4.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #51
2024-06-08 13:01:21 +05:30
Aravinth Manivannan a561059cce Merge pull request 'fix(deps): update rust crate actix-http to v3.7.0' (#46) from renovate/actix-http-3.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #46
2024-06-08 13:01:16 +05:30
Aravinth Manivannan 0e08bb1b77 Merge pull request 'chore(deps): update dependency ts-loader to v9.5.1' (#40) from renovate/ts-loader-9.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #40
2024-06-08 13:00:07 +05:30
Renovate Bot eb98fea3b0 chore(deps): update postgres docker tag to v16
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 22:05:19 +00:00
Aravinth Manivannan 290f6c5cc1 Merge pull request 'fix(deps): update rust crate actix-rt to v2.9.0' (#49) from renovate/actix-rt-2.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #49
2024-06-08 03:28:12 +05:30
Aravinth Manivannan bbf64ba1fb Merge pull request 'chore(deps): update postgres docker tag to v13.15' (#43) from renovate/postgres-13.x into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #43
2024-06-08 03:18:25 +05:30
Renovate Bot 6e6b814b86 fix(deps): update rust crate actix-web to v4.6.0
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 21:34:32 +00:00
Renovate Bot 171718b788 fix(deps): update rust crate actix-http to v3.7.0
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 21:34:24 +00:00
Renovate Bot f30463b0ac chore(deps): update dependency ts-loader to v9.5.1
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 21:33:28 +00:00
Aravinth Manivannan 6e68747978 Merge pull request 'chore(deps): update rust crate sqlx to v0.7.4' (#47) from renovate/sqlx-0.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #47
2024-06-08 02:58:02 +05:30
Aravinth Manivannan ab2915ece1 Merge pull request 'chore(deps): update dependency @wasm-tool/wasm-pack-plugin to v1.7.0' (#38) from renovate/wasm-tool-wasm-pack-plugin-1.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #38
2024-06-08 02:57:57 +05:30
Renovate Bot f00aba852c chore(deps): update jamesives/github-pages-deploy-action action to v4
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:58:08 +00:00
Renovate Bot 38b73dac8c chore(deps): update dependency webpack-cli to v5
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:57:48 +00:00
Renovate Bot 4f53b67d9b chore(deps): update dependency typescript to v5
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:57:40 +00:00
Renovate Bot 9e7b1740f4 chore(deps): update dependency @types/jsdom to v21
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:56:56 +00:00
Renovate Bot 06a5669157 fix(deps): update rust crate url to v2.5.0
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:56:37 +00:00
Renovate Bot dcd6e5619b fix(deps): update rust crate rust-embed to v6.8.1
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:56:10 +00:00
Renovate Bot 0737797c26 fix(deps): update rust crate derive_builder to 0.20
Some checks failed
renovate/artifacts Artifact file update failure
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:55:59 +00:00
Renovate Bot 5630b2e41c fix(deps): update rust crate config to 0.14
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:55:47 +00:00
Renovate Bot 18d59fc61b fix(deps): update rust crate actix-rt to v2.9.0
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:55:33 +00:00
Renovate Bot da0ce2c61b chore(deps): update postgres docker tag to v13.15
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:55:09 +00:00
Renovate Bot c421692595 chore(deps): update dependency @wasm-tool/wasm-pack-plugin to v1.7.0
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:54:35 +00:00
Renovate Bot d54c238529 chore(deps): update rust crate sqlx to v0.7.4
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 19:54:23 +00:00
Aravinth Manivannan dade31ba60 Merge pull request 'fix(deps): update rust crate urlencoding to v2.1.3' (#37) from renovate/urlencoding-2.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #37
2024-06-08 01:15:11 +05:30
Aravinth Manivannan af79e82881 Merge pull request 'chore(deps): update dependency eslint to v8.57.0' (#39) from renovate/eslint-monorepo into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #39
2024-06-08 01:15:02 +05:30
Aravinth Manivannan 3a619a681d Merge pull request 'chore(deps): update rust crate serde_json to v1.0.117' (#35) from renovate/serde_json-1.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #35
2024-06-08 00:38:05 +05:30
Renovate Bot 48186185c5 chore(deps): update dependency eslint to v8.57.0
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 17:07:18 +00:00
Renovate Bot 0efb1b7555 fix(deps): update rust crate urlencoding to v2.1.3
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 17:06:52 +00:00
Renovate Bot d15589d118 chore(deps): update rust crate serde_json to v1.0.117
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-07 17:06:44 +00:00
Aravinth Manivannan 88136c8ab5 Merge pull request 'chore(deps): update dependency @types/node to v16.18.98' (#24) from renovate/node-16.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #24
2024-06-07 22:23:44 +05:30
Aravinth Manivannan 2685fdbe0e Merge pull request 'chore(deps): update dependency @types/sinon to v10.0.20' (#25) from renovate/sinon-10.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #25
2024-06-07 22:23:39 +05:30
Aravinth Manivannan 9192b32904 Merge pull request 'chore(deps): update dependency ts-node to v10.9.2' (#26) from renovate/ts-node-10.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #26
2024-06-07 22:23:35 +05:30
Aravinth Manivannan ed190a9f1a Merge pull request 'chore(deps): update dependency typescript to v4.9.5' (#27) from renovate/typescript-4.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #27
2024-06-07 22:23:31 +05:30
Aravinth Manivannan d44e982acd Merge pull request 'chore(deps): update rust crate mime to v0.3.17' (#28) from renovate/mime-0.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #28
2024-06-07 22:23:28 +05:30
Aravinth Manivannan 9d212797d5 Merge pull request 'fix(deps): update rust crate actix-files to v0.6.5' (#29) from renovate/actix-files-0.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #29
2024-06-07 22:23:24 +05:30
Aravinth Manivannan d2651f0800 Merge pull request 'fix(deps): update rust crate async-trait to v0.1.80' (#30) from renovate/async-trait-0.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #30
2024-06-07 22:23:21 +05:30
Aravinth Manivannan 5cb3bcdca5 Merge pull request 'fix(deps): update rust crate futures to v0.3.30' (#31) from renovate/rust-futures-monorepo into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #31
2024-06-07 22:23:17 +05:30
Aravinth Manivannan 21e89209a1 Merge pull request 'fix(deps): update rust crate log to v0.4.21' (#32) from renovate/log-0.x-lockfile into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #32
2024-06-07 22:23:13 +05:30
Aravinth Manivannan 39011330f3 Merge pull request 'fix(deps): update rust crate tracing to v0.1.40' (#33) from renovate/tokio-tracing-monorepo into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #33
2024-06-07 22:22:55 +05:30
Renovate Bot d94d6d7717 fix(deps): update rust crate tracing to v0.1.40
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:08:40 +00:00
Renovate Bot 84a8a4331c fix(deps): update rust crate log to v0.4.21
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:08:33 +00:00
Renovate Bot aad5797299 fix(deps): update rust crate futures to v0.3.30
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:08:28 +00:00
Renovate Bot bcc489c577 fix(deps): update rust crate async-trait to v0.1.80
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:08:24 +00:00
Renovate Bot 79cdf68b8f fix(deps): update rust crate actix-files to v0.6.5
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:08:21 +00:00
Renovate Bot 07910c1190 chore(deps): update rust crate mime to v0.3.17
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:08:07 +00:00
Renovate Bot 86b044703c chore(deps): update dependency typescript to v4.9.5
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:07:54 +00:00
Renovate Bot 253aaf4542 chore(deps): update dependency ts-node to v10.9.2
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:07:47 +00:00
Renovate Bot 34a203ec49 chore(deps): update dependency @types/sinon to v10.0.20
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:07:40 +00:00
Renovate Bot f46d680405 chore(deps): update dependency @types/node to v16.18.98
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-06-04 08:07:33 +00:00
Aravinth Manivannan 5ffbed57e0 Merge pull request 'chore: Configure Renovate' (#23) from renovate/configure into master
Reviewed-on: #23
2024-06-04 13:07:59 +05:30
Renovate Bot 711cdd64d2 Add renovate.json 2024-06-04 07:36:41 +00:00
Aravinth Manivannan ab2f2c0a90 Merge pull request 'fix: install libssk-dev to compile openssl' (#22) from fix-docker into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #22
2023-11-05 02:04:40 +05:30
Aravinth Manivannan ba379f1999
fix: use bookworm
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-11-05 00:07:53 +05:30
Aravinth Manivannan 0ba21e184d
fix: install libssk-dev to compile openssl
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-11-02 17:06:44 +05:30
Aravinth Manivannan abf4ded284 Merge pull request 'fix: use individual database for each test' (#21) from fix-flaky-tests into master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #21
2023-11-02 04:29:40 +05:30
Aravinth Manivannan cfc459dde1
fix: use individual database for each test
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-11-02 04:24:49 +05:30
Aravinth Manivannan ae1bc888f3 Merge pull request 'feat: API to retrieve percentile for benches' (#20) from feat-percentile into master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled
Reviewed-on: #20
2023-11-02 03:15:57 +05:30
Aravinth Manivannan 3ba7b591f5
feat: API to retrieve percentile for benches
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-11-01 19:47:52 +05:30
Aravinth Manivannan b48cc8ffc7 Merge pull request 'feat: reuse init' (#19) from reuse into master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #19
2023-11-01 19:47:35 +05:30
Aravinth Manivannan 76075099be
fix: CI: install libssl-dev
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-11-01 19:30:26 +05:30
Aravinth Manivannan 241ccab5fc
fix: use vendored openssl
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-11-01 17:59:02 +05:30
Aravinth Manivannan 802bf71325
feat: reuse
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-11-01 17:12:16 +05:30
Aravinth Manivannan 2879a4da01 Merge pull request 'mcaptcha-upload' (#17) from mcaptcha-upload into master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #17
2023-10-20 03:18:14 +05:30
Aravinth Manivannan be03da096e
fix: run tests sequentially to avoid race cond
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-10-20 03:07:44 +05:30
Aravinth Manivannan f17e38c531
fix: dont use mod db while migrating with tests-migrate
Some checks failed
ci/woodpecker/push/woodpecker Pipeline is pending
ci/woodpecker/pr/woodpecker Pipeline failed
2023-10-20 02:57:47 +05:30
Aravinth Manivannan dfa83b1031
feat: offline sqlx data
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-10-20 01:49:26 +05:30
Aravinth Manivannan 5e7d1cae65
feat: schedule download jobs on reqs from mCaptcha/mCaptcha 2023-10-20 01:42:54 +05:30
Aravinth Manivannan 3e5dca9069
feat: clean up auth method and include hostname in scheduler ctx 2023-10-20 01:41:46 +05:30
Aravinth Manivannan c8ecd29e94
feat: job runner to execute download requests from mCaptcha/mCaptcha 2023-10-20 01:40:32 +05:30
Aravinth Manivannan c0a125d5f1
feat: add db methods to manage scheduled jobs and their states 2023-10-19 22:11:31 +05:30
Aravinth Manivannan 6b93524027
feat: schedule and record job states. Create job states during migration 2023-10-19 11:29:59 +05:30
Aravinth Manivannan b5b83b955a
fix: make archive shutdown responsive
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-10-19 10:10:33 +05:30
Aravinth Manivannan 3c445411e9
fix: update parameters to mCaptcha/mCaptcha 2023-10-19 09:55:00 +05:30
Aravinth Manivannan 786a9afe22
feat: routes to accept analytics from mCaptcha/mCaptcha 2023-10-18 10:28:50 +05:30
Aravinth Manivannan cdbf6788f0
feat: HTTP client to talk to mCaptcha/mCaptcha 2023-10-18 10:28:50 +05:30
Aravinth Manivannan 0dc74c1c05
feat: helper subroutines to spin up docker DB container in make 2023-10-18 10:28:50 +05:30
Aravinth Manivannan 8e0e94f98b
feat: DB methods to save analytics from mCaptcha/mCaptcha 2023-10-18 10:28:48 +05:30
Aravinth Manivannan ca34646b0c Merge pull request 'update-sqlx' (#18) from update-sqlx into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #18
2023-10-17 22:48:41 +05:30
Aravinth Manivannan 43dc36554d
fix: upgrade docker img
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-10-17 22:30:22 +05:30
Aravinth Manivannan d4d08e9d9a
debug: disable linting 2023-10-17 22:30:21 +05:30
Aravinth Manivannan fb472ed6c6
chore: update deps, docker img and woodpecker ci def 2023-10-17 22:30:19 +05:30
Aravinth Manivannan dcfc290099
fix: sqlx offline data
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled
2023-03-14 20:47:51 +05:30
Aravinth Manivannan 6620cc6857 Merge pull request 'feat: Publish links to exports page and list puiblicly list all campaigns' (#16) from publish-results into master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #16
2023-03-14 20:20:40 +05:30
Aravinth Manivannan 4bfc6a180f
feat: add public navbars to home and bench pages
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-03-14 15:56:51 +05:30
Aravinth Manivannan b8e52ff82a
feat: add export page 2023-03-14 15:51:53 +05:30
Aravinth Manivannan 1e33e5303a
feat: serve files from export dir 2023-03-14 15:51:46 +05:30
Aravinth Manivannan 3d74a8ce89
feat: add public navbar 2023-03-14 15:51:28 +05:30
Aravinth Manivannan 740b1a331e
feat: get all campaigns on an instance 2023-03-14 15:50:27 +05:30
Aravinth Manivannan 1c1617d684
fix: sqlx offline data
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-03-13 00:34:04 +05:30
Aravinth Manivannan 6657dba05b Merge pull request 'Periodically publish results from mCaptcha/survey.' (#15) from publish-results into master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #15
2023-03-12 21:15:42 +05:30
Aravinth Manivannan a3f2c3632e
feat: publish benchmark data periodically (configurable)
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2023-03-12 20:11:06 +05:30
Aravinth Manivannan 9411c2ba9f
feat: read publication dir config and serve it 2023-03-12 20:10:40 +05:30
Aravinth Manivannan d2c52cc62c
feat: cleanup archiver and include tests 2023-03-08 17:38:24 +05:30
Aravinth Manivannan 604fca0a62
feat: archive campaign and benchmark data
DESCRIPTION
    FORMATS
	- Campaign configuration is stored in JSON format
	- Benchmark data is stored in CSV format

    DIRECTORY STRUCTURE
	Each campaign gets a separate directory. A campaign can have
	multiple archives. Archives are stored in directories whose names
	would be the same as the UNIX timestamp of when they were
	recorded.

	EXAMPLE
	    The example below shows three campaigns with one archive
	    each. Each archive is stored in a directory denoting the
	    moment in which the archive was generated. Each archive
	    includes campaign configuration and benchmark.

	    ```bash
		14:53 atm@lab archive → tree
		.
		├── 4e951e01-71ee-4a18-9b97-782965495ae3
		│   └── 1675329764
		│       ├── benchmark.csv
		│       └── challenge.json
		├── 9d16df08-dffc-484e-bbe2-10c00b431c7e
		│   └── 1675329764
		│       ├── benchmark.csv
		│       └── challenge.json
		└── fa9f7c24-afec-4505-adb9-8e0c3ce54d37
		    └── 1675329764
			├── benchmark.csv
			└── challenge.json

		7 directories, 6 files
		```
2023-03-08 17:36:45 +05:30
Aravinth Manivannan a44f6f1748
feat: add archive base_path in settings 2023-03-08 17:36:45 +05:30
Aravinth Manivannan ca0c847f04
feat: add NLnet funding details
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-03-08 17:19:47 +05:30
Aravinth Manivannan 9e153b22ca
fix: update sqlx and fix container build
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
closes: #14
2023-02-14 17:25:27 +05:30
Aravinth Manivannan 4f224d782a
Merge pull request #14 from mCaptcha/js-bench
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Collect JavaScript polyfill benchmark using mCaptcha/survey
2023-02-13 14:14:44 +05:30
Aravinth Manivannan d6a80fa1fe
fix: show next page link only when next_page is set
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-02-02 00:31:19 +05:30
Aravinth Manivannan 92ab34967d
feat: display filtered (wasm, js and all) filtered benchmarks 2023-02-02 00:30:51 +05:30
Aravinth Manivannan 9d128033ac
feat: fetch and test filtered benchmark results. 2023-02-02 00:29:52 +05:30
Aravinth Manivannan 7a2cc1646d
chore: linting 2023-02-02 00:29:24 +05:30
Aravinth Manivannan 1a466bbd3c
feat: display benchmark type 2023-02-01 18:19:06 +05:30
Aravinth Manivannan 0fa21911f0
feat: submit and store benchmark type 2023-02-01 18:18:50 +05:30
Aravinth Manivannan 49abe1eb65
fix: default campaign 2023-02-01 18:18:11 +05:30
Aravinth Manivannan 8f0673c637
chore: ignore non-import loading 2023-02-01 18:17:34 +05:30
Aravinth Manivannan 94eb76c188
feat: show timestamp in results page 2023-02-01 15:27:26 +05:30
Aravinth Manivannan 21ff6741a0
feat: add timestamp to benchmark submissions 2023-02-01 15:26:58 +05:30
Aravinth Manivannan 78daff1036
feat: benchmark using polyfil when wasm is not available 2023-01-27 00:18:26 +05:30
Aravinth Manivannan b41cd212b4
fix: rm winner instructions and unnecessary glue code inclusion 2023-01-26 21:09:33 +05:30
Aravinth Manivannan 6bce9877ab
feat: link to results page from admin campaign list page 2023-01-26 20:58:53 +05:30
Aravinth Manivannan ab3d496bca
feat: show campaign results in web UI 2023-01-26 20:58:32 +05:30
Aravinth Manivannan 79bd99d398
fix: page title 2023-01-26 20:58:07 +05:30
Aravinth Manivannan 82bb2f327a
chore: get page helper method 2023-01-26 20:57:38 +05:30
Aravinth Manivannan c576487333
fix: store and get submission ID
DESCRIPTION
    If the same survey_user submits the survey many times, their first
    submission ID was being used to record benchmarks from their future
    submissions. Which resulted in submissions with empty benchmarks.

    This patch gets the submission ID as the record is being created and
    records benches using that. Bug fix and one less query.
2023-01-26 20:55:23 +05:30
Aravinth Manivannan e9709ed744
feat: formmatting is being deprecated 2023-01-26 20:55:04 +05:30
Aravinth Manivannan e1b8836732
feat: make workflow to sqlx offline codegen 2023-01-26 20:54:40 +05:30
Aravinth Manivannan 0ac915b426
feat: REST API endpoint to return survey campaign results 2023-01-26 19:07:48 +05:30
Aravinth Manivannan aa63783794
feat: list survey responses per campaign 2023-01-26 18:25:52 +05:30
Aravinth Manivannan f6030577a0
fix: upload difficulty via web form 2023-01-26 18:25:22 +05:30
Aravinth Manivannan 7b4fa1e54c
feat: rm gift 2023-01-24 23:39:01 +05:30
Aravinth Manivannan dfaaf291ea
chore: update deps 2023-01-24 23:33:51 +05:30
Aravinth Manivannan 5a27b86c6f
chore: update deps 2023-01-24 23:32:12 +05:30
Aravinth Manivannan 6b608c6677
feat: finish porting pages to tera. Fix tests 2023-01-24 23:31:04 +05:30
Aravinth Manivannan b874c17362
feat: port panel to tera 2023-01-24 23:29:58 +05:30
Aravinth Manivannan 8e6a3afb4c
feat: port new campaign page to tera 2023-01-24 23:29:47 +05:30
Aravinth Manivannan e5bf7feebe
feat: port benchmark page to tera 2023-01-24 23:29:17 +05:30
Aravinth Manivannan 82d293a159
feat: port delete campaign to tera 2023-01-24 23:28:57 +05:30
Aravinth Manivannan 599777cbf3
feat: port list campaigns to tera 2023-01-24 23:28:22 +05:30
Aravinth Manivannan 67d50a3d80
feat: rm gift and port about page to tera 2023-01-24 23:27:43 +05:30
Aravinth Manivannan 9f0d38ab07
feat: define campaign doesnt exist error type 2023-01-24 23:00:08 +05:30
Aravinth Manivannan 1423fa73d9
feat: port nav component to tera 2023-01-24 19:26:19 +05:30
Aravinth Manivannan 1942d44283
feat: port footer compoenet to tera 2023-01-24 19:26:06 +05:30
Aravinth Manivannan d2f266c689
feat: port login page to tera 2023-01-24 19:25:56 +05:30
Aravinth Manivannan c645bf83a3
feat: port join page to tera 2023-01-24 19:25:23 +05:30
Aravinth Manivannan 23330a29a7
feat: port base component to tera 2023-01-24 19:24:57 +05:30
Aravinth Manivannan 9a15e11c5d
feat: port error component to tera 2023-01-24 19:24:18 +05:30
Aravinth Manivannan fe584190f5
feat: bootstrap tera 2023-01-24 19:23:29 +05:30
Aravinth Manivannan fdc10ff28d
chore: update deps and load tera 2023-01-24 19:22:39 +05:30
Aravinth Manivannan 29a5e065aa
feat: use settings from app context 2023-01-24 19:22:19 +05:30
Aravinth Manivannan b66d5e7c70
chore: linting 2023-01-24 19:21:54 +05:30
Aravinth Manivannan 33cd4e23bd
feat: load static assets info in serialize able form for tera integration 2023-01-24 19:02:52 +05:30
Aravinth Manivannan 73cfe1b95d
chore: lint, use actix_web_codegen_const_routes and serde all routes 2023-01-24 19:02:29 +05:30
Aravinth Manivannan 17307074c3
feat: make check 2023-01-24 19:02:07 +05:30
Aravinth Manivannan 654a898413
chore: rm PageError 2023-01-24 19:01:56 +05:30
Aravinth Manivannan 009ee5d11e
feat: load settings in application context 2023-01-24 19:01:30 +05:30
Aravinth Manivannan d5211cf946
feat: linting and load application context with test-env Modifications 2023-01-24 19:00:56 +05:30
Aravinth Manivannan c92aa10327
feat: load project links from configuration 2023-01-24 19:00:31 +05:30
Aravinth Manivannan 6fd79446de
feat: update js deps 2023-01-08 23:02:49 +05:30
Aravinth Manivannan f055f14a46
feat: add ci status badge
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-08 21:27:33 +05:30
Aravinth Manivannan 02bf357743
feat: switch to woodpecker
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-08 20:41:18 +05:30
147 changed files with 30153 additions and 6272 deletions

View file

@ -12,6 +12,7 @@ module.exports = {
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-types": "off",
indent: ["error", 2],
"linebreak-style": ["error", "unix"],

View file

@ -84,7 +84,7 @@ jobs:
- name: Deploy to GitHub Pages
if: matrix.version == 'stable' && (github.repository == 'mcapthca/survey')
uses: JamesIves/github-pages-deploy-action@3.7.1
uses: JamesIves/github-pages-deploy-action@v4.6.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages

2
.gitignore vendored
View file

@ -16,3 +16,5 @@ scripts/creds.py
__pycache__/
*.py[cod]
*$py.class
src/sailfish/
src/libcachebust_data.json

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n synced_till\n FROM\n survey_mcaptcha_campaign\n WHERE \n campaign_id = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "synced_till",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "05b7fe6d93a4c988e9eae32f4a57e369f9ddc703b8fd3251c6baa52b60c98a1d"
}

View file

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO survey_admins \n (name , password, secret) VALUES ($1, $2, $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "0d22134cc5076304b7895827f006ee8269cc500f400114a7472b83f0f1c568b5"
}

View file

@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "SELECT name, id FROM survey_campaigns ORDER BY id;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false
]
},
"hash": "10924f3726a45c3bc709118375d691f2867bbcd50dc47a000ac9bf3ff878c97c"
}

View file

@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"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;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Uuid",
"Uuid",
"Varchar",
"Varchar",
"Int4",
"Timestamptz",
"Text"
]
},
"nullable": [
false
]
},
"hash": "117f1ae18f6a3936f27446b75b555951fe217d3a3cefe40a006fdd3cb31f0ac4"
}

View file

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO survey_mcaptcha_upload_job_states \n (name) VALUES ($1) ON CONFLICT (name) DO NOTHING;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "11ff04344412d1a2e5fdb1ab654fe4e90c2ba897bb4889426031ffacc2ae06e4"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT password FROM survey_admins WHERE name = ($1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "password",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "1373df097fa0e58b23a374753318ae53a44559aa0e7eb64680185baf1c481723"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"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 ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false
]
},
"hash": "15a8484de6f035e56c34ce3f6979eadea81f125933f76261c8b3c8319d43bbe0"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE\n survey_mcaptcha_campaign\n SET\n synced_till = $1\n WHERE \n campaign_id = $2; ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Text"
]
},
"nullable": []
},
"hash": "163a1ab861234bbf52b1b1c03bbac0d37bbbb539146f93c6fba24ffd80ad1485"
}

View file

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE\n survey_mcaptcha_upload_jobs\n SET\n job_state = (SELECT ID FROM survey_mcaptcha_upload_job_states WHERE name = $1),\n scheduled_at = $2\n WHERE public_id = $3;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Timestamptz",
"Text"
]
},
"nullable": []
},
"hash": "18495d6198079fdb8e4806d8a59aa0a1abee44a8b568ce74fa275ab936e8362f"
}

View file

@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "SELECT name, password FROM survey_admins WHERE email = ($1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "password",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false
]
},
"hash": "19686bfe8772cbc6831d46d18994e2b9aa40c7181eae9a31e51451cce95f04e8"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"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)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid"
]
},
"nullable": []
},
"hash": "1972be28a6bda2c3a3764a836e95c8cb0c5db277fc4c8a9b19951a03166c6492"
}

View file

@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "insert into survey_admins \n (name , password, email, secret) values ($1, $2, $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "1b7e17bfc949fa97e8dec1f95e35a02bcf3aa1aa72a1f6f6c8884e885fc3b953"
}

View file

@ -0,0 +1,64 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n survey_mcaptcha_upload_jobs.ID,\n survey_mcaptcha_upload_jobs.public_id,\n survey_mcaptcha_campaign.campaign_id,\n survey_mcaptcha_campaign.public_id as campaign_public_id,\n survey_mcaptcha_upload_job_states.name,\n survey_mcaptcha_upload_jobs.created_at,\n survey_mcaptcha_upload_jobs.scheduled_at,\n survey_mcaptcha_upload_jobs.finished_at\n\n FROM survey_mcaptcha_upload_jobs\n INNER JOIN\n survey_mcaptcha_upload_job_states\n ON\n survey_mcaptcha_upload_job_states.ID = survey_mcaptcha_upload_jobs.job_state\n INNER JOIN\n survey_mcaptcha_campaign\n ON\n survey_mcaptcha_campaign.ID = survey_mcaptcha_upload_jobs.campaign_id\n WHERE\n survey_mcaptcha_upload_job_states.name = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "public_id",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "campaign_id",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "campaign_public_id",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "scheduled_at",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "finished_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
true,
true
]
},
"hash": "1e41c42d89762ff4dc4b60a534a54db2741b325727c01852cbc68ea8442d15ef"
}

View file

@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n public_id\n FROM\n survey_mcaptcha_campaign\n WHERE\n campaign_id = $1\n AND\n url_id = (SELECT ID FROM survey_mcaptcha_hostname WHERE secret = $2);",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "public_id",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "2904486838bed381aa00f6a1b1e9b860a74b07b15256f3764434901471ff820b"
}

View file

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM survey_admins WHERE name = ($1)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "2ccaecfee4d2f29ef5278188b304017719720aa986d680d4727a1facbb869c7a"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS (SELECT 1 from survey_mcaptcha_upload_job_states WHERE name = $1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "2d18e0fad79c6df26465f82eca20cdfca35a710f34a54ac115d23435762a3038"
}

View file

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO\n survey_mcaptcha_campaign (campaign_id, public_id, url_id)\n VALUES ($1, $2, (SELECT ID FROM survey_mcaptcha_hostname WHERE secret = $3));",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "38a517b011519ec80d35d12ea463e7aed1f25290a5f3e8b19c5aa781da362ae3"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO survey_users (created_at, id) VALUES($1, $2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Timestamptz",
"Uuid"
]
},
"nullable": []
},
"hash": "43b3e771f38bf8059832169227705be06a28925af1b3799ffef5371d511fd138"
}

View file

@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n difficulty\n FROM\n survey_benches\n WHERE\n duration <= $1\n ORDER BY difficulty ASC LIMIT 1 OFFSET $2;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "difficulty",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Float4",
"Int8"
]
},
"nullable": [
false
]
},
"hash": "52c16c2c0759140af6348ef7de56b74151a20532ceebc8ee41d079decee3acb5"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS (SELECT 1 from survey_admins WHERE name = $1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "536541ecf2e1c0403c74b6e2e09b42b73a7741ae4a348ff539ac410022e03ace"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE survey_admins set password = $1\n WHERE name = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "55dde28998a6d12744806035f0a648494a403c7d09ea3caf91bf54869a81aa73"
}

View file

@ -0,0 +1,61 @@
{
"db_name": "PostgreSQL",
"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",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "device_software_recognised",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "threads",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "user_id",
"type_info": "Uuid"
},
{
"ordinal": 4,
"name": "submitted_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "device_user_provided",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "name",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Uuid",
"Text",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
true,
false,
false,
false,
false
]
},
"hash": "57c673ad8529371d77aa305917cf680dd2273ead74c3583ef0322f472b1d33fd"
}

View file

@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"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)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
false
]
},
"hash": "58ec3b8f98c27e13ec2732f8ee23f6eb9845ac5d9fd97b1e5c9f2eed4b1f5693"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS (SELECT 1 from survey_mcaptcha_hostname WHERE url = $1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "5c1ad3208ece06ba7a503d650e15d06906e56018798cba2b4672c393327131aa"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(difficulty) FROM survey_benches WHERE duration <= $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Float4"
]
},
"nullable": [
null
]
},
"hash": "63370a30a4ff6d31292a3cb632c66184ccff75583e21df5ddf5e8872f710d3d2"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE survey_admins set email = $1\n WHERE name = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "683707dbc847b37c58c29aaad0d1a978c9fe0657da13af99796e4461134b5a43"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS (SELECT 1 from survey_admins WHERE email = $1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "6a26daa84578aed2b2085697cb8358ed7c0a50ba9597fd387b4b09b0a8a154db"
}

View file

@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO\n survey_mcaptcha_analytics (\n campaign_id, time, difficulty_factor, worker_type\n )\n VALUES ((\n SELECT\n ID\n FROM\n survey_mcaptcha_campaign\n WHERE \n campaign_id = $1\n ), $2, $3, $4\n );",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int4",
"Int4",
"Varchar"
]
},
"nullable": []
},
"hash": "6c8fda20aa4a9174a5b008032d493773274ebfbf9dc204d89609cdff1ebc0335"
}

View file

@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"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 )",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false
]
},
"hash": "70cc7bfc9b6ff5b68db70c069c0947d51bfc4a53cedc020016ee25ff98586c93"
}

View file

@ -0,0 +1,42 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, time, difficulty_factor, worker_type FROM survey_mcaptcha_analytics\n WHERE \n campaign_id = (\n SELECT \n ID FROM survey_mcaptcha_campaign \n WHERE \n public_id = $1\n )\n ORDER BY ID\n OFFSET $2 LIMIT $3\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "time",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "difficulty_factor",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "worker_type",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "714925a5209400a17bcafe23c34ce9546106e8bdd788c27ee579b278e671bcb0"
}

View file

@ -0,0 +1,64 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n survey_mcaptcha_upload_jobs.ID,\n survey_mcaptcha_upload_jobs.public_id,\n survey_mcaptcha_campaign.campaign_id,\n survey_mcaptcha_campaign.public_id as campaign_public_id,\n survey_mcaptcha_upload_job_states.name,\n survey_mcaptcha_upload_jobs.created_at,\n survey_mcaptcha_upload_jobs.scheduled_at,\n survey_mcaptcha_upload_jobs.finished_at\n\n FROM survey_mcaptcha_upload_jobs\n INNER JOIN\n survey_mcaptcha_upload_job_states\n ON\n survey_mcaptcha_upload_job_states.ID = survey_mcaptcha_upload_jobs.job_state\n INNER JOIN\n survey_mcaptcha_campaign\n ON\n survey_mcaptcha_campaign.ID = survey_mcaptcha_upload_jobs.campaign_id\n WHERE\n survey_mcaptcha_upload_jobs.public_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "public_id",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "campaign_id",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "campaign_public_id",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "scheduled_at",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "finished_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
true,
true
]
},
"hash": "722f2d297a318f9804c1388d427d069a315b45c0c85c0b344d34cd8928b22c9c"
}

View file

@ -0,0 +1,62 @@
{
"db_name": "PostgreSQL",
"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",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "device_software_recognised",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "threads",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "user_id",
"type_info": "Uuid"
},
{
"ordinal": 4,
"name": "submitted_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "device_user_provided",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "name",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Uuid",
"Text",
"Text",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
true,
false,
false,
false,
false
]
},
"hash": "74c41e33f91cf31ea13582c8b3ca464544374842450d580517ca2bd01d67402e"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS (\n SELECT\n url\n FROM\n survey_mcaptcha_hostname\n WHERE secret = $1\n )",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "7d764a7b1c2991dda7498f243c6e4bd83fdf431e3510f9afb0ef5e9b10f35181"
}

View file

@ -0,0 +1,18 @@
{
"db_name": "PostgreSQL",
"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 );",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid",
"Varchar",
"Int4Array",
"Timestamptz"
]
},
"nullable": []
},
"hash": "82feafc36533144e49ba374c8c47ca4aa0d6558a9803778ad28cfa7b62382c3e"
}

View file

@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n created_at,\n ID\n FROM\n survey_users\n WHERE\n ID = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 1,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false,
false
]
},
"hash": "858a4c06a5c1ba7adb79bcac7d42d106d09d0cbff10c197f2242dcb5c437a1df"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO survey_mcaptcha_hostname (url, secret) VALUES ($1, $2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "94205e3e65a8f6bf315a282ec8fcc64119dc08e5565925bb2a3f5fccf663b5ab"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT difficulties FROM survey_campaigns WHERE id = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "difficulties",
"type_info": "Int4Array"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false
]
},
"hash": "9cdade613ce724631cc3f187510758ee0929e93ff3f8ce81fe35594756644246"
}

View file

@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS (\n SELECT\n ID\n FROM\n survey_mcaptcha_campaign\n WHERE\n campaign_id = $1\n AND\n url_id = (\n SELECT\n ID\n FROM\n survey_mcaptcha_hostname\n WHERE\n secret = $2\n )\n )",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "9da39f618b9dea08360d4c1625650b5055de47a7e89f99ffc589b99d22b8ac9d"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM survey_mcaptcha_hostname WHERE secret = $1 AND url =$2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "a3cddc0ace32cfb7df70e171b2618c7fe6d7824bbfcbae8248905e927049528b"
}

View file

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO survey_benches \n (resp_id, difficulty, duration) \n VALUES ($1, $2, $3);",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Int4",
"Float4"
]
},
"nullable": []
},
"hash": "a721cfa249acf328c2f29c4cf8c2aeba1a635bcf49d18ced5474caa10b7cae4f"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE survey_admins set secret = $1\n WHERE name = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "ab951c5c318174c6538037947c2f52c61bcfe5e5be1901379b715e77f5214dd2"
}

View file

@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n duration,\n difficulty\n FROM\n survey_benches\n WHERE\n resp_id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "duration",
"type_info": "Float4"
},
{
"ordinal": 1,
"name": "difficulty",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false
]
},
"hash": "b2619292aa6bd1ac38dca152cbe607b795a151ddc212361a3c6d8c70ea1c93eb"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE survey_mcaptcha_hostname set secret = $1 WHERE url = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "baabef729999fe63426b3b2373f1ecbf294d4bfddbce04209269644f4a7511ed"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE survey_admins set name = $1\n WHERE name = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "c757589ef26a005e3285e7ab20d8a44c4f2e1cb125f8db061dd198cc380bf807"
}

View file

@ -0,0 +1,65 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n survey_mcaptcha_upload_jobs.ID,\n survey_mcaptcha_upload_jobs.public_id,\n survey_mcaptcha_campaign.campaign_id,\n survey_mcaptcha_campaign.public_id as campaign_public_id,\n survey_mcaptcha_upload_job_states.name,\n survey_mcaptcha_upload_jobs.created_at,\n survey_mcaptcha_upload_jobs.scheduled_at,\n survey_mcaptcha_upload_jobs.finished_at\n\n FROM survey_mcaptcha_upload_jobs\n INNER JOIN\n survey_mcaptcha_upload_job_states\n ON\n survey_mcaptcha_upload_job_states.ID = survey_mcaptcha_upload_jobs.job_state\n INNER JOIN\n survey_mcaptcha_campaign\n ON\n survey_mcaptcha_campaign.ID = survey_mcaptcha_upload_jobs.campaign_id\n WHERE\n survey_mcaptcha_campaign.campaign_id = $1\n AND\n survey_mcaptcha_upload_job_states.name = $2;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "public_id",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "campaign_id",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "campaign_public_id",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "scheduled_at",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "finished_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
true,
true
]
},
"hash": "ca41f4e15fa5c5657a525ed9385a92214b644194443ae165957d9659d30dc3f9"
}

View file

@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n survey_mcaptcha_campaign.campaign_id,\n survey_mcaptcha_upload_jobs.public_id,\n survey_mcaptcha_hostname.url\n FROM\n survey_mcaptcha_campaign\n INNER JOIN\n survey_mcaptcha_upload_jobs\n ON\n survey_mcaptcha_upload_jobs.campaign_id = survey_mcaptcha_campaign.ID\n INNER JOIN\n survey_mcaptcha_hostname\n ON\n survey_mcaptcha_hostname.ID = survey_mcaptcha_campaign.url_id\n WHERE\n survey_mcaptcha_upload_jobs.job_state = (\n SELECT ID FROM survey_mcaptcha_upload_job_states WHERE name = $1\n )\n AND\n survey_mcaptcha_upload_jobs.finished_at is NULL\n AND\n survey_mcaptcha_upload_jobs.scheduled_at is NULL\n ORDER BY created_at ASC;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "campaign_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "public_id",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "url",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "d7a099c6f381fd02ad6a114b0146e4e52f7886f0164d05ccd3f1818a2a70cf67"
}

View file

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM\n survey_mcaptcha_campaign\n WHERE\n campaign_id = $1\n AND\n url_id = (\n SELECT\n ID\n FROM\n survey_mcaptcha_hostname\n WHERE\n secret = $2\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "dbe5d5c450a50bb829a39e6149eb4e6307547120b10762140d250f163b584a23"
}

View file

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT secret FROM survey_admins WHERE name = ($1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "secret",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "e9cf5d6d8c9e8327d5c809d47a14a933f324e267f1e7dbb48e1caf1c021adc3f"
}

View file

@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO survey_mcaptcha_upload_jobs\n (campaign_id, job_state, created_at, public_id)\n VALUES (\n (SELECT ID FROM survey_mcaptcha_campaign WHERE campaign_id = $1),\n (SELECT ID FROM survey_mcaptcha_upload_job_states WHERE name = $2),\n $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Timestamptz",
"Varchar"
]
},
"nullable": []
},
"hash": "ebfc456dd76b3fb2e5484f935703ad6aa4712c782222f2015b92916827f81079"
}

View file

@ -0,0 +1,38 @@
{
"db_name": "PostgreSQL",
"query": "SELECT ID, name, difficulties, created_at FROM survey_campaigns",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "difficulties",
"type_info": "Int4Array"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "efa0e41910fa5bcb187ba9e2fc8f37bee5b25ffe9a2d175f39a69899bc559965"
}

View file

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE\n survey_mcaptcha_upload_jobs\n SET\n job_state = (SELECT ID FROM survey_mcaptcha_upload_job_states WHERE name = $1),\n finished_at = $2\n WHERE public_id = $3;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Timestamptz",
"Text"
]
},
"nullable": []
},
"hash": "fade9f99846165c34486f6492ece38148bf0dd2d79e1a4f97b8cbf04015ceff0"
}

View file

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO survey_response_tokens \n (resp_id, user_id, id)\n VALUES ($1, $2, $3);",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Uuid"
]
},
"nullable": []
},
"hash": "fcdc5fe5d496eb516c805e64ec96d9626b74ab33cd6e75e5a08ae88967403b72"
}

59
.woodpecker.yml Normal file
View file

@ -0,0 +1,59 @@
pipeline:
backend:
image: rust
environment:
- DATABASE_URL=postgres://postgres:password@database:5432/postgres
- GIT_HASH=8e77345f1597e40c2e266cb4e6dee74888918a61 # dummy value
- OPEN_API_DOCS=8e77345f1597e40c2e266cb4e6dee74888918a61
- COMPILED_DATE=2021-07-21
commands:
- apt-get update
- apt-get install -y ca-certificates curl gnupg tar wget libssl-dev
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
- NODE_MAJOR=18 echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
- apt-get -y install nodejs npm
- npm install --global yarn
- rustup component add rustfmt
- rustup component add clippy
- make dev-env
- make migrate
- make frontend
- make test
build_docker_img:
image: plugins/docker
when:
event: [pull_request]
settings:
dry_run: true
repo: mcaptcha/survey
tags: latest
build_and_publish_docker_img:
image: plugins/docker
when:
event: [push, tag, deployment]
settings:
username: mcaptcha
password:
from_secret: DOCKER_TOKEN
repo: mcaptcha/survey
tags: latest
# publish_bins:
# image: rust
# when:
# event: [push, tag, deployment]
# commands:
# - apt update
# - apt-get -y --no-install-recommends install gpg tar curl wget
# - echo -n "$RELEASE_BOT_GPG_SIGNING_KEY" | gpg --batch --import --pinentry-mode loopback
# - scripts/bin-publish.sh publish master latest $DUMBSERVE_PASSWORD
# secrets: [RELEASE_BOT_GPG_SIGNING_KEY, DUMBSERVE_PASSWORD, GPG_PASSWORD]
services:
database:
image: postgres
environment:
- POSTGRES_PASSWORD=password

3479
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ documentation = "https://github.con/mCaptcha/survey"
readme = "https://github.com/mCaptcha/survey/blob/master/README.md"
license = "AGPLv3 or later version"
authors = ["realaravinth <realaravinth@batsense.net>"]
edition = "2018"
edition = "2021"
default-run = "survey"
build = "build.rs"
@ -22,50 +22,58 @@ name = "tests-migrate"
path = "./src/tests-migrate.rs"
[dependencies]
actix-web = "4.0.1"
actix-identity = "0.4.0-beta.2"
actix-web = "4.3"
actix-identity = "0.4.0"
actix-session = { version = "0.6.1", features = ["cookie-session"]}
actix-http = "3.0.4"
actix-rt = "2"
actix-cors = "0.6.0-beta.2"
actix-cors = "0.6.1"
actix-files = "0.6.0"
actix-service = "2.0.0"
#actix = "0.12"
my-codegen = {package = "actix-web-codegen", git ="https://github.com/realaravinth/actix-web"}
actix-web-codegen-const-routes = "0.2.0"
#libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
futures = "0.3.15"
sqlx = { version = "0.5.9", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] }
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "postgres", "time", "uuid" ] }
#argon2-creds = "0.2.3"
argon2-creds = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"}
derive_builder = "0.11"
validator = { version = "0.14", features = ["derive"]}
derive_builder = "0.20"
validator = { version = "0.18", features = ["derive"]}
derive_more = "0.99"
config = "0.11"
config = "0.14"
serde = "1"
serde_json = "1"
pretty_env_logger = "0.4"
pretty_env_logger = "0.5"
log = "0.4"
lazy_static = "1.4"
url = "2.2"
url = { version = "2.2", features = ["serde"] }
urlencoding = "2.1.0"
rand = "0.8"
uuid = { version="0.8.2", features = ["v4"]}
uuid = { version = "1.4.1", features = ["v4", "serde"] }
mime_guess = "2.0.3"
rust-embed = "6.0.0"
rust-embed = "8.0.0"
#libcachebust = "0.3.0"
cache-buster = { git = "https://github.com/realaravinth/cache-buster" }
mime = "0.3.16"
sailfish = "0.3.2"
#sailfish = "0.3.2"
tracing = { version = "0.1.37", features = ["log"] }
tera = { version="1.17.1", features=["builtins"]}
tokio = { version = "1.25.0", features = ["fs", "macros"] }
csv-async = { version = "1.2.5", features = ["serde", "tokio"] }
async-trait = "0.1.68"
reqwest = { version = "0.12.0", features = ["json", "gzip", "native-tls-vendored"] }
#tokio = "1.11.0"
@ -77,9 +85,11 @@ version = "0.2"
[build-dependencies]
sqlx = { version = "0.5.9", features = [ "runtime-actix-rustls", "uuid", "postgres", "time", "offline" ] }
#serde_yaml = "0.8.17"
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "uuid", "postgres", "time"] }
serde_json = "1"
#yaml-rust = "0.4.5"
cache-buster = { version = "0.2.0", git = "https://github.com/realaravinth/cache-buster" }
mime = "0.3.16"
[dev-dependencies]
mktemp = "0.5.0"

View file

@ -1,4 +1,4 @@
FROM node:16.11-bullseye-slim as frontend
FROM node:20-bookworm-slim as frontend
LABEL org.opencontainers.image.source https://github.com/mCaptcha/survey
RUN apt-get update && apt-get install -y make
COPY package.json yarn.lock /src/
@ -8,14 +8,14 @@ RUN yarn install
COPY . .
RUN make frontend
FROM rust:1-slim-bullseye as rust
FROM rust:latest as rust
WORKDIR /src
RUN apt-get update && apt-get install -y git
RUN apt-get update && apt-get install -y git libssl-dev
COPY . /src
COPY --from=frontend /src/static/cache/bundle /src/static/cache/bundle
RUN cargo build --release
FROM debian:bullseye-slim
FROM debian:bookworm
RUN useradd -ms /bin/bash -u 1001 mcaptcha-survey
WORKDIR /home/mcaptcha-survey
COPY --from=rust /src/target/release/survey /usr/local/bin/

View file

@ -1,3 +1,15 @@
define deploy_dependencies
@-docker create --name ${db} \
-e POSTGRES_PASSWORD=password \
-p 5433:5432 \
postgres
docker start ${db}
endef
define run_migrations
cargo run --bin tests-migrate
endef
default: frontend ## Debug build
cargo build
@ -9,6 +21,9 @@ clean: ## Clean all build artifacts and dependencies
@-rm -rf ./static/cache/bundle
@-rm -rf ./assets
check: ## Check for syntax errors on all workspaces
cargo check --workspace --tests --all-features
coverage: migrate ## Generate HTML code coverage
cargo tarpaulin -t 1200 --out Html
@ -16,11 +31,25 @@ dev-env: ## Download development dependencies
cargo fetch
yarn install
env.db.recreate: ## Deploy dependencies
@-docker rm -f ${db}
$(call deploy_dependencies)
sleep 5
$(call run_migrations)
env.db: ## Deploy dependencies
$(call deploy_dependencies)
sleep 5
$(call run_migrations)
doc: ## Prepare documentation
cargo doc --no-deps --workspace --all-features
docker: ## Build docker images
docker build -t mcaptcha/survey:master -t mcaptcha/survey:latest .
docker buildx build -t mcaptcha/survey:master -t mcaptcha/survey:latest . --load
docker-publish: docker ## Build and publish docker images
docker push mcaptcha/survey:master
@ -40,7 +69,7 @@ lint: ## Lint codebase
yarn lint
migrate: ## Run database migrations
cargo run --bin tests-migrate
$(call run_migrations)
release: frontend ## Release build
cargo build --release
@ -48,10 +77,16 @@ release: frontend ## Release build
run: default ## Run debug build
cargo run
sqlx-offline-data: ## prepare sqlx offline data
cargo sqlx prepare \
--database-url=${DATABASE_URL} -- \
--all-features \
--bin survey
test: frontend ## Run tests
echo 'static/' && tree static || true
echo 'tree/' && tree assets || true
cargo test --all-features --no-fail-fast
cargo test --all-features --no-fail-fast -j 1
xml-test-coverage: migrate ## Generate cobertura.xml test coverage
cargo tarpaulin -t 1200 --out Xml

View file

@ -7,9 +7,8 @@
</p>
[![Docker](https://img.shields.io/docker/pulls/mcaptcha/survey)](https://hub.docker.com/r/mcaptcha/survey)
[![Build](https://github.com/mCaptcha/survey/actions/workflows/linux.yml/badge.svg)](https://github.com/mCaptcha/survey/actions/workflows/linux.yml)
[![dependency status](https://deps.rs/repo/github/mCaptcha/survey/status.svg)](https://deps.rs/repo/github/mCaptcha/survey)
[![codecov](https://codecov.io/gh/mCaptcha/survey/branch/master/graph/badge.svg)](https://codecov.io/gh/mCaptcha/survey)
[![status-badge](https://ci.batsense.net/api/badges/mCaptcha/survey/status.svg)](https://ci.batsense.net/mCaptcha/survey)
</div>
@ -41,3 +40,21 @@ published to fine-tune their CAPTCHA deployment.
## What data do you collect?
TODO: run program, record and share actual network traffic logs
## Funding
### NLnet
<div align="center">
<img
height="150px"
alt="NLnet NGIZero logo"
src="./docs/third-party/NGIZero-green.hex.svg"
/>
</div>
<br />
2023 development is funded through the [NGI0 Entrust
Fund](https://nlnet.nl/entrust), via [NLnet](https://nlnet.nl/). Please
see [here](https://nlnet.nl/project/mCaptcha/) for more details.

View file

@ -28,8 +28,13 @@ fn main() {
let git_hash = String::from_utf8(output.stdout).unwrap();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
let now = OffsetDateTime::now_utc().format("%y-%m-%d");
println!("cargo:rustc-env=COMPILED_DATE={}", &now);
let now = OffsetDateTime::now_utc();
println!(
"cargo:rustc-env=COMPILED_DATE={}-{}-{}",
now.year(),
now.month(),
now.day()
);
cache_bust();
}

View file

@ -1,7 +1,8 @@
debug = true
allow_registration = true
source_code = "https://github.com/mcaptcha/survey"
default_campaign = "b6b261fa-3ef9-4d7f-8852-339b8f81bb01"
default_campaign = "4e951e01-71ee-4a18-9b97-782965495ae3"
support_email="support@example.org"
[server]
# Please set a unique value, your kaizen instance's security depends on this being
@ -32,3 +33,14 @@ username = "postgres"
password = "password"
name = "postgres"
pool = 4
[publish]
dir = "/tmp/mcaptcha-survey"
duration = 3600
[footer]
about = "https://mcapthca.org/about"
donate = "https://mcapthca.org/donate"
thanks = "https://mcapthca.org/thanks"
privacy = "https://mcapthca.org/privacy"
security = "https://mcapthca.org/security"

View file

@ -10,7 +10,7 @@ services:
RUST_LOG: debug
postgres:
image: postgres:13.2
image: postgres:16.3
volumes:
- mcaptcha-survey-data:/var/lib/postgresql/
environment:

103
docs/third-party/NGIZero-green.hex.svg vendored Normal file
View file

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Ebene_1"
x="0px"
y="0px"
width="165.92125"
height="191.45087"
viewBox="0 0 165.92125 191.45086"
enable-background="new 0 0 198.425 198.425"
xml:space="preserve"
sodipodi:docname="NGIZero-green.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata4142"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs4140" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1007"
id="namedview4138"
showgrid="false"
inkscape:zoom="1.6820179"
inkscape:cx="-191.39267"
inkscape:cy="54.855534"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Ebene_1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<polygon
points="36.911,63.104 36.911,66.116 36.911,132.309 36.911,135.321 39.346,136.825 96.715,169.921 99.273,171.419 101.853,169.921 159.319,136.825 161.938,135.321 161.938,132.309 161.938,66.116 161.938,63.104 159.308,61.6 101.841,28.504 99.234,27.006 96.629,28.504 39.347,61.6 "
id="polygon4013"
style="fill:#96c00a;fill-opacity:1"
transform="matrix(1.3249745,0,0,1.3249745,-48.642464,-35.674938)" />
<polygon
points="161.712,62.925 161.712,131.589 99.212,167.589 36.712,131.589 36.712,62.925 99.212,26.925 "
id="polygon4015"
style="fill:#97bf00;fill-opacity:0.91764706"
transform="matrix(1.3249745,0,0,1.3249745,-48.642464,-35.674938)" />
<polygon
stroke-miterlimit="10"
points="157.712,65.379 157.712,133.046 99.212,166.88 40.712,133.046 40.712,65.379 99.212,31.546 "
id="Outerline"
transform="matrix(1.3249745,0,0,1.3249745,-48.642464,-35.674938)"
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-miterlimit:10"
inkscape:label="#outerline" />
<g
id="g4281"
transform="matrix(1.3249745,0,0,1.3249745,-47.067006,-23.859001)"><path
inkscape:connector-curvature="0"
id="path42"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.55783975"
d="m 133.45691,60.461638 v 0 c 2.27263,0 4.11462,1.841988 4.11462,4.114628 v 27.330241 c 0,2.27264 -1.84199,4.114628 -4.11462,4.114628 -2.27264,0 -4.11463,-1.841988 -4.11463,-4.114628 V 64.576266 c 0,-2.27264 1.84199,-4.114628 4.11463,-4.114628" /><g
transform="matrix(0.55783976,0,0,-0.55783976,120.13631,77.682765)"
id="g44"><path
inkscape:connector-curvature="0"
id="path46"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 H -0.506 C -0.57,0 -0.633,-0.008 -0.698,-0.01 -0.762,-0.008 -0.825,0 -0.89,0 h -7.283 c -3.929,0 -7.359,-2.965 -7.613,-6.885 -0.278,-4.296 3.124,-7.867 7.361,-7.867 0.776,0 1.343,-0.754 1.111,-1.494 -0.658,-2.088 -2.341,-3.751 -4.547,-4.333 -2.074,-0.547 -4.276,-0.821 -6.605,-0.821 -4.007,0 -7.574,0.865 -10.7,2.595 -3.127,1.73 -5.57,4.144 -7.331,7.24 -1.761,3.096 -2.641,6.617 -2.641,10.564 0,4.006 0.88,7.558 2.641,10.654 1.761,3.097 4.219,5.493 7.377,7.195 3.156,1.698 6.768,2.549 10.836,2.549 4.681,0 8.865,-1.269 12.55,-3.807 2.341,-1.612 5.524,-1.588 7.757,0.171 3.48,2.741 3.289,8.045 -0.315,10.452 -1.7,1.136 -3.538,2.112 -5.512,2.928 -4.553,1.881 -9.623,2.823 -15.208,2.823 -6.679,0 -12.69,-1.412 -18.03,-4.235 -5.344,-2.822 -9.517,-6.738 -12.522,-11.747 -3.005,-5.008 -4.508,-10.67 -4.508,-16.983 0,-6.315 1.503,-11.975 4.508,-16.984 3.005,-5.009 7.148,-8.924 12.43,-11.747 5.282,-2.824 11.231,-4.235 17.849,-4.235 4.613,0 9.197,0.699 13.751,2.095 0.045,0.014 0.091,0.028 0.136,0.042 7.104,2.202 11.884,8.86 11.884,16.297 v 9.047 C 6.486,-2.904 3.583,0 0,0" /></g><g
transform="matrix(0.55783976,0,0,-0.55783976,85.80763,64.525332)"
id="g48"><path
inkscape:connector-curvature="0"
id="path50"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 v -49.176 c 0,-4.023 -3.262,-7.285 -7.286,-7.285 h -1.381 c -2.181,0 -4.247,0.977 -5.631,2.662 l -24.229,29.505 c -1.804,2.197 -5.368,0.921 -5.368,-1.922 v -22.96 c 0,-4.023 -3.261,-7.285 -7.285,-7.285 -4.023,0 -7.285,3.262 -7.285,7.285 V 0 c 0,4.024 3.262,7.285 7.285,7.285 h 1.468 c 2.184,0 4.253,-0.979 5.636,-2.669 l 24.135,-29.475 c 1.802,-2.202 5.37,-0.927 5.37,1.918 V 0 c 0,4.024 3.261,7.285 7.285,7.285 C -3.262,7.285 0,4.024 0,0" /></g></g><g
aria-label="Z E R O"
transform="matrix(0.94681934,0,0,0.94681934,-209.97267,182.03385)"
style="font-variant:normal;font-weight:600;font-stretch:normal;font-size:31.76000023px;font-family:'Montserrat SemiBold';-inkscape-font-specification:Montserrat-SemiBold;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:0.7171717;fill-rule:nonzero;stroke:none"
id="text56"><path
inkscape:connector-curvature="0"
d="m 243.58117,-73.015206 h 19.46231 v 3.613321 l -12.42176,15.02707 h 12.77844 v 4.512774 h -20.17567 v -3.613321 l 12.42176,-15.02707 h -12.06508 z"
id="path2325" /><path
inkscape:connector-curvature="0"
d="m 278.7684,-73.015206 h 16.11262 v 4.512774 h -10.14211 v 4.311172 h 9.5373 v 4.512773 h -9.5373 v 5.303672 h 10.48328 v 4.512774 H 278.7684 Z"
id="path2327" /><path
inkscape:connector-curvature="0"
d="m 320.00367,-62.749034 q 1.87645,0 2.68285,-0.697851 0.82192,-0.697852 0.82192,-2.295157 0,-1.581796 -0.82192,-2.26414 -0.8064,-0.682344 -2.68285,-0.682344 h -2.51226 v 5.939492 z m -2.51226,4.125078 v 8.761915 h -5.97051 v -23.153165 h 9.11859 q 4.57481,0 6.69938,1.535274 2.14008,1.535273 2.14008,4.853945 0,2.295156 -1.11657,3.768399 -1.10105,1.473242 -3.33418,2.171093 1.22512,0.279141 2.18661,1.271641 0.97699,0.976992 1.96949,2.9775 l 3.24113,6.575313 h -6.3582 l -2.82242,-5.753399 q -0.85293,-1.736875 -1.73688,-2.372695 -0.86844,-0.635821 -2.32617,-0.635821 z"
id="path2329" /><path
inkscape:connector-curvature="0"
d="m 357.57911,-69.107237 q -2.72938,0 -4.23364,2.016016 -1.50425,2.016015 -1.50425,5.675859 0,3.644336 1.50425,5.660352 1.50426,2.016015 4.23364,2.016015 2.74488,0 4.24914,-2.016015 1.50426,-2.016016 1.50426,-5.660352 0,-3.659844 -1.50426,-5.675859 -1.50426,-2.016016 -4.24914,-2.016016 z m 0,-4.32668 q 5.58281,0 8.7464,3.19461 3.1636,3.194609 3.1636,8.823945 0,5.613828 -3.1636,8.808438 -3.16359,3.194609 -8.7464,3.194609 -5.56731,0 -8.74641,-3.194609 -3.16359,-3.19461 -3.16359,-8.808438 0,-5.629336 3.16359,-8.823945 3.1791,-3.19461 8.74641,-3.19461 z"
id="path2331" /></g></svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -1,3 +1,7 @@
-- SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
CREATE TABLE IF NOT EXISTS survey_users (
ID UUID PRIMARY KEY NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL

View file

@ -1,3 +1,7 @@
-- SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
-- Add migration script here
CREATE TABLE IF NOT EXISTS survey_admins (
name VARCHAR(100) NOT NULL UNIQUE,

View file

@ -0,0 +1,6 @@
-- SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
ALTER TABLE survey_responses
ADD COLUMN submitted_at TIMESTAMPTZ NOT NULL DEFAULT now();

View file

@ -0,0 +1,22 @@
-- SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
CREATE TABLE IF NOT EXISTS survey_bench_type (
name VARCHAR(30) UNIQUE NOT NULL,
ID SERIAL PRIMARY KEY NOT NULL
);
INSERT INTO survey_bench_type (name) VALUES ('wasm');
INSERT INTO survey_bench_type (name) VALUES ('js');
CREATE OR REPLACE FUNCTION id_in_survey_bench_type(iname varchar)
RETURNS int LANGUAGE SQL AS $$
SELECT ID FROM survey_bench_type WHERE name = name;
$$;
ALTER TABLE survey_responses
ADD COLUMN submission_bench_type_id INTEGER references survey_bench_type(ID) NOT NULL
DEFAULT id_in_survey_bench_type('wasm');

View file

@ -0,0 +1,42 @@
-- SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
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
);
CREATE TABLE IF NOT EXISTS survey_mcaptcha_upload_job_states (
name VARCHAR(20) NOT NULL UNIQUE,
ID SERIAL PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS survey_mcaptcha_upload_jobs (
campaign_id INTEGER references survey_mcaptcha_campaign(ID) ON DELETE CASCADE,
public_id varchar(100) NOT NULL UNIQUE,
created_at timestamptz NOT NULL DEFAULT now(),
scheduled_at timestamptz DEFAULT NULL,
finished_at timestamptz DEFAULT NULL,
job_state INTEGER references survey_mcaptcha_upload_job_states(ID) ON DELETE CASCADE,
ID SERIAL PRIMARY KEY NOT NULL
);

18296
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -10,29 +10,30 @@
"test": "jest"
},
"devDependencies": {
"@types/jest": "^27.0.2",
"@types/jsdom": "^16.2.10",
"@types/node": "^16.10.5",
"@types/sinon": "^10.0.0",
"@types/jest": "^29.0.0",
"@types/jsdom": "^21.0.0",
"@types/node": "^20.0.0",
"@types/sinon": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"@wasm-tool/wasm-pack-plugin": "^1.4.0",
"@wasm-tool/wasm-pack-plugin": "^1.6.0",
"dart-sass": "^1.25.0",
"eslint": "^8.0.1",
"jest": "^27.2.5",
"eslint": "^9.0.0",
"jest": "^29.0.0",
"jest-fetch-mock": "^3.0.3",
"jsdom": "^18.0.0",
"sinon": "^11.1.2",
"ts-jest": "^27.0.5",
"jsdom": "^24.0.0",
"sinon": "^18.0.0",
"ts-jest": "^29.0.0",
"ts-loader": "^9.2.6",
"ts-node": "^10.3.0",
"typescript": "^4.4.4",
"typescript": "^5.0.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^4.3.1"
"webpack-cli": "^5.0.0",
"webpack-dev-server": "^5.0.0"
},
"dependencies": {
"@mcaptcha/vanilla-glue": "^0.1.0-alpha-1",
"mcaptcha-browser": "./vendor/pow/"
"@mcaptcha/vanilla-glue": "^0.1.0-alpha-3",
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-alpha-1",
"@mcaptcha/pow-wasm": "^0.1.0-alpha-1"
}
}

21
renovate.json Normal file
View file

@ -0,0 +1,21 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":dependencyDashboard"
],
"labels": [
"renovate-bot"
],
"prHourlyLimit": 0,
"timezone": "Asia/kolkata",
"prCreation": "immediate",
"vulnerabilityAlerts": {
"enabled": true,
"labels": [
"renovate-bot",
"renovate-security",
"security"
]
}
}

View file

@ -1,376 +1,3 @@
{
"db": "PostgreSQL",
"03c9789e83a398bed96354924a0e63ccaa97bec667fda1b8277bb9afda9a6fcd": {
"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)"
},
"0d22134cc5076304b7895827f006ee8269cc500f400114a7472b83f0f1c568b5": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Varchar"
]
}
},
"query": "INSERT INTO survey_admins \n (name , password, secret) VALUES ($1, $2, $3)"
},
"1373df097fa0e58b23a374753318ae53a44559aa0e7eb64680185baf1c481723": {
"describe": {
"columns": [
{
"name": "password",
"ordinal": 0,
"type_info": "Text"
}
],
"nullable": [
false
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT password FROM survey_admins WHERE name = ($1)"
},
"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)"
},
"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"
},
"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 )"
},
"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 );"
},
"8320dda2b3e107d1451fdfb35eb2a4b8e97364e7b1b74ffe4d6913faf132fb61": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int4"
}
],
"nullable": [
false
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
}
},
"query": "SELECT ID \n FROM survey_responses \n WHERE \n user_id = $1 \n AND \n device_software_recognised = $2;"
},
"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"
},
"b4cd1e5240de1968c8b6d56672cec639b22f41ebf2754dadbf00efe0948c7e68": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Uuid",
"Uuid",
"Varchar",
"Varchar",
"Int4"
]
}
},
"query": "INSERT INTO survey_responses (\n user_id, \n campaign_id,\n device_user_provided,\n device_software_recognised,\n threads\n ) VALUES ($1, $2, $3, $4, $5);"
},
"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)"
},
"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"
}

View file

@ -1,18 +1,6 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
pub mod v1;

View file

@ -1,19 +1,7 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
@ -22,7 +10,7 @@ use super::auth::runners::Password;
use crate::errors::*;
use crate::AppData;
#[my_codegen::post(
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.account.delete",
wrap = "crate::api::v1::admin::get_admin_check_login()"
)]

View file

@ -1,19 +1,8 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::borrow::Cow;
use actix_identity::Identity;
@ -29,7 +18,9 @@ pub struct Email {
pub email: String,
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.admin.account.email_exists")]
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.account.email_exists"
)]
pub async fn email_exists(
payload: web::Json<AccountCheckPayload>,
data: AppData,
@ -53,7 +44,7 @@ pub async fn email_exists(
}
/// update email
#[my_codegen::post(
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.account.update_email",
wrap = "crate::api::v1::admin::get_admin_check_login()"
)]

View file

@ -1,19 +1,7 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use serde::{Deserialize, Serialize};
@ -28,7 +16,9 @@ pub mod username;
pub use super::auth;
pub mod routes {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Account {
pub delete: &'static str,
pub email_exists: &'static str,

View file

@ -1,19 +1,8 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use argon2_creds::Config;
@ -68,7 +57,7 @@ async fn update_password_runner(
Ok(())
}
#[my_codegen::post(
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.account.update_password",
wrap = "crate::api::v1::admin::get_admin_check_login()"
)]
@ -118,7 +107,6 @@ mod tests {
use actix_web::test;
use crate::api::v1::ROUTES;
use crate::data::Data;
use crate::tests::*;
#[actix_rt::test]
@ -127,12 +115,12 @@ mod tests {
const PASSWORD: &str = "longpassword2";
const EMAIL: &str = "updatepassuser@a.com";
let data = get_test_data().await;
{
let data = Data::new().await;
delete_user(NAME, &data).await;
}
let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let (_, signin_resp) = register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await;
@ -165,6 +153,7 @@ mod tests {
};
bad_post_req_test(
&data,
NAME,
new_password,
ROUTES.admin.account.update_password,
@ -180,6 +169,7 @@ mod tests {
};
bad_post_req_test(
&data,
NAME,
new_password,
ROUTES.admin.account.update_password,

View file

@ -1,19 +1,8 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::borrow::Cow;
use actix_identity::Identity;
@ -29,7 +18,7 @@ pub struct Secret {
pub secret: String,
}
#[my_codegen::get(
#[actix_web_codegen_const_routes::get(
path = "crate::V1_API_ROUTES.admin.account.get_secret",
wrap = "crate::api::v1::admin::get_admin_check_login()"
)]
@ -47,7 +36,7 @@ async fn get_secret(id: Identity, data: AppData) -> ServiceResult<impl Responder
Ok(HttpResponse::Ok().json(secret))
}
#[my_codegen::post(
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.account.update_secret",
wrap = "crate::api::v1::admin::get_admin_check_login()"
)]

View file

@ -1,19 +1,7 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::http::StatusCode;
use actix_web::test;
@ -23,7 +11,6 @@ use super::username::Username;
use super::*;
use crate::api::v1::admin::auth::runners::Password;
use crate::api::v1::ROUTES;
use crate::data::Data;
use crate::*;
use crate::errors::*;
@ -35,12 +22,12 @@ async fn uname_email_exists_works() {
const PASSWORD: &str = "longpassword2";
const EMAIL: &str = "testuserexists@a.com2";
let data = get_test_data().await;
{
let data = Data::new().await;
delete_user(NAME, &data).await;
}
let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let (_, signin_resp) = register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await;
@ -125,14 +112,14 @@ async fn email_udpate_password_validation_del_userworks() {
const NAME2: &str = "eupdauser";
const EMAIL2: &str = "eupdauser@a.com";
let data = get_test_data().await;
{
let data = Data::new().await;
delete_user(NAME, &data).await;
delete_user(NAME2, &data).await;
}
let _ = register_and_signin(NAME2, EMAIL2, PASSWORD).await;
let (data, _creds, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let _ = register_and_signin(&data, NAME2, EMAIL2, PASSWORD).await;
let (_creds, signin_resp) = register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await;
@ -153,6 +140,7 @@ async fn email_udpate_password_validation_del_userworks() {
// check duplicate email while duplicate email
email_payload.email = EMAIL2.into();
bad_post_req_test(
&data,
NAME,
PASSWORD,
ROUTES.admin.account.update_email,
@ -166,6 +154,7 @@ async fn email_udpate_password_validation_del_userworks() {
password: NAME.into(),
};
bad_post_req_test(
&data,
NAME,
PASSWORD,
ROUTES.admin.account.delete,
@ -208,9 +197,8 @@ async fn username_update_works() {
const NAME2: &str = "terstusrtds";
const NAME_CHANGE: &str = "terstusrtdsxx";
let data = get_test_data().await;
{
let data = Data::new().await;
futures::join!(
delete_user(NAME, &data),
delete_user(NAME2, &data),
@ -218,8 +206,8 @@ async fn username_update_works() {
);
}
let _ = register_and_signin(NAME2, EMAIL2, PASSWORD).await;
let (data, _creds, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let _ = register_and_signin(&data, NAME2, EMAIL2, PASSWORD).await;
let (_creds, signin_resp) = register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await;
@ -239,6 +227,7 @@ async fn username_update_works() {
// check duplicate username with duplicate username
username_udpate.username = NAME2.into();
bad_post_req_test(
&data,
NAME_CHANGE,
PASSWORD,
ROUTES.admin.account.update_username,

View file

@ -1,19 +1,8 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::borrow::Cow;
use actix_identity::Identity;
@ -24,7 +13,9 @@ use super::{AccountCheckPayload, AccountCheckResp};
use crate::errors::*;
use crate::AppData;
#[my_codegen::post(path = "crate::V1_API_ROUTES.admin.account.username_exists")]
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.account.username_exists"
)]
async fn username_exists(
payload: web::Json<AccountCheckPayload>,
data: AppData,
@ -65,7 +56,7 @@ pub struct Username {
}
/// update username
#[my_codegen::post(
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.account.update_username",
wrap = "crate::api::v1::admin::get_admin_check_login()"
)]

View file

@ -1,19 +1,7 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_identity::Identity;
use actix_web::http::header;
@ -26,7 +14,9 @@ use crate::AppData;
pub mod routes {
use actix_auth_middleware::GetLoginRoute;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Auth {
pub logout: &'static str,
pub login: &'static str,
@ -153,7 +143,7 @@ pub mod runners {
payload: &Register,
data: &AppData,
) -> ServiceResult<()> {
if !crate::SETTINGS.allow_registration {
if !data.settings.allow_registration {
return Err(ServiceError::ClosedForRegistration);
}
@ -222,7 +212,9 @@ pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(login);
cfg.service(signout);
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.admin.auth.register")]
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.auth.register"
)]
async fn register(
payload: web::Json<runners::Register>,
data: AppData,
@ -231,7 +223,7 @@ async fn register(
Ok(HttpResponse::Ok())
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.admin.auth.login")]
#[actix_web_codegen_const_routes::post(path = "crate::V1_API_ROUTES.admin.auth.login")]
async fn login(
id: Identity,
payload: web::Json<runners::Login>,
@ -250,7 +242,7 @@ async fn login(
Ok(HttpResponse::Ok().into())
}
}
#[my_codegen::get(
#[actix_web_codegen_const_routes::get(
path = "crate::V1_API_ROUTES.admin.auth.logout",
wrap = "crate::api::v1::admin::get_admin_check_login()"
)]

View file

@ -1,37 +1,34 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::borrow::Cow;
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use sqlx::types::time::OffsetDateTime;
use uuid::Uuid;
use sqlx::types::Uuid;
use super::{get_admin_check_login, get_uuid};
use crate::api::v1::bench::Bench;
use crate::api::v1::bench::SubmissionType;
use crate::errors::*;
use crate::AppData;
pub mod routes {
use serde::{Deserialize, Serialize};
use super::ResultsPage;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Campaign {
pub add: &'static str,
pub delete: &'static str,
// pub get_feedback: &'static str,
pub list: &'static str,
pub results: &'static str,
}
impl Campaign {
@ -40,8 +37,14 @@ pub mod routes {
let delete = "/admin/api/v1/campaign/{uuid}/delete";
// let get_feedback = "/api/v1/campaign/{uuid}/feedback";
let list = "/admin/api/v1/campaign/list";
let results = "/admin/api/v1/campaign/{uuid}/results";
Campaign { add, delete, list }
Campaign {
add,
delete,
list,
results,
}
}
// pub fn get_benches_route(&self, campaign_id: &str) -> String {
// self.get_feedback.replace("{uuid}", &campaign_id)
@ -50,17 +53,45 @@ pub mod routes {
pub fn get_delete_route(&self, campaign_id: &str) -> String {
self.delete.replace("{uuid}", campaign_id)
}
pub fn get_results_route(
&self,
campaign_id: &str,
modifier: Option<ResultsPage>,
) -> String {
let mut res = self.results.replace("{uuid}", campaign_id);
if let Some(modifier) = modifier {
if let Some(page) = modifier.page {
res = format!("{res}?page={page}");
}
if let Some(bench_type) = modifier.bench_type {
if modifier.page.is_some() {
res = format!("{res}&bench_type={}", bench_type.to_string());
} else {
res = format!("{res}?bench_type={}", bench_type.to_string());
}
}
}
res
}
}
}
pub mod runners {
use std::str::FromStr;
use futures::try_join;
use crate::api::v1::bench::Bench;
use super::*;
pub async fn add_runner(
username: &str,
payload: &mut AddCapmaign,
data: &AppData,
) -> ServiceResult<uuid::Uuid> {
) -> ServiceResult<sqlx::types::Uuid> {
let mut uuid;
let now = OffsetDateTime::now_utc();
@ -101,6 +132,32 @@ pub mod runners {
Ok(uuid)
}
pub async fn list_all_campaigns(
data: &AppData,
) -> ServiceResult<Vec<ListCampaignResp>> {
struct ListCampaign {
name: String,
id: Uuid,
}
let mut campaigns = sqlx::query_as!(
ListCampaign,
"SELECT name, id FROM survey_campaigns ORDER BY id;"
)
.fetch_all(&data.db)
.await?;
let mut list_resp = Vec::with_capacity(campaigns.len());
campaigns.drain(0..).for_each(|c| {
list_resp.push(ListCampaignResp {
name: c.name,
uuid: c.id.to_string(),
});
});
Ok(list_resp)
}
pub async fn list_campaign_runner(
username: &str,
data: &AppData,
@ -141,86 +198,176 @@ pub mod runners {
Ok(list_resp)
}
// pub async fn get_benches(
// username: &str,
// uuid: &str,
// data: &AppData,
// ) -> ServiceResult<GetFeedbackResp> {
// let uuid = Uuid::parse_str(uuid).map_err(|_| ServiceError::NotAnId)?;
//
// struct FeedbackInternal {
// time: OffsetDateTime,
// description: String,
// helpful: bool,
// }
//
// struct Name {
// name: String,
// }
//
// let name_fut = sqlx::query_as!(
// Name,
// "SELECT name
// FROM survey_campaigns
// WHERE uuid = $1
// AND
// user_id = (
// SELECT
// ID
// FROM
// kaizen_users
// WHERE
// name = $2
// )
// ",
// uuid,
// username
// )
// .fetch_one(&data.db); //.await?;
//
// let feedback_fut = sqlx::query_as!(
// FeedbackInternal,
// "SELECT
// time, description, helpful
// FROM
// kaizen_feedbacks
// WHERE campaign_id = (
// SELECT uuid
// FROM
// survey_campaigns
// WHERE
// uuid = $1
// AND
// user_id = (
// SELECT
// ID
// FROM
// kaizen_users
// WHERE
// name = $2
// )
// )",
// uuid,
// username
// )
// .fetch_all(&data.db);
// let (name, mut feedbacks) = try_join!(name_fut, feedback_fut)?;
// //.await?;
//
// let mut feedback_resp = Vec::with_capacity(feedbacks.len());
// feedbacks.drain(0..).for_each(|f| {
// feedback_resp.push(Feedback {
// time: f.time.unix_timestamp() as u64,
// description: f.description,
// helpful: f.helpful,
// });
// });
//
// Ok(GetFeedbackResp {
// feedbacks: feedback_resp,
// name: name.name,
// })
// }
#[derive(Debug)]
struct InternalSurveyResp {
id: i32,
submitted_at: OffsetDateTime,
user_id: Uuid,
threads: Option<i32>,
device_user_provided: String,
device_software_recognised: String,
name: String,
}
#[derive(Debug)]
struct InnerU {
created_at: OffsetDateTime,
id: Uuid,
}
impl From<InnerU> for SurveyUser {
fn from(u: InnerU) -> Self {
Self {
id: uuid::Uuid::parse_str(&u.id.to_string()).unwrap(),
created_at: u.created_at.unix_timestamp(),
}
}
}
pub async fn get_results(
username: &str,
uuid: &Uuid,
data: &AppData,
page: usize,
limit: usize,
filter: Option<SubmissionType>,
) -> ServiceResult<Vec<SurveyResponse>> {
let mut db_responses = if let Some(filter) = filter {
sqlx::query_as!(
InternalSurveyResp,
"SELECT
survey_responses.ID,
survey_responses.device_software_recognised,
survey_responses.threads,
survey_responses.user_id,
survey_responses.submitted_at,
survey_responses.device_user_provided,
survey_bench_type.name
FROM
survey_responses
INNER JOIN survey_bench_type ON
survey_responses.submission_bench_type_id = survey_bench_type.ID
WHERE
survey_bench_type.name = $3
AND
survey_responses.campaign_id = (
SELECT ID FROM survey_campaigns
WHERE
ID = $1
AND
user_id = (SELECT ID FROM survey_admins WHERE name = $2)
)
LIMIT $4 OFFSET $5",
uuid,
username,
filter.to_string(),
limit as i32,
page as i32,
)
.fetch_all(&data.db)
.await?
} else {
#[derive(Debug)]
struct I {
id: i32,
submitted_at: OffsetDateTime,
user_id: Uuid,
threads: Option<i32>,
device_user_provided: String,
device_software_recognised: String,
name: String,
}
let mut i = sqlx::query_as!(
I,
"SELECT
survey_responses.ID,
survey_responses.device_software_recognised,
survey_responses.threads,
survey_responses.user_id,
survey_responses.submitted_at,
survey_responses.device_user_provided,
survey_bench_type.name
FROM
survey_responses
INNER JOIN survey_bench_type ON
survey_responses.submission_bench_type_id = survey_bench_type.ID
WHERE
survey_responses.campaign_id = (
SELECT ID FROM survey_campaigns
WHERE
ID = $1
AND
user_id = (SELECT ID FROM survey_admins WHERE name = $2)
)
LIMIT $3 OFFSET $4",
uuid,
username,
limit as i32,
page as i32,
)
.fetch_all(&data.db)
.await?;
let mut res = Vec::with_capacity(i.len());
i.drain(0..).for_each(|x| {
res.push(InternalSurveyResp {
id: x.id,
submitted_at: x.submitted_at,
user_id: x.user_id,
threads: x.threads,
device_user_provided: x.device_user_provided,
device_software_recognised: x.device_software_recognised,
name: x.name,
})
});
res
};
let mut responses = Vec::with_capacity(db_responses.len());
for r in db_responses.drain(0..) {
let benches_fut = sqlx::query_as!(
Bench,
"SELECT
duration,
difficulty
FROM
survey_benches
WHERE
resp_id = $1
",
r.id,
)
.fetch_all(&data.db);
let user_fut = sqlx::query_as!(
InnerU,
"SELECT
created_at,
ID
FROM
survey_users
WHERE
ID = $1
",
r.user_id,
)
.fetch_one(&data.db);
let (benches, user) = try_join!(benches_fut, user_fut)?;
let user = user.into();
responses.push(SurveyResponse {
benches,
user,
device_user_provided: r.device_user_provided,
device_software_recognised: r.device_software_recognised,
submitted_at: r.submitted_at.unix_timestamp(),
id: r.id as usize,
submission_type: SubmissionType::from_str(&r.name).unwrap(),
threads: r.threads.map(|t| t as usize),
})
}
Ok(responses)
}
pub async fn delete(
uuid: &Uuid,
@ -233,11 +380,11 @@ pub mod runners {
WHERE
user_id = (
SELECT
ID
ID
FROM
survey_admins
survey_admins
WHERE
name = $1
name = $1
)
AND
id = ($2)",
@ -250,7 +397,7 @@ pub mod runners {
}
}
#[my_codegen::post(
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.campaign.delete",
wrap = "get_admin_check_login()"
)]
@ -266,41 +413,31 @@ pub async fn delete(
Ok(HttpResponse::Ok())
}
//#[derive(Serialize, Deserialize)]
//pub struct Feedback {
// pub time: u64,
// pub description: String,
// pub helpful: bool,
//}
//
//#[derive(Serialize, Deserialize)]
//pub struct GetFeedbackResp {
// pub name: String,
// pub feedbacks: Vec<Feedback>,
//}
//
//#[my_codegen::post(
// path = "crate::V1_API_ROUTES.campaign.get_feedback",
// wrap = "crate::CheckLogin"
//)]
//pub async fn get_feedback(
// id: Identity,
// data: AppData,
// path: web::Path<String>,
//) -> ServiceResult<impl Responder> {
// let username = id.identity().unwrap();
// let path = path.into_inner();
// let feedback_resp = runners::get_feedback(&username, &path, &data).await?;
// Ok(HttpResponse::Ok().json(feedback_resp))
//}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SurveyResponse {
pub user: SurveyUser,
pub device_user_provided: String,
pub device_software_recognised: String,
pub id: usize,
pub threads: Option<usize>,
pub submitted_at: i64,
pub submission_type: SubmissionType,
pub benches: Vec<Bench>,
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SurveyUser {
pub created_at: i64, // OffsetDateTime,
pub id: uuid::Uuid,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ListCampaignResp {
pub name: String,
pub uuid: String,
}
#[my_codegen::post(
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.admin.campaign.list",
wrap = "get_admin_check_login()"
)]
@ -314,13 +451,13 @@ pub async fn list_campaign(
Ok(HttpResponse::Ok().json(list_resp))
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AddCapmaign {
pub name: String,
pub difficulties: Vec<i32>,
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AddCapmaignResp {
pub campaign_id: String,
}
@ -329,10 +466,48 @@ pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(add);
cfg.service(delete);
cfg.service(list_campaign);
//cfg.service(get_feedback);
cfg.service(get_campaign_resutls);
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.admin.campaign.add")]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct ResultsPage {
page: Option<usize>,
pub bench_type: Option<SubmissionType>,
}
impl ResultsPage {
pub fn page(&self) -> usize {
self.page.unwrap_or(0)
}
pub fn new(page: Option<usize>, bench_type: Option<SubmissionType>) -> Self {
Self { page, bench_type }
}
}
#[actix_web_codegen_const_routes::get(
path = "crate::V1_API_ROUTES.admin.campaign.results",
wrap = "get_admin_check_login()"
)]
pub async fn get_campaign_resutls(
id: Identity,
query: web::Query<ResultsPage>,
path: web::Path<uuid::Uuid>,
data: AppData,
) -> ServiceResult<impl Responder> {
let username = id.identity().unwrap();
let query = query.into_inner();
let page = query.page();
let path = Uuid::parse_str(&path.to_string()).unwrap();
let results =
runners::get_results(&username, &path, &data, page, 50, query.bench_type)
.await?;
Ok(HttpResponse::Ok().json(results))
}
#[actix_web_codegen_const_routes::post(path = "crate::V1_API_ROUTES.admin.campaign.add")]
async fn add(
payload: web::Json<AddCapmaign>,
data: AppData,
@ -350,7 +525,7 @@ async fn add(
#[cfg(test)]
mod tests {
use crate::api::v1::bench::Submission;
use crate::data::Data;
use crate::api::v1::bench::SubmissionType;
use crate::errors::*;
use crate::tests::*;
use crate::*;
@ -360,7 +535,7 @@ mod tests {
#[actix_rt::test]
async fn test_bench_register_works() {
let data = Data::new().await;
let data = get_test_data().await;
let app = get_app!(data).await;
let signin_resp = test::call_service(
&app,
@ -399,16 +574,17 @@ mod tests {
const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
const THREADS: i32 = 4;
let data = get_test_data().await;
{
let data = Data::new().await;
delete_user(NAME, &data).await;
}
let (data, _creds, signin_resp) =
register_and_signin(NAME, EMAIL, PASSWORD).await;
let (_creds, signin_resp) =
register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let survey = get_survey_user(data.clone()).await;
let survey_cookie = get_cookie!(survey);
let app = get_app!(data).await;
let campaign = create_new_campaign(NAME, data.clone(), cookies.clone()).await;
let campaign_config =
@ -421,6 +597,7 @@ mod tests {
device_software_recognised: DEVICE_SOFTWARE_RECOGNISED.into(),
threads: THREADS,
benches: BENCHES.clone(),
submission_type: SubmissionType::Wasm,
};
let _proof =
@ -429,7 +606,72 @@ mod tests {
let list = list_campaings(data.clone(), cookies.clone()).await;
assert!(list.iter().any(|c| c.name == NAME));
let responses = super::runners::get_results(
NAME,
&sqlx::types::Uuid::parse_str(&campaign.campaign_id).unwrap(),
&AppData::new(data.clone()),
0,
50,
None,
)
.await
.unwrap();
assert_eq!(responses.len(), 1);
assert_eq!(responses[0].threads, Some(THREADS as usize));
let mut l = responses[0].benches.clone();
l.sort_by(|a, b| a.difficulty.cmp(&b.difficulty));
let mut r = BENCHES.clone();
r.sort_by(|a, b| a.difficulty.cmp(&b.difficulty));
assert_eq!(
super::runners::get_results(
NAME,
&sqlx::types::Uuid::parse_str(&campaign.campaign_id).unwrap(),
&AppData::new(data.clone()),
0,
50,
Some(SubmissionType::Wasm),
)
.await
.unwrap(),
responses
);
assert_eq!(
super::runners::get_results(
NAME,
&sqlx::types::Uuid::parse_str(&campaign.campaign_id).unwrap(),
&AppData::new(data.clone()),
0,
50,
Some(SubmissionType::Js),
)
.await
.unwrap(),
Vec::default()
);
assert_eq!(l, r);
assert_eq!(
responses[0].device_software_recognised,
DEVICE_SOFTWARE_RECOGNISED
);
assert_eq!(responses[0].device_user_provided, DEVICE_USER_PROVIDED);
let results_resp = get_request!(
&app,
&V1_API_ROUTES
.admin
.campaign
.get_results_route(&campaign.campaign_id, None),
cookies.clone()
);
assert_eq!(results_resp.status(), StatusCode::OK);
let res: Vec<super::SurveyResponse> = test::read_body_json(results_resp).await;
assert_eq!(responses, res);
bad_post_req_test_witout_payload(
&data,
NAME,
PASSWORD,
&V1_API_ROUTES.admin.campaign.delete.replace("{uuid}", NAME),

View file

@ -1,19 +1,8 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_auth_middleware::*;
use actix_web::web::ServiceConfig;
@ -24,7 +13,6 @@ pub mod campaigns;
mod tests;
pub use super::{get_random, get_uuid, RedirectQuery};
use crate::api::v1::bench::SURVEY_USER_ID;
pub fn services(cfg: &mut ServiceConfig) {
auth::services(cfg);
@ -40,7 +28,9 @@ pub mod routes {
use super::account::routes::Account;
use super::auth::routes::Auth;
use super::campaigns::routes::Campaign;
use serde::Serialize;
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct Admin {
pub auth: Auth,
pub account: Account,

View file

@ -1,26 +1,13 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::http::{header, StatusCode};
use actix_web::test;
use crate::api::v1::admin::auth::runners::{Login, Register};
use crate::api::v1::ROUTES;
use crate::data::Data;
use crate::errors::*;
use crate::*;
@ -28,7 +15,7 @@ use crate::tests::*;
#[actix_rt::test]
async fn auth_works() {
let data = Data::new().await;
let data = get_test_data().await;
const NAME: &str = "testuser";
const PASSWORD: &str = "longpassword";
const EMAIL: &str = "testuser1@a.com";
@ -54,11 +41,11 @@ async fn auth_works() {
delete_user(NAME, &data).await;
// 1. Register and signin
let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let (_, signin_resp) = register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
// Sign in with email
signin(EMAIL, PASSWORD).await;
signin(&data, EMAIL, PASSWORD).await;
// 2. check if duplicate username is allowed
let mut msg = Register {
@ -68,6 +55,7 @@ async fn auth_works() {
email: Some(EMAIL.into()),
};
bad_post_req_test(
&data,
NAME,
PASSWORD,
ROUTES.admin.auth.register,
@ -79,6 +67,7 @@ async fn auth_works() {
let name = format!("{}dupemail", NAME);
msg.username = name;
bad_post_req_test(
&data,
NAME,
PASSWORD,
ROUTES.admin.auth.register,
@ -93,6 +82,7 @@ async fn auth_works() {
password: msg.password.clone(),
};
bad_post_req_test(
&data,
NAME,
PASSWORD,
ROUTES.admin.auth.login,
@ -103,6 +93,7 @@ async fn auth_works() {
creds.login = "nonexistantuser@example.com".into();
bad_post_req_test(
&data,
NAME,
PASSWORD,
ROUTES.admin.auth.login,
@ -116,6 +107,7 @@ async fn auth_works() {
creds.password = NAME.into();
bad_post_req_test(
&data,
NAME,
PASSWORD,
ROUTES.admin.auth.login,
@ -146,7 +138,7 @@ async fn serverside_password_validation_works() {
const NAME: &str = "testuser542";
const PASSWORD: &str = "longpassword2";
let data = Data::new().await;
let data = get_test_data().await;
delete_user(NAME, &data).await;
let app = get_app!(data).await;

View file

@ -1,18 +1,7 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
mod auth;
mod protected;

View file

@ -1,24 +1,11 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::http::StatusCode;
use actix_web::test;
use crate::data::Data;
use crate::*;
use crate::tests::*;
@ -30,13 +17,13 @@ async fn protected_routes_work() {
const EMAIL: &str = "testuser119@a.com2";
let get_protected_urls = [V1_API_ROUTES.admin.auth.logout];
let data = get_test_data().await;
{
let data = Data::new().await;
delete_user(NAME, &data).await;
}
let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let (_, signin_resp) = register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await;

View file

@ -1,19 +1,8 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::borrow::Cow;
use std::str::FromStr;
@ -24,7 +13,7 @@ use actix_web::{http, web, HttpResponse, Responder};
use futures::future::try_join_all;
use serde::{Deserialize, Serialize};
use sqlx::types::time::OffsetDateTime;
use uuid::Uuid;
use sqlx::types::Uuid;
use super::{get_uuid, RedirectQuery};
use crate::errors::*;
@ -33,9 +22,11 @@ use crate::AppData;
pub const SURVEY_USER_ID: &str = "survey_user_id";
pub mod routes {
use serde::{Deserialize, Serialize};
use actix_auth_middleware::GetLoginRoute;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Benches {
pub submit: &'static str,
pub register: &'static str,
@ -93,7 +84,7 @@ pub fn services(cfg: &mut web::ServiceConfig) {
pub mod runners {
use super::*;
pub async fn register_runner(data: &AppData) -> ServiceResult<uuid::Uuid> {
pub async fn register_runner(data: &AppData) -> ServiceResult<sqlx::types::Uuid> {
let mut uuid;
let now = OffsetDateTime::now_utc();
@ -125,7 +116,7 @@ pub mod runners {
}
}
#[my_codegen::get(path = "crate::V1_API_ROUTES.benches.register")]
#[actix_web_codegen_const_routes::get(path = "crate::V1_API_ROUTES.benches.register")]
async fn register(
data: AppData,
session: Session,
@ -155,32 +146,53 @@ async fn register(
}
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Bench {
pub duration: f32,
pub difficulty: i32,
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Submission {
pub device_user_provided: String,
pub device_software_recognised: String,
pub threads: i32,
pub benches: Vec<Bench>,
pub submission_type: SubmissionType,
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SubmissionType {
Wasm,
Js,
}
impl ToString for SubmissionType {
fn to_string(&self) -> String {
let s = serde_json::to_string(&self).unwrap();
(&s[1..(s.len() - 1)]).to_string()
}
}
impl FromStr for SubmissionType {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(&format!("\"{}\"", s))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubmissionProof {
pub token: String,
pub proof: String,
}
fn is_session_authenticated(r: &HttpRequest, mut pl: &mut Payload) -> bool {
fn is_session_authenticated(r: &HttpRequest, pl: &mut Payload) -> bool {
use actix_web::FromRequest;
matches!(
Session::from_request(&r, &mut pl).into_inner().map(|x| {
Session::from_request(r, pl).into_inner().map(|x| {
let val = x.get::<String>(SURVEY_USER_ID);
println!("{:#?}", val);
val
}),
Ok(Ok(Some(_)))
@ -196,7 +208,7 @@ pub fn get_check_login() -> Authentication<routes::Benches> {
// }
//}
#[my_codegen::post(
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.benches.submit",
wrap = "get_check_login()"
)]
@ -212,44 +224,38 @@ async fn submit(
let user_id = Uuid::from_str(&username).unwrap();
let payload = payload.into_inner();
sqlx::query!(
"INSERT INTO survey_responses (
user_id,
campaign_id,
device_user_provided,
device_software_recognised,
threads
) VALUES ($1, $2, $3, $4, $5);",
&user_id,
&campaign_id,
&payload.device_user_provided,
&payload.device_software_recognised,
&payload.threads
)
.execute(&data.db)
.await?;
let now = OffsetDateTime::now_utc();
struct ID {
id: i32,
}
let resp_id = sqlx::query_as!(
ID,
"SELECT ID
FROM survey_responses
WHERE
user_id = $1
AND
device_software_recognised = $2;",
"INSERT INTO survey_responses (
user_id,
campaign_id,
device_user_provided,
device_software_recognised,
threads,
submitted_at,
submission_bench_type_id
) VALUES (
$1, $2, $3, $4, $5, $6,
(SELECT ID FROM survey_bench_type WHERE name = $7)
)
RETURNING ID;",
&user_id,
&payload.device_software_recognised
&campaign_id,
&payload.device_user_provided,
&payload.device_software_recognised,
&payload.threads,
&now,
&payload.submission_type.to_string(),
)
.fetch_one(&data.db)
.await?;
let mut futs = Vec::with_capacity(payload.benches.len());
for bench in payload.benches.iter() {
let fut = sqlx::query!(
"INSERT INTO survey_benches
@ -259,8 +265,7 @@ async fn submit(
&bench.difficulty,
bench.duration
)
.execute(&data.db);
.execute(&data.db); //.await?;
futs.push(fut);
}
@ -302,12 +307,12 @@ async fn submit(
Ok(HttpResponse::Ok().json(resp))
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BenchConfig {
pub difficulties: Vec<i32>,
}
#[my_codegen::get(
#[actix_web_codegen_const_routes::get(
path = "crate::V1_API_ROUTES.benches.fetch",
wrap = "get_check_login()"
)]
@ -324,3 +329,14 @@ async fn fetch(data: AppData, path: web::Path<String>) -> ServiceResult<impl Res
.await?;
Ok(HttpResponse::Ok().json(config))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn survey_response_type_no_panic_test() {
assert_eq!(SubmissionType::Wasm.to_string(), "wasm".to_string());
assert_eq!(SubmissionType::Js.to_string(), "js".to_string());
}
}

819
src/api/v1/mcaptcha/db.rs Normal file
View file

@ -0,0 +1,819 @@
// Copyright (C) 2023 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use url::Url;
use uuid::Uuid;
use crate::api::v1::get_random;
use crate::db::{
JobState, JOB_STATES, JOB_STATE_CREATE, JOB_STATE_FINISH, JOB_STATE_RUNNING,
};
use crate::errors::*;
use crate::mcaptcha::PerformanceAnalytics;
use crate::Data;
use sqlx::types::time::OffsetDateTime;
fn now_unix_time_stamp() -> OffsetDateTime {
OffsetDateTime::now_utc()
}
impl Data {
/// Check if an mCaptcha instance is registered on the database
pub async fn mcaptcha_url_exists(&self, url: &str) -> ServiceResult<bool> {
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<String> {
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<String> {
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(&self, secret: &str) -> ServiceResult<()> {
let res = sqlx::query!(
"SELECT EXISTS (
SELECT
url
FROM
survey_mcaptcha_hostname
WHERE secret = $1
)",
secret
)
.fetch_one(&self.db)
.await?;
if !matches!(res.exists, Some(true)) {
return Err(ServiceError::WrongPassword);
}
Ok(())
}
/// 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 campaign from database
pub async fn mcaptcha_delete_mcaptcha_campaign(
&self,
campaign_id: &Uuid,
secret: &str,
) -> ServiceResult<()> {
let campaign_str = campaign_id.to_string();
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,
secret: &str,
) -> ServiceResult<bool> {
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,
secret: &str,
) -> ServiceResult<()> {
let campaign_str = campaign_id.to_string();
let public_id = 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,
secret: &str,
) -> ServiceResult<Uuid> {
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::parse_str(&res.public_id).unwrap())
}
/// Get an mCaptcha instance campaign checkpoint
pub async fn mcaptcha_get_checkpoint(
&self,
campaign_id: &Uuid,
) -> ServiceResult<usize> {
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;",
&campaign_str,
)
.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,
checkpoint: usize,
) -> ServiceResult<()> {
let campaign_str = campaign_id.to_string();
sqlx::query!(
"UPDATE
survey_mcaptcha_campaign
SET
synced_till = $1
WHERE
campaign_id = $2; ",
checkpoint as i32,
&campaign_str,
)
.execute(&self.db)
.await?;
Ok(())
}
/// Store mCaptcha instance campaign analytics
pub async fn mcaptcha_insert_analytics(
&self,
campaign_id: &Uuid,
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
), $2, $3, $4
);",
&campaign_str,
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,
limit: usize,
offset: usize,
) -> ServiceResult<Vec<PerformanceAnalytics>> {
let public_id_str = public_id.to_string();
struct P {
id: i32,
time: i32,
difficulty_factor: i32,
worker_type: String,
}
impl From<P> 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)
}
pub async fn get_next_job_to_run(&self) -> ServiceResult<Option<SchedulerJob>> {
let res = match sqlx::query_as!(
InnerSchedulerJob,
"SELECT
survey_mcaptcha_campaign.campaign_id,
survey_mcaptcha_upload_jobs.public_id,
survey_mcaptcha_hostname.url
FROM
survey_mcaptcha_campaign
INNER JOIN
survey_mcaptcha_upload_jobs
ON
survey_mcaptcha_upload_jobs.campaign_id = survey_mcaptcha_campaign.ID
INNER JOIN
survey_mcaptcha_hostname
ON
survey_mcaptcha_hostname.ID = survey_mcaptcha_campaign.url_id
WHERE
survey_mcaptcha_upload_jobs.job_state = (
SELECT ID FROM survey_mcaptcha_upload_job_states WHERE name = $1
)
AND
survey_mcaptcha_upload_jobs.finished_at is NULL
AND
survey_mcaptcha_upload_jobs.scheduled_at is NULL
ORDER BY created_at ASC;",
&JOB_STATE_CREATE.name
)
.fetch_one(&self.db)
.await
{
Ok(res) => Ok(Some(res.into())),
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(e),
}?;
Ok(res)
}
pub async fn add_job(&self, campaign_id: &Uuid) -> ServiceResult<Uuid> {
let now = now_unix_time_stamp();
if let Some(unfinished_job) =
self.get_unfinished_job_for_campaign(campaign_id).await?
{
return Ok(unfinished_job.public_job_id);
}
let public_id = Uuid::new_v4();
let public_id_str = public_id.to_string();
let campaign_str = campaign_id.to_string();
sqlx::query!(
"INSERT INTO survey_mcaptcha_upload_jobs
(campaign_id, job_state, created_at, public_id)
VALUES (
(SELECT ID FROM survey_mcaptcha_campaign WHERE campaign_id = $1),
(SELECT ID FROM survey_mcaptcha_upload_job_states WHERE name = $2),
$3, $4)",
&campaign_str,
&JOB_STATE_CREATE.name,
now,
public_id_str
)
.execute(&self.db)
.await?;
Ok(public_id)
}
pub async fn get_unfinished_job_for_campaign(
&self,
campaign_id: &Uuid,
) -> ServiceResult<Option<Job>> {
let res = match sqlx::query_as!(
InnerJob,
"
SELECT
survey_mcaptcha_upload_jobs.ID,
survey_mcaptcha_upload_jobs.public_id,
survey_mcaptcha_campaign.campaign_id,
survey_mcaptcha_campaign.public_id as campaign_public_id,
survey_mcaptcha_upload_job_states.name,
survey_mcaptcha_upload_jobs.created_at,
survey_mcaptcha_upload_jobs.scheduled_at,
survey_mcaptcha_upload_jobs.finished_at
FROM survey_mcaptcha_upload_jobs
INNER JOIN
survey_mcaptcha_upload_job_states
ON
survey_mcaptcha_upload_job_states.ID = survey_mcaptcha_upload_jobs.job_state
INNER JOIN
survey_mcaptcha_campaign
ON
survey_mcaptcha_campaign.ID = survey_mcaptcha_upload_jobs.campaign_id
WHERE
survey_mcaptcha_campaign.campaign_id = $1
AND
survey_mcaptcha_upload_job_states.name = $2;",
&campaign_id.to_string(),
&JOB_STATE_CREATE.name
)
.fetch_one(&self.db)
.await {
Ok(res) => Ok(Some(res.into())),
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(e),
}?;
Ok(res)
}
pub async fn get_job(&self, public_id: &uuid::Uuid) -> ServiceResult<Option<Job>> {
let res = match sqlx::query_as!(
InnerJob,
"
SELECT
survey_mcaptcha_upload_jobs.ID,
survey_mcaptcha_upload_jobs.public_id,
survey_mcaptcha_campaign.campaign_id,
survey_mcaptcha_campaign.public_id as campaign_public_id,
survey_mcaptcha_upload_job_states.name,
survey_mcaptcha_upload_jobs.created_at,
survey_mcaptcha_upload_jobs.scheduled_at,
survey_mcaptcha_upload_jobs.finished_at
FROM survey_mcaptcha_upload_jobs
INNER JOIN
survey_mcaptcha_upload_job_states
ON
survey_mcaptcha_upload_job_states.ID = survey_mcaptcha_upload_jobs.job_state
INNER JOIN
survey_mcaptcha_campaign
ON
survey_mcaptcha_campaign.ID = survey_mcaptcha_upload_jobs.campaign_id
WHERE
survey_mcaptcha_upload_jobs.public_id = $1",
&public_id.to_string()
)
.fetch_one(&self.db)
.await {
Ok(res) => Ok(Some(res.into())),
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(e),
}?;
Ok(res)
}
pub async fn get_all_jobs_of_state(
&self,
state: &JobState,
) -> ServiceResult<Vec<Job>> {
let mut res = sqlx::query_as!(
InnerJob,
"
SELECT
survey_mcaptcha_upload_jobs.ID,
survey_mcaptcha_upload_jobs.public_id,
survey_mcaptcha_campaign.campaign_id,
survey_mcaptcha_campaign.public_id as campaign_public_id,
survey_mcaptcha_upload_job_states.name,
survey_mcaptcha_upload_jobs.created_at,
survey_mcaptcha_upload_jobs.scheduled_at,
survey_mcaptcha_upload_jobs.finished_at
FROM survey_mcaptcha_upload_jobs
INNER JOIN
survey_mcaptcha_upload_job_states
ON
survey_mcaptcha_upload_job_states.ID = survey_mcaptcha_upload_jobs.job_state
INNER JOIN
survey_mcaptcha_campaign
ON
survey_mcaptcha_campaign.ID = survey_mcaptcha_upload_jobs.campaign_id
WHERE
survey_mcaptcha_upload_job_states.name = $1;",
&state.name
)
.fetch_all(&self.db)
.await?;
let res = res.drain(0..).map(|r| r.into()).collect();
Ok(res)
}
pub async fn mark_job_scheduled(&self, job: &SchedulerJob) -> ServiceResult<()> {
let now = now_unix_time_stamp();
sqlx::query!(
"
UPDATE
survey_mcaptcha_upload_jobs
SET
job_state = (SELECT ID FROM survey_mcaptcha_upload_job_states WHERE name = $1),
scheduled_at = $2
WHERE public_id = $3;",
&JOB_STATE_RUNNING.name,
now,
&job.public_job_id.to_string(),
)
.execute(&self.db)
.await
?;
Ok(())
}
pub async fn mark_job_finished(&self, job: &SchedulerJob) -> ServiceResult<()> {
let now = now_unix_time_stamp();
sqlx::query!(
"
UPDATE
survey_mcaptcha_upload_jobs
SET
job_state = (SELECT ID FROM survey_mcaptcha_upload_job_states WHERE name = $1),
finished_at = $2
WHERE public_id = $3;",
&JOB_STATE_FINISH.name,
now,
&job.public_job_id.to_string(),
)
.execute(&self.db)
.await
?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SchedulerJob {
pub campaign_id: Uuid,
pub public_job_id: Uuid,
pub url: Url,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct InnerSchedulerJob {
campaign_id: String,
public_id: String,
url: String,
}
impl From<InnerSchedulerJob> for SchedulerJob {
fn from(j: InnerSchedulerJob) -> Self {
SchedulerJob {
campaign_id: Uuid::parse_str(&j.campaign_id).unwrap(),
public_job_id: Uuid::parse_str(&j.public_id).unwrap(),
url: Url::parse(&j.url).unwrap(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Job {
pub state: JobState,
pub campaign_id: Uuid,
pub campaign_public_id: Uuid,
pub public_job_id: Uuid,
pub id: u32,
pub created_at: OffsetDateTime,
pub scheduled_at: Option<OffsetDateTime>,
pub finished_at: Option<OffsetDateTime>,
}
struct InnerJob {
name: String,
campaign_id: String,
public_id: String,
campaign_public_id: String,
id: i32,
created_at: OffsetDateTime,
scheduled_at: Option<OffsetDateTime>,
finished_at: Option<OffsetDateTime>,
}
impl From<InnerJob> for Job {
fn from(j: InnerJob) -> Self {
Job {
state: (JOB_STATES)
.iter()
.find(|d| d.name == j.name)
.unwrap()
.to_owned()
.to_owned(),
id: j.id as u32,
created_at: j.created_at,
scheduled_at: j.scheduled_at,
finished_at: j.finished_at,
campaign_id: Uuid::parse_str(&j.campaign_id).unwrap(),
campaign_public_id: Uuid::parse_str(&j.campaign_public_id).unwrap(),
public_job_id: Uuid::parse_str(&j.public_id).unwrap(),
}
}
}
#[cfg(test)]
mod tests {
use crate::{mcaptcha::PerformanceAnalytics, tests::*};
use super::*;
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!(data.mcaptcha_authenticate(&secret).await.is_ok());
assert_eq!(
data.mcaptcha_authenticate("foo").await.err(),
Some(ServiceError::WrongPassword)
);
let 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).await.unwrap(), 0);
data.mcaptcha_set_checkpoint(&uuid, 1).await.unwrap();
assert_eq!(data.mcaptcha_get_checkpoint(&uuid).await.unwrap(), 1);
let analytics = PerformanceAnalytics {
id: 1,
time: 1,
difficulty_factor: 1,
worker_type: "foo".to_string(),
};
data.mcaptcha_insert_analytics(&uuid, &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![]
);
// job related stuff
let job1_public_id = data.add_job(&uuid).await.unwrap();
let job = data.get_job(&job1_public_id).await.unwrap().unwrap();
assert_eq!(public_id, job.campaign_public_id);
assert_eq!(
data.get_unfinished_job_for_campaign(&uuid)
.await
.unwrap()
.unwrap(),
job
);
let job2_public_id = data.add_job(&uuid).await.unwrap();
let job2 = data.get_job(&job2_public_id).await.unwrap().unwrap();
assert_eq!(job2, job);
let scheduler_job = data.get_next_job_to_run().await.unwrap().unwrap();
assert_eq!(scheduler_job.url, url);
assert_eq!(
data.get_next_job_to_run()
.await
.unwrap()
.unwrap()
.public_job_id,
job.public_job_id
);
assert!(job.created_at < now_unix_time_stamp());
assert!(job.scheduled_at.is_none());
assert!(job.finished_at.is_none());
assert_eq!(
data.get_all_jobs_of_state(&JOB_STATE_CREATE).await.unwrap(),
vec![job.clone()]
);
data.mark_job_scheduled(&scheduler_job).await.unwrap();
assert!(data.get_next_job_to_run().await.unwrap().is_none(),);
let job = data.get_job(&job.public_job_id).await.unwrap().unwrap();
assert!(job.scheduled_at.is_some());
assert_eq!(
data.get_all_jobs_of_state(&JOB_STATE_RUNNING)
.await
.unwrap(),
vec![job.clone()]
);
data.mark_job_finished(&scheduler_job).await.unwrap();
let job = data.get_job(&job.public_job_id).await.unwrap().unwrap();
assert!(job.finished_at.is_some());
assert_eq!(
data.get_all_jobs_of_state(&JOB_STATE_FINISH).await.unwrap(),
vec![job.clone()]
);
let job2_public_id = data.add_job(&uuid).await.unwrap();
let job2 = data.get_job(&job2_public_id).await.unwrap().unwrap();
assert_ne!(job2.public_job_id, job.public_job_id);
assert_eq!(
data.get_next_job_to_run()
.await
.unwrap()
.unwrap()
.public_job_id,
job2.public_job_id
);
assert_eq!(public_id, job2.campaign_public_id);
}
}

View file

@ -0,0 +1,260 @@
// Copyright (C) 2023 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::web::ServiceConfig;
use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use url::Url;
use uuid::Uuid;
use crate::api::v1::ROUTES;
use crate::errors::*;
use crate::AppData;
pub fn services(cfg: &mut ServiceConfig) {
cfg.service(register);
cfg.service(upload);
cfg.service(download);
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct MCaptchaInstance {
pub url: Url,
pub auth_token: String,
}
#[actix_web_codegen_const_routes::post(path = "ROUTES.mcaptcha.register")]
async fn register(
data: AppData,
payload: web::Json<MCaptchaInstance>,
) -> ServiceResult<impl Responder> {
/* Summary
* 1. Check if secret exists
* 2. If not, add hostname and create secret
* 3. Post to mCaptcha
*/
let url_str = payload.url.to_string();
let secret = if data.mcaptcha_url_exists(&url_str).await? {
data.mcaptcha_update_secret(&url_str).await?
} else {
data.mcaptcha_register_instance(&url_str).await?
};
let payload = payload.into_inner();
data.mcaptcha
.share_secret(payload.url, secret, payload.auth_token)
.await?;
Ok(HttpResponse::Ok())
}
#[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Clone)]
pub struct UploadJobCreated {
id: Uuid,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
pub struct Secret {
pub secret: String,
}
#[actix_web_codegen_const_routes::post(path = "ROUTES.mcaptcha.upload")]
async fn upload(
data: AppData,
campaign: web::Path<uuid::Uuid>,
payload: web::Json<Secret>,
) -> ServiceResult<impl Responder> {
/* TODO
* 1. Authenticate: Get URL from secret
* 2. Check if campaign exists
* 3. If not: create campaign
* 4. Get last known sync point
* 5. Download results
* 6. Update sync point
*/
data.mcaptcha_authenticate(&payload.secret).await?;
// let campaign_str = campaign.to_string();
if !data
.mcaptcha_campaign_is_registered(&campaign, &payload.secret)
.await?
{
data.mcaptcha_register_campaign(&campaign, &payload.secret)
.await?;
}
let res = UploadJobCreated {
id: data.add_job(&campaign).await?,
};
Ok(HttpResponse::Created().json(res))
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct Page {
pub page: usize,
}
#[actix_web_codegen_const_routes::get(path = "ROUTES.mcaptcha.download")]
async fn download(
data: AppData,
page: web::Query<Page>,
public_id: web::Path<uuid::Uuid>,
) -> ServiceResult<impl Responder> {
const LIMIT: usize = 50;
let offset = LIMIT as isize * ((page.page as isize) - 1);
let offset = if offset < 0 { 0 } else { offset };
let public_id = public_id.into_inner();
let resp = data
.mcaptcha_analytics_fetch(&public_id, LIMIT, offset as usize)
.await?;
Ok(HttpResponse::Ok().json(resp))
}
#[cfg(test)]
mod tests {
use super::Secret;
use crate::api::v1::get_random;
use crate::mcaptcha::PerformanceAnalytics;
use crate::tests::*;
use crate::*;
use actix_web::test;
#[actix_rt::test]
async fn mcaptcha_hooks_work() {
let mcaptcha_instance =
url::Url::parse("http://mcaptcha_hooks_work.example.org").unwrap();
let mcaptcha_instance_str = mcaptcha_instance.to_string();
let campaign_id = uuid::Uuid::new_v4();
let (data, client) = get_test_data_with_mcaptcha_client().await;
let app = get_app!(data).await;
let mcaptcha_downloader =
crate::mcaptcha::MCaptchaDownloader::new(AppData::new(data.clone()));
let (mcaptcha_downloader_killer, mcaptcha_downloader_job) =
mcaptcha_downloader.start_job().await.unwrap();
if data
.mcaptcha_url_exists(&mcaptcha_instance_str)
.await
.unwrap()
{
let secret = data
.mcaptcha_update_secret(&mcaptcha_instance_str)
.await
.unwrap();
data.mcaptcha_delete_mcaptcha_instance(&mcaptcha_instance_str, &secret)
.await
.unwrap();
}
let payload = super::MCaptchaInstance {
url: mcaptcha_instance.clone(),
auth_token: get_random(23),
};
let resp = test::call_service(
&app,
post_request!(&payload, V1_API_ROUTES.mcaptcha.register).to_request(),
)
.await;
assert_eq!(resp.status(), StatusCode::OK);
let secret = {
let mut mcaptcha = payload.url.clone();
mcaptcha.set_path("/api/v1/survey/secret");
let mut x = client.client.write().unwrap();
x.remove(&mcaptcha.to_string()).unwrap()
};
let resp2 = test::call_service(
&app,
post_request!(&payload, V1_API_ROUTES.mcaptcha.register).to_request(),
)
.await;
assert_eq!(resp2.status(), StatusCode::OK);
let secret2 = {
let mut mcaptcha = payload.url.clone();
mcaptcha.set_path("/api/v1/survey/secret");
let mut x = client.client.write().unwrap();
x.remove(&mcaptcha.to_string()).unwrap()
};
assert_ne!(secret, secret2);
let secret = secret2;
let payload = Secret {
secret: secret.clone(),
};
if data
.mcaptcha_campaign_is_registered(&campaign_id, &secret)
.await
.unwrap()
{
data.mcaptcha_delete_mcaptcha_campaign(&campaign_id, &secret)
.await
.unwrap();
}
let resp = test::call_service(
&app,
post_request!(
&payload,
&V1_API_ROUTES
.mcaptcha
.get_upload_route(&campaign_id.to_string())
)
.to_request(),
)
.await;
assert_eq!(resp.status(), StatusCode::CREATED);
let job: super::UploadJobCreated = test::read_body_json(resp).await;
loop {
if let Some(job) = data.get_job(&job.id).await.unwrap() {
if job.state == *crate::db::JOB_STATE_FINISH {
break;
}
}
tokio::time::sleep(std::time::Duration::new(1, 0)).await;
}
let public_id = data
.mcaptcha_get_campaign_public_id(&campaign_id, &secret)
.await
.unwrap();
let expected = crate::mcaptcha::tests::BENCHMARK.clone();
let got = data
.mcaptcha_analytics_fetch(&public_id, 50, 0)
.await
.unwrap();
for i in 0..2 {
assert_eq!(got[i].time, expected[i].time);
assert_eq!(got[i].difficulty_factor, expected[i].difficulty_factor);
assert_eq!(got[i].worker_type, expected[i].worker_type);
}
let resp = get_request!(
&app,
&V1_API_ROUTES
.mcaptcha
.get_download_route(&public_id.to_string(), 0)
);
assert_eq!(resp.status(), StatusCode::OK);
let resp: Vec<PerformanceAnalytics> = test::read_body_json(resp).await;
assert_eq!(resp.len(), 2);
assert_eq!(resp, got);
mcaptcha_downloader_killer.send(()).unwrap();
mcaptcha_downloader_job.await.unwrap();
}
}

View file

@ -0,0 +1,46 @@
// Copyright (C) 2023 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::web::ServiceConfig;
pub mod db;
pub mod hooks;
pub fn services(cfg: &mut ServiceConfig) {
hooks::services(cfg);
}
pub mod routes {
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Mcaptcha {
pub upload: &'static str,
pub download: &'static str,
pub register: &'static str,
}
impl Mcaptcha {
pub const fn new() -> Self {
Self {
register: "/mcaptcha/api/v1/register",
upload: "/mcaptcha/api/v1/{campaign_id}/upload",
download: "/mcapthca/api/v1/{campaign_id}/download",
}
}
pub fn get_download_route(&self, campaign_id: &str, page: usize) -> String {
format!(
"{}?page={}",
self.download.replace("{campaign_id}", campaign_id),
page
)
}
pub fn get_upload_route(&self, campaign_id: &str) -> String {
self.upload.replace("{campaign_id}", campaign_id)
}
}
}

View file

@ -1,19 +1,7 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::{web, HttpResponse, Responder};
use derive_builder::Builder;
@ -29,6 +17,9 @@ pub struct BuildDetails {
}
pub mod routes {
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Meta {
pub build_details: &'static str,
pub health: &'static str,
@ -45,7 +36,7 @@ pub mod routes {
}
/// emmits build details of the bninary
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")]
#[actix_web_codegen_const_routes::get(path = "crate::V1_API_ROUTES.meta.build_details")]
async fn build_details() -> impl Responder {
let build = BuildDetails {
version: VERSION,
@ -61,7 +52,7 @@ pub struct Health {
}
/// checks all components of the system
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.health")]
#[actix_web_codegen_const_routes::get(path = "crate::V1_API_ROUTES.meta.health")]
async fn health(data: AppData) -> impl Responder {
use sqlx::Connection;
@ -87,6 +78,7 @@ mod tests {
use super::*;
use crate::api::v1::services;
use crate::tests::get_test_data;
use crate::*;
#[actix_rt::test]
@ -106,7 +98,7 @@ mod tests {
#[actix_rt::test]
async fn health_works() {
println!("{}", V1_API_ROUTES.meta.health);
let data = Data::new().await;
let data = get_test_data().await;
let app = get_app!(data).await;
let resp = test::call_service(

View file

@ -1,33 +1,26 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::web::ServiceConfig;
use serde::Deserialize;
use uuid::Uuid;
use sqlx::types::Uuid;
pub mod admin;
pub mod bench;
pub mod mcaptcha;
mod meta;
pub mod routes;
pub mod stats;
pub use routes::ROUTES;
pub fn services(cfg: &mut ServiceConfig) {
meta::services(cfg);
bench::services(cfg);
admin::services(cfg);
mcaptcha::services(cfg);
stats::services(cfg);
}
pub fn get_random(len: usize) -> String {
@ -44,7 +37,7 @@ pub fn get_random(len: usize) -> String {
}
pub fn get_uuid() -> Uuid {
Uuid::new_v4()
Uuid::parse_str(&uuid::Uuid::new_v4().to_string()).unwrap()
}
#[derive(Deserialize)]

View file

@ -1,29 +1,25 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use serde::Serialize;
use super::admin::routes::Admin;
use super::bench::routes::Benches;
use super::mcaptcha::routes::Mcaptcha;
use super::meta::routes::Meta;
use super::stats::routes::Stats;
pub const ROUTES: Routes = Routes::new();
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct Routes {
pub admin: Admin,
pub meta: Meta,
pub benches: Benches,
pub mcaptcha: Mcaptcha,
pub stats: Stats,
}
impl Routes {
@ -32,6 +28,8 @@ impl Routes {
admin: Admin::new(),
meta: Meta::new(),
benches: Benches::new(),
mcaptcha: Mcaptcha::new(),
stats: Stats::new(),
}
}
}

256
src/api/v1/stats.rs Normal file
View file

@ -0,0 +1,256 @@
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::{web, HttpResponse, Responder};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use crate::errors::*;
use crate::AppData;
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
pub struct BuildDetails {
pub version: &'static str,
pub git_commit_hash: &'static str,
}
pub mod routes {
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Stats {
pub percentile_benches: &'static str,
}
impl Stats {
pub const fn new() -> Self {
Self {
percentile_benches: "/api/v1/stats/benches/percentile",
}
}
}
}
/// Get difficulty factor with max time limit for percentile of stats
#[actix_web_codegen_const_routes::post(
path = "crate::V1_API_ROUTES.stats.percentile_benches"
)]
async fn percentile_benches(
data: AppData,
payload: web::Json<PercentileReq>,
) -> ServiceResult<impl Responder> {
struct Count {
count: Option<i64>,
}
let count = sqlx::query_as!(
Count,
"SELECT COUNT(difficulty) FROM survey_benches WHERE duration <= $1;",
payload.time as f32
)
.fetch_one(&data.db)
.await?;
if count.count.is_none() {
return Ok(HttpResponse::Ok().json(PercentileResp {
difficulty_factor: None,
}));
}
let count = count.count.unwrap();
if count < 2 {
return Ok(HttpResponse::Ok().json(PercentileResp {
difficulty_factor: None,
}));
}
let location = ((count - 1) as f64 * (payload.percentile / 100.00)) + 1.00;
let fraction = location - location.floor();
async fn get_data_at_location(
data: &crate::Data,
time: u32,
location: i64,
) -> ServiceResult<Option<u32>> {
struct Difficulty {
difficulty: Option<i32>,
}
match sqlx::query_as!(
Difficulty,
"SELECT
difficulty
FROM
survey_benches
WHERE
duration <= $1
ORDER BY difficulty ASC LIMIT 1 OFFSET $2;",
time as f32,
location as i64 - 1,
)
.fetch_one(&data.db)
.await
{
Ok(res) => Ok(Some(res.difficulty.unwrap() as u32)),
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(e.into()),
}
}
if fraction > 0.00 {
if let (Some(base), Some(ceiling)) = (
get_data_at_location(&data, payload.time, location.floor() as i64).await?,
get_data_at_location(&data, payload.time, location.floor() as i64 + 1)
.await?,
) {
let res = base as u32 + ((ceiling - base) as f64 * fraction).floor() as u32;
return Ok(HttpResponse::Ok().json(PercentileResp {
difficulty_factor: Some(res),
}));
}
} else {
if let Some(base) =
get_data_at_location(&data, payload.time, location.floor() as i64).await?
{
let res = base as u32;
return Ok(HttpResponse::Ok().json(PercentileResp {
difficulty_factor: Some(res),
}));
}
};
Ok(HttpResponse::Ok().json(PercentileResp {
difficulty_factor: None,
}))
}
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
/// Health check return datatype
pub struct PercentileReq {
time: u32,
percentile: f64,
}
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
/// Health check return datatype
pub struct PercentileResp {
difficulty_factor: Option<u32>,
}
pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(percentile_benches);
}
#[cfg(test)]
mod tests {
use actix_web::{http::StatusCode, test, App};
use super::*;
use crate::api::v1::services;
use crate::tests::get_test_data;
use crate::*;
#[actix_rt::test]
async fn stats_bench_work() {
use crate::tests::*;
const NAME: &str = "benchstatsuesr";
const EMAIL: &str = "benchstatsuesr@testadminuser.com";
const PASSWORD: &str = "longpassword2";
const DEVICE_USER_PROVIDED: &str = "foo";
const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
const THREADS: i32 = 4;
let data = get_test_data().await;
{
delete_user(NAME, &data).await;
}
let (creds, signin_resp) =
register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await;
let survey = get_survey_user(data.clone()).await;
let survey_cookie = get_cookie!(survey);
let campaign = create_new_campaign(NAME, data.clone(), cookies.clone()).await;
let campaign_config =
get_campaign_config(&campaign, data.clone(), survey_cookie.clone()).await;
assert_eq!(DIFFICULTIES.to_vec(), campaign_config.difficulties);
let submit_payload = crate::api::v1::bench::Submission {
device_user_provided: DEVICE_USER_PROVIDED.into(),
device_software_recognised: DEVICE_SOFTWARE_RECOGNISED.into(),
threads: THREADS,
benches: BENCHES.clone(),
submission_type: crate::api::v1::bench::SubmissionType::Wasm,
};
submit_bench(&submit_payload, &campaign, survey_cookie, data.clone()).await;
let msg = PercentileReq {
time: 1,
percentile: 99.00,
};
let resp = test::call_service(
&app,
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
)
.await;
assert_eq!(resp.status(), StatusCode::OK);
let resp: PercentileResp = test::read_body_json(resp).await;
assert!(resp.difficulty_factor.is_none());
let msg = PercentileReq {
time: 1,
percentile: 100.00,
};
let resp = test::call_service(
&app,
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
)
.await;
assert_eq!(resp.status(), StatusCode::OK);
let resp: PercentileResp = test::read_body_json(resp).await;
assert!(resp.difficulty_factor.is_none());
let msg = PercentileReq {
time: 2,
percentile: 100.00,
};
let resp = test::call_service(
&app,
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
)
.await;
assert_eq!(resp.status(), StatusCode::OK);
let resp: PercentileResp = test::read_body_json(resp).await;
assert_eq!(resp.difficulty_factor.unwrap(), 2);
let msg = PercentileReq {
time: 5,
percentile: 90.00,
};
let resp = test::call_service(
&app,
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
)
.await;
assert_eq!(resp.status(), StatusCode::OK);
let resp: PercentileResp = test::read_body_json(resp).await;
assert_eq!(resp.difficulty_factor.unwrap(), 4);
delete_user(NAME, &data).await;
}
}

423
src/archive.rs Normal file
View file

@ -0,0 +1,423 @@
// Copyright (C) 2023 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use sqlx::types::time::OffsetDateTime;
use sqlx::types::Uuid;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use tokio::sync::oneshot::{self, error::TryRecvError, Sender};
use tokio::task::JoinHandle;
use crate::api::v1::admin::campaigns::runners::get_results;
use crate::api::v1::admin::campaigns::SurveyResponse;
use crate::{errors::ServiceResult, AppData, Settings};
const CAMPAIGN_INFO_FILE: &str = "campaign.json";
const BENCHMARK_FILE: &str = "benchmark.csv";
pub struct Archiver {
base_path: String,
}
pub struct Archive {
now: i64,
base_path: String,
campaign: Uuid,
}
impl Archive {
pub fn new(campaign: Uuid, base_path: String) -> Self {
let now = OffsetDateTime::now_utc().unix_timestamp();
Self {
now,
campaign,
base_path,
}
}
fn campaign_path(&self) -> PathBuf {
Path::new(&self.base_path).join(&self.campaign.to_string())
}
fn archive_path_now(&self) -> PathBuf {
self.campaign_path().join(self.now.to_string())
}
fn campaign_file_path(&self) -> PathBuf {
self.archive_path_now().join(CAMPAIGN_INFO_FILE)
}
fn benchmark_file_path(&self) -> PathBuf {
self.archive_path_now().join(BENCHMARK_FILE)
}
}
impl Archiver {
pub fn new(s: &Settings) -> Self {
Archiver {
base_path: s.publish.dir.clone(),
}
}
async fn create_dir_util(p: &PathBuf) -> ServiceResult<()> {
if p.exists() {
if !p.is_dir() {
fs::remove_file(&p).await.unwrap();
fs::create_dir_all(&p).await.unwrap();
}
} else {
fs::create_dir_all(&p).await.unwrap();
}
Ok(())
}
async fn write_campaign_file(&self, c: &Campaign, a: &Archive) -> ServiceResult<()> {
let archive_path = a.archive_path_now();
Self::create_dir_util(&archive_path).await?;
let campaign_file_path = a.campaign_file_path();
let contents = serde_json::to_string(c).unwrap();
// fs::write(campaign_file_path, contents).await.unwrap();
let mut file = fs::File::create(&campaign_file_path).await.unwrap();
file.write_all(contents.as_bytes()).await.unwrap();
file.flush().await.unwrap();
Ok(())
}
fn get_headers(c: &Campaign) -> Vec<String> {
let mut keys = vec![
"ID".to_string(),
"user".to_string(),
"device_user_provided".to_string(),
"device_software_recognised".to_string(),
"threads".to_string(),
"submitted_at".to_string(),
"submission_type".to_string(),
];
let mut diff_order = Vec::with_capacity(c.difficulties.len());
for d in c.difficulties.iter() {
diff_order.push(d);
keys.push(format!("Difficulty {}", d));
}
keys
}
fn extract_record(c: &Campaign, r: SurveyResponse) -> Vec<String> {
let mut rec = vec![
r.id.to_string(),
r.user.id.to_string(),
r.device_user_provided,
r.device_software_recognised,
r.threads.map_or_else(|| "-".into(), |v| v.to_string()),
r.submitted_at.to_string(),
r.submission_type.to_string(),
];
for d in c.difficulties.iter() {
let bench = r
.benches
.iter()
.find(|b| b.difficulty == *d as i32)
.map_or_else(|| "-".into(), |v| v.duration.to_string());
rec.push(bench);
}
rec
}
async fn write_benchmark_file(
&self,
c: &Campaign,
archive: &Archive,
data: &AppData,
) -> ServiceResult<()> {
let archive_path = archive.archive_path_now();
Self::create_dir_util(&archive_path).await?;
let benchmark_file_path = archive.benchmark_file_path();
struct Username {
name: String,
}
let owner = sqlx::query_as!(
Username,
"SELECT
survey_admins.name
FROM
survey_admins
INNER JOIN survey_campaigns ON
survey_admins.ID = survey_campaigns.user_id
WHERE
survey_campaigns.ID = $1
",
&Uuid::parse_str(&c.id.to_string()).unwrap()
)
.fetch_one(&data.db)
.await?;
let mut page = 0;
let limit = 50;
let file = fs::OpenOptions::new()
.read(true)
.append(true)
.create(true)
.open(&benchmark_file_path)
.await
.unwrap();
let mut wri = csv_async::AsyncWriter::from_writer(file);
let keys = Self::get_headers(c);
wri.write_record(&keys).await.unwrap();
loop {
let mut resp = get_results(
&owner.name,
&Uuid::parse_str(&c.id.to_string()).unwrap(),
data,
page,
limit,
None,
)
.await?;
for r in resp.drain(0..) {
let rec = Self::extract_record(c, r);
wri.write_record(&rec).await.unwrap();
wri.flush().await.unwrap();
}
if resp.len() < limit {
break;
} else {
page += 1
}
}
Ok(())
}
pub async fn init_archive_job(
self,
data: AppData,
) -> ServiceResult<(Sender<bool>, JoinHandle<()>)> {
let (tx, mut rx) = oneshot::channel();
fn can_run(rx: &mut oneshot::Receiver<bool>) -> bool {
match rx.try_recv() {
Err(TryRecvError::Empty) => true,
_ => false,
}
}
let job = async move {
loop {
if !can_run(&mut rx) {
log::info!("Killing archive loop: received signal");
break;
}
for _ in 0..data.settings.publish.duration {
if !can_run(&mut rx) {
log::info!("Killing archive loop: received signal");
break;
}
tokio::time::sleep(std::time::Duration::new(1, 0)).await;
}
let _ = self.archive(&data).await;
}
};
let job_fut = tokio::spawn(job);
Ok((tx, job_fut))
}
pub async fn archive(&self, data: &AppData) -> ServiceResult<()> {
let mut db_campaigns = sqlx::query_as!(
InnerCampaign,
"SELECT ID, name, difficulties, created_at FROM survey_campaigns"
)
.fetch_all(&data.db)
.await?;
for c in db_campaigns.drain(0..) {
let archive = Archive::new(c.id.clone(), self.base_path.clone());
let campaign: Campaign = c.into();
self.write_campaign_file(&campaign, &archive).await?;
self.write_benchmark_file(&campaign, &archive, data).await?;
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct InnerCampaign {
id: Uuid,
name: String,
difficulties: Vec<i32>,
created_at: OffsetDateTime,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Campaign {
pub id: uuid::Uuid,
pub name: String,
pub difficulties: Vec<u32>,
pub created_at: i64,
}
impl From<InnerCampaign> for Campaign {
fn from(i: InnerCampaign) -> Self {
Self {
id: uuid::Uuid::parse_str(&i.id.to_string()).unwrap(),
name: i.name,
difficulties: i.difficulties.iter().map(|d| *d as u32).collect(),
created_at: i.created_at.unix_timestamp(),
}
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use csv_async::StringRecord;
use futures::stream::StreamExt;
use crate::api::v1::bench::Submission;
use crate::api::v1::bench::SubmissionType;
use crate::*;
use super::*;
use mktemp::Temp;
#[test]
fn archive_path_works() {
let mut settings = Settings::new().unwrap();
let tmp_dir = Temp::new_dir().unwrap();
settings.publish.dir = tmp_dir.join("base_path").to_str().unwrap().into();
let uuid = Uuid::new_v4();
let archive = Archive::new(uuid.clone(), settings.publish.dir.clone());
let archive_path = archive.archive_path_now();
assert_eq!(
archive_path,
Path::new(&settings.publish.dir)
.join(&uuid.to_string())
.join(&archive.now.to_string())
);
let campaign_file_path = archive.campaign_file_path();
assert_eq!(
campaign_file_path,
Path::new(&settings.publish.dir)
.join(&uuid.to_string())
.join(&archive.now.to_string())
.join(CAMPAIGN_INFO_FILE)
);
let benchmark_file_path = archive.benchmark_file_path();
assert_eq!(
benchmark_file_path,
Path::new(&settings.publish.dir)
.join(&uuid.to_string())
.join(&archive.now.to_string())
.join(BENCHMARK_FILE)
);
}
#[actix_rt::test]
async fn archive_is_correct_test() {
use crate::tests::*;
const NAME: &str = "arciscorrecttesuser";
const EMAIL: &str = "archive_is_correct_testuser@testadminuser.com";
const PASSWORD: &str = "longpassword2";
const DEVICE_USER_PROVIDED: &str = "foo";
const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
const THREADS: i32 = 4;
let data = get_test_data().await;
{
delete_user(NAME, &data).await;
}
//let campaign: Campaign = c.into();
//let archive = Archive::new(campaign.id.clone(), self.base_path.clone());
//self.write_campaign_file(&campaign, &archive).await?;
//self.write_benchmark_file(&campaign, &archive, data).await?;
let (creds, signin_resp) =
register_and_signin(&data, NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let survey = get_survey_user(data.clone()).await;
let survey_cookie = get_cookie!(survey);
let campaign = create_new_campaign(NAME, data.clone(), cookies.clone()).await;
let campaign_config =
get_campaign_config(&campaign, data.clone(), survey_cookie.clone()).await;
assert_eq!(DIFFICULTIES.to_vec(), campaign_config.difficulties);
let submit_payload = Submission {
device_user_provided: DEVICE_USER_PROVIDED.into(),
device_software_recognised: DEVICE_SOFTWARE_RECOGNISED.into(),
threads: THREADS,
benches: BENCHES.clone(),
submission_type: SubmissionType::Wasm,
};
let _proof =
submit_bench(&submit_payload, &campaign, survey_cookie, data.clone()).await;
let campaign_id = Uuid::from_str(&campaign.campaign_id).unwrap();
let db_campaign = sqlx::query_as!(
InnerCampaign,
"SELECT ID, name, difficulties, created_at FROM survey_campaigns WHERE ID = $1",
campaign_id,
)
.fetch_one(&data.db)
.await.unwrap();
let campaign: Campaign = db_campaign.into();
let archive = Archive::new(
Uuid::parse_str(&campaign.id.to_string()).unwrap(),
data.settings.publish.dir.clone(),
);
let archiver = Archiver::new(&data.settings);
archiver.archive(&AppData::new(data.clone())).await.unwrap();
let contents: Campaign = serde_json::from_str(
&fs::read_to_string(&archive.campaign_file_path())
.await
.unwrap(),
)
.unwrap();
assert_eq!(contents, campaign);
let page = 0;
let limit = 10;
let mut responses = get_results(
NAME,
&campaign_id,
&AppData::new(data.clone()),
page,
limit,
None,
)
.await
.unwrap();
assert_eq!(responses.len(), 1);
let r = responses.pop().unwrap();
let rec = Archiver::extract_record(&campaign, r);
let mut rdr = csv_async::AsyncReader::from_reader(
fs::File::open(archive.benchmark_file_path()).await.unwrap(),
);
let mut records = rdr.records();
assert_eq!(
records.next().await.unwrap().unwrap(),
StringRecord::from(rec)
);
}
}

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
SPDX-License-Identifier: AGPL-3.0-or-later

View file

@ -1,19 +1,8 @@
/*
* Copyright (C) 2021 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/>.
*/
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
//! App data: database connections, etc.
use std::sync::Arc;
use std::thread;
@ -22,13 +11,17 @@ use argon2_creds::{Config, ConfigBuilder, PasswordPolicy};
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use crate::SETTINGS;
use crate::mcaptcha::*;
use crate::settings::Settings;
/// App data
pub struct Data {
/// databse pool
/// database pool
pub db: PgPool,
pub creds: Config,
pub settings: Settings,
pub mcaptcha: Box<dyn MCaptchaClient>,
}
impl Data {
@ -44,7 +37,10 @@ impl Data {
#[cfg(not(tarpaulin_include))]
/// create new instance of app data
pub async fn new() -> Arc<Self> {
pub async fn new(
settings: Settings,
mcaptcha: Box<dyn MCaptchaClient>,
) -> Arc<Self> {
let creds = Self::get_creds();
let c = creds.clone();
#[allow(unused_variables)]
@ -55,14 +51,19 @@ impl Data {
});
let db = PgPoolOptions::new()
.max_connections(SETTINGS.database.pool)
.connect(&SETTINGS.database.url)
.max_connections(settings.database.pool)
.connect(&settings.database.url)
.await
.expect("Unable to form database pool");
#[cfg(not(debug_assertions))]
init.join().unwrap();
let data = Data { db, creds };
let data = Data {
db,
creds,
settings,
mcaptcha,
};
Arc::new(data)
}

91
src/db.rs Normal file
View file

@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)]
pub struct JobState {
pub name: String,
}
impl JobState {
pub fn new(name: String) -> Self {
Self { name }
}
}
lazy_static! {
pub static ref JOB_STATE_CREATE: JobState = JobState::new("job.state.create".into());
pub static ref JOB_STATE_FINISH: JobState = JobState::new("job.state.finish".into());
pub static ref JOB_STATE_RUNNING: JobState =
JobState::new("job.state.running".into());
pub static ref JOB_STATES: [&'static JobState; 3] =
[&*JOB_STATE_CREATE, &*JOB_STATE_FINISH, &*JOB_STATE_RUNNING];
}
async fn job_state_exists(
db: &PgPool,
job_state: &JobState,
) -> sqlx::error::Result<bool> {
let res = sqlx::query!(
"SELECT EXISTS (SELECT 1 from survey_mcaptcha_upload_job_states WHERE name = $1)",
job_state.name,
)
.fetch_one(db)
.await?;
let mut resp = false;
if let Some(x) = res.exists {
resp = x;
}
Ok(resp)
}
async fn create_job_states(db: &PgPool) -> sqlx::error::Result<()> {
for j in &*JOB_STATES {
if !job_state_exists(db, j).await? {
sqlx::query!(
"INSERT INTO survey_mcaptcha_upload_job_states
(name) VALUES ($1) ON CONFLICT (name) DO NOTHING;",
j.name
)
.execute(db)
.await?;
}
}
Ok(())
}
pub async fn migrate_db(db: &PgPool) -> sqlx::error::Result<()> {
sqlx::migrate!("./migrations/").run(db).await?;
create_job_states(db).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[actix_rt::test]
async fn test_mcaptcha_job_states_exist() {
// can't use crate::tests::get_test_data because this module is used by
// ./src/tests-migrate.rs too, which doesn't load tests module
let settings = crate::settings::Settings::new().unwrap();
let db = sqlx::postgres::PgPoolOptions::new()
.max_connections(2)
.connect(&settings.database.url)
.await
.expect("Unable to form database pool");
migrate_db(&db).await.unwrap();
for e in (*JOB_STATES).iter() {
println!("checking job state {}", e.name);
assert!(job_state_exists(&db, e).await.unwrap());
}
}
}

Some files were not shown because too many files have changed in this diff Show more