From 2e2adce7f47c1e90a729663fc4e0380929e640dc Mon Sep 17 00:00:00 2001 From: Chen Steenvoorden Date: Fri, 3 Apr 2026 03:00:11 +0200 Subject: [PATCH 1/7] Record cleanup (1/?) --- .github/workflows/pre-merge-checks.yml | 3 +- .gitignore | 2 +- ...af3351a6629a942b732ccf6740b2e683eedae.json | 0 ...aaf466dc8a7da6fbcab592422569ba22eeeb2.json | 12 + ...0f6564320611495cae59db3a23036792e5b07.json | 0 ...a60e17066d80cd0a0c404a4031dae4df01781.json | 0 ...a35973a4333a3d19ec0b9e3dfabbd0bc97b8c.json | 0 ...31e6b9e29f27da54f8ed525dc71e8f3162a0a.json | 0 ...3c60fd9160da037b78741eab97ca4ea214f0b.json | 4 +- ...c4971bd7f593a6bcec201970fe5ae9e120e7f.json | 0 ...eb11432326d631d24b5ebaea76946aa913df1.json | 0 ...d99a87b879306f730d728f6887fd80a25d168.json | 4 +- ...b16b87bf12743e25be5d1707c07edd8d94dfd.json | 0 ...0845a788f13b570be1e71ce9ff40de6035291.json | 4 +- ...f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json | 0 ...68774d5c9484d6cdce61f6039b7fcb35cca4c.json | 0 ...2667d2e04eb76fae182fbc33af16fc28af2e5.json | 0 ...9d27b565235a41e7d747d41e49238674fb5c3.json | 4 +- ...7a84e910024d0f8666dd30db4fc3a2e5527af.json | 0 ...dc8fdfc05f489328e8376513124dfb42996e3.json | 0 ...7c47896571f1b0b9b844e84e298f87e3ed09b.json | 0 ...5f60fb2209a53e8360ea5f1675b99667306b2.json | 4 +- ...8370944664024942cc5276a17cb79385be874.json | 4 +- ...462cd94a548cd83fbc39fb826ced7c0bce9f1.json | 0 ...9b2532cd7d4b2dfc33d4a5735c33f2be64794.json | 4 +- ...c5602d215db4e5cf61484cf7b80c84b0cfe5c.json | 0 ...f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json | 0 ...c4842facf875aa48e0b63003bd4f8e043597b.json | 0 ...21e1264cb90f1da58b9220bce53371eebd851.json | 0 ...dad562d8eecde82db258dc183868b211f4410.json | 0 ...7ecac61590f27b02ac8540f5de42e7bdb2064.json | 0 ...338dbc7ed6bed3ac6cfa696391dc95e69c18f.json | 4 +- ...2e6fd3fabcc164935906cd974dabd677ec14f.json | 0 ...b829c0a9d46d6dc4aa18e76f9fd309e207d54.json | 0 api/src/client.rs | 11 +- api/src/get.rs | 59 ----- api/src/lib.rs | 1 - cli/src/client.rs | 2 +- common/src/records/assignment.rs | 52 ++--- common/src/records/file.rs | 23 +- common/src/records/mod.rs | 84 +++++++ common/src/records/platform.rs | 32 ++- common/src/records/project.rs | 40 ++-- common/src/records/project_version.rs | 59 ++--- common/src/records/result.rs | 56 ++--- common/src/records/task.rs | 42 ++-- common/src/records/user.rs | 29 +-- server/example.env => example.env | 0 ...843f1842eee48fbadd80550e982f0dd308a79.json | 12 - server/src/auth.rs | 7 +- server/src/main.rs | 51 ++--- server/src/routes/mod.rs | 6 +- server/src/routes/validate_submit.rs | 20 +- server/src/tasks/mod.rs | 3 + .../src/tasks/update_expired_assignments.rs | 28 +++ server/src/util/assignment_deadline.rs | 19 -- server/src/util/mod.rs | 18 -- server/src/util/select.rs | 214 ------------------ server/src/util/set_assignment_state.rs | 24 -- server/src/util/set_result_state.rs | 21 -- 60 files changed, 337 insertions(+), 625 deletions(-) rename {server/.sqlx => .sqlx}/query-1260e6fd3c1f30f651d1f86bf86af3351a6629a942b732ccf6740b2e683eedae.json (100%) create mode 100644 .sqlx/query-1514f5c5e99670f6029e561dc5aaaf466dc8a7da6fbcab592422569ba22eeeb2.json rename {server/.sqlx => .sqlx}/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json (100%) rename {server/.sqlx => .sqlx}/query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json (100%) rename {server/.sqlx => .sqlx}/query-29356ceade5be500173e8655241a35973a4333a3d19ec0b9e3dfabbd0bc97b8c.json (100%) rename {server/.sqlx => .sqlx}/query-2daeacfdb74c4d12e2e5801d58931e6b9e29f27da54f8ed525dc71e8f3162a0a.json (100%) rename server/.sqlx/query-71c5f3dc504e67ae2a50509256f2d883e6806c44c0d24a9f3bd1ca153aecaf5b.json => .sqlx/query-32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b.json (69%) rename {server/.sqlx => .sqlx}/query-37247bf346c477389245f83ce58c4971bd7f593a6bcec201970fe5ae9e120e7f.json (100%) rename {server/.sqlx => .sqlx}/query-37787d0e5dbb0fd034a68efbe9eeb11432326d631d24b5ebaea76946aa913df1.json (100%) rename server/.sqlx/query-d87fa0f565013412bab63b399f7dd435b8f6ef317eec2981cf24b97d9a1394ed.json => .sqlx/query-381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168.json (81%) rename {server/.sqlx => .sqlx}/query-4f28c9855a87500c39fc4e88308b16b87bf12743e25be5d1707c07edd8d94dfd.json (100%) rename server/.sqlx/query-0a85c57626456d79f6a57c607437b2e6b5a31199b622f28ca3692fe60eec53c9.json => .sqlx/query-60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291.json (79%) rename {server/.sqlx => .sqlx}/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json (100%) rename {server/.sqlx => .sqlx}/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json (100%) rename {server/.sqlx => .sqlx}/query-7305287ecfbb560215b39bac1bb2667d2e04eb76fae182fbc33af16fc28af2e5.json (100%) rename server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json => .sqlx/query-7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3.json (67%) rename {server/.sqlx => .sqlx}/query-7d25b6a7d50f8cde47e1e7c38677a84e910024d0f8666dd30db4fc3a2e5527af.json (100%) rename {server/.sqlx => .sqlx}/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json (100%) rename {server/.sqlx => .sqlx}/query-8660893ff85be731039fb2402bf7c47896571f1b0b9b844e84e298f87e3ed09b.json (100%) rename server/.sqlx/query-0115ccc7673022c6b942fec6ce5fe24dd36f3b7bf5dfa7b6c56b0ec8f1fc4a6b.json => .sqlx/query-a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2.json (69%) rename server/.sqlx/query-0d6d91c4e0acb78e4fda4df1804d430966e8ae83a168de0d3444bb8a4c7b1051.json => .sqlx/query-b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874.json (80%) rename {server/.sqlx => .sqlx}/query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json (100%) rename server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json => .sqlx/query-c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794.json (75%) rename {server/.sqlx => .sqlx}/query-ce2921487afc54738ea394ab248c5602d215db4e5cf61484cf7b80c84b0cfe5c.json (100%) rename {server/.sqlx => .sqlx}/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json (100%) rename {server/.sqlx => .sqlx}/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json (100%) rename {server/.sqlx => .sqlx}/query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json (100%) rename {server/.sqlx => .sqlx}/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json (100%) rename {server/.sqlx => .sqlx}/query-deaec7b6fc41372bf2231ec85b57ecac61590f27b02ac8540f5de42e7bdb2064.json (100%) rename server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json => .sqlx/query-e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f.json (70%) rename {server/.sqlx => .sqlx}/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json (100%) rename {server/.sqlx => .sqlx}/query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json (100%) delete mode 100644 api/src/get.rs rename server/example.env => example.env (100%) delete mode 100644 server/.sqlx/query-71c855f936697605ccfd2869c81843f1842eee48fbadd80550e982f0dd308a79.json create mode 100644 server/src/tasks/mod.rs create mode 100644 server/src/tasks/update_expired_assignments.rs delete mode 100644 server/src/util/assignment_deadline.rs delete mode 100644 server/src/util/mod.rs delete mode 100644 server/src/util/select.rs delete mode 100644 server/src/util/set_assignment_state.rs delete mode 100644 server/src/util/set_result_state.rs diff --git a/.github/workflows/pre-merge-checks.yml b/.github/workflows/pre-merge-checks.yml index 4c3f04b..4e204fb 100644 --- a/.github/workflows/pre-merge-checks.yml +++ b/.github/workflows/pre-merge-checks.yml @@ -35,5 +35,4 @@ jobs: - name: Cargo Format Check run: cargo fmt --check - name: Sqlx Prepare Check - run: cargo sqlx prepare --check - working-directory: ./server + run: cargo sqlx prepare --check --workspace diff --git a/.gitignore b/.gitignore index 860734b..4f83806 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /target -/server/.env +/.env diff --git a/server/.sqlx/query-1260e6fd3c1f30f651d1f86bf86af3351a6629a942b732ccf6740b2e683eedae.json b/.sqlx/query-1260e6fd3c1f30f651d1f86bf86af3351a6629a942b732ccf6740b2e683eedae.json similarity index 100% rename from server/.sqlx/query-1260e6fd3c1f30f651d1f86bf86af3351a6629a942b732ccf6740b2e683eedae.json rename to .sqlx/query-1260e6fd3c1f30f651d1f86bf86af3351a6629a942b732ccf6740b2e683eedae.json diff --git a/.sqlx/query-1514f5c5e99670f6029e561dc5aaaf466dc8a7da6fbcab592422569ba22eeeb2.json b/.sqlx/query-1514f5c5e99670f6029e561dc5aaaf466dc8a7da6fbcab592422569ba22eeeb2.json new file mode 100644 index 0000000..0390752 --- /dev/null +++ b/.sqlx/query-1514f5c5e99670f6029e561dc5aaaf466dc8a7da6fbcab592422569ba22eeeb2.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE\n assignments\n SET\n state = 'expired'\n WHERE\n state = 'init'\n AND deadline_at < now()\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "1514f5c5e99670f6029e561dc5aaaf466dc8a7da6fbcab592422569ba22eeeb2" +} diff --git a/server/.sqlx/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json b/.sqlx/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json similarity index 100% rename from server/.sqlx/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json rename to .sqlx/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json diff --git a/server/.sqlx/query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json b/.sqlx/query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json similarity index 100% rename from server/.sqlx/query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json rename to .sqlx/query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json diff --git a/server/.sqlx/query-29356ceade5be500173e8655241a35973a4333a3d19ec0b9e3dfabbd0bc97b8c.json b/.sqlx/query-29356ceade5be500173e8655241a35973a4333a3d19ec0b9e3dfabbd0bc97b8c.json similarity index 100% rename from server/.sqlx/query-29356ceade5be500173e8655241a35973a4333a3d19ec0b9e3dfabbd0bc97b8c.json rename to .sqlx/query-29356ceade5be500173e8655241a35973a4333a3d19ec0b9e3dfabbd0bc97b8c.json diff --git a/server/.sqlx/query-2daeacfdb74c4d12e2e5801d58931e6b9e29f27da54f8ed525dc71e8f3162a0a.json b/.sqlx/query-2daeacfdb74c4d12e2e5801d58931e6b9e29f27da54f8ed525dc71e8f3162a0a.json similarity index 100% rename from server/.sqlx/query-2daeacfdb74c4d12e2e5801d58931e6b9e29f27da54f8ed525dc71e8f3162a0a.json rename to .sqlx/query-2daeacfdb74c4d12e2e5801d58931e6b9e29f27da54f8ed525dc71e8f3162a0a.json diff --git a/server/.sqlx/query-71c5f3dc504e67ae2a50509256f2d883e6806c44c0d24a9f3bd1ca153aecaf5b.json b/.sqlx/query-32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b.json similarity index 69% rename from server/.sqlx/query-71c5f3dc504e67ae2a50509256f2d883e6806c44c0d24a9f3bd1ca153aecaf5b.json rename to .sqlx/query-32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b.json index 7a61719..0c9571b 100644 --- a/server/.sqlx/query-71c5f3dc504e67ae2a50509256f2d883e6806c44c0d24a9f3bd1ca153aecaf5b.json +++ b/.sqlx/query-32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n projects\n WHERE\n created_by_user_id = $1 IS NOT FALSE\n AND disabled_at IS NULL IS DISTINCT FROM $2\n ", + "query": "SELECT * FROM projects WHERE TRUE AND (created_by_user_id = $1 IS NOT FALSE) AND (disabled_at IS NULL IS DISTINCT FROM $2)", "describe": { "columns": [ { @@ -43,5 +43,5 @@ false ] }, - "hash": "71c5f3dc504e67ae2a50509256f2d883e6806c44c0d24a9f3bd1ca153aecaf5b" + "hash": "32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b" } diff --git a/server/.sqlx/query-37247bf346c477389245f83ce58c4971bd7f593a6bcec201970fe5ae9e120e7f.json b/.sqlx/query-37247bf346c477389245f83ce58c4971bd7f593a6bcec201970fe5ae9e120e7f.json similarity index 100% rename from server/.sqlx/query-37247bf346c477389245f83ce58c4971bd7f593a6bcec201970fe5ae9e120e7f.json rename to .sqlx/query-37247bf346c477389245f83ce58c4971bd7f593a6bcec201970fe5ae9e120e7f.json diff --git a/server/.sqlx/query-37787d0e5dbb0fd034a68efbe9eeb11432326d631d24b5ebaea76946aa913df1.json b/.sqlx/query-37787d0e5dbb0fd034a68efbe9eeb11432326d631d24b5ebaea76946aa913df1.json similarity index 100% rename from server/.sqlx/query-37787d0e5dbb0fd034a68efbe9eeb11432326d631d24b5ebaea76946aa913df1.json rename to .sqlx/query-37787d0e5dbb0fd034a68efbe9eeb11432326d631d24b5ebaea76946aa913df1.json diff --git a/server/.sqlx/query-d87fa0f565013412bab63b399f7dd435b8f6ef317eec2981cf24b97d9a1394ed.json b/.sqlx/query-381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168.json similarity index 81% rename from server/.sqlx/query-d87fa0f565013412bab63b399f7dd435b8f6ef317eec2981cf24b97d9a1394ed.json rename to .sqlx/query-381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168.json index b16c3f7..4e06eaf 100644 --- a/server/.sqlx/query-d87fa0f565013412bab63b399f7dd435b8f6ef317eec2981cf24b97d9a1394ed.json +++ b/.sqlx/query-381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n results\n WHERE\n assignment_id = $1 IS NOT FALSE\n AND (group_result_id = $2 OR $2 IS NULL)\n AND state = $3 IS NOT FALSE\n ", + "query": "SELECT * FROM results WHERE TRUE AND (assignment_id = $1 IS NOT FALSE) AND (group_result_id = $2 OR $2 IS NULL) AND (state = $3 IS NOT FALSE)", "describe": { "columns": [ { @@ -88,5 +88,5 @@ true ] }, - "hash": "d87fa0f565013412bab63b399f7dd435b8f6ef317eec2981cf24b97d9a1394ed" + "hash": "381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168" } diff --git a/server/.sqlx/query-4f28c9855a87500c39fc4e88308b16b87bf12743e25be5d1707c07edd8d94dfd.json b/.sqlx/query-4f28c9855a87500c39fc4e88308b16b87bf12743e25be5d1707c07edd8d94dfd.json similarity index 100% rename from server/.sqlx/query-4f28c9855a87500c39fc4e88308b16b87bf12743e25be5d1707c07edd8d94dfd.json rename to .sqlx/query-4f28c9855a87500c39fc4e88308b16b87bf12743e25be5d1707c07edd8d94dfd.json diff --git a/server/.sqlx/query-0a85c57626456d79f6a57c607437b2e6b5a31199b622f28ca3692fe60eec53c9.json b/.sqlx/query-60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291.json similarity index 79% rename from server/.sqlx/query-0a85c57626456d79f6a57c607437b2e6b5a31199b622f28ca3692fe60eec53c9.json rename to .sqlx/query-60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291.json index ee9c8a1..6bc2050 100644 --- a/server/.sqlx/query-0a85c57626456d79f6a57c607437b2e6b5a31199b622f28ca3692fe60eec53c9.json +++ b/.sqlx/query-60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n assignments\n WHERE\n task_id = $1 IS NOT FALSE\n AND user_id = $2 IS NOT FALSE\n AND state = $3 IS NOT FALSE\n ", + "query": "SELECT * FROM assignments WHERE TRUE AND (task_id = $1 IS NOT FALSE) AND (user_id = $2 IS NOT FALSE) AND (state = $3 IS NOT FALSE)", "describe": { "columns": [ { @@ -74,5 +74,5 @@ false ] }, - "hash": "0a85c57626456d79f6a57c607437b2e6b5a31199b622f28ca3692fe60eec53c9" + "hash": "60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291" } diff --git a/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json b/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json similarity index 100% rename from server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json rename to .sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json diff --git a/server/.sqlx/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json b/.sqlx/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json similarity index 100% rename from server/.sqlx/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json rename to .sqlx/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json diff --git a/server/.sqlx/query-7305287ecfbb560215b39bac1bb2667d2e04eb76fae182fbc33af16fc28af2e5.json b/.sqlx/query-7305287ecfbb560215b39bac1bb2667d2e04eb76fae182fbc33af16fc28af2e5.json similarity index 100% rename from server/.sqlx/query-7305287ecfbb560215b39bac1bb2667d2e04eb76fae182fbc33af16fc28af2e5.json rename to .sqlx/query-7305287ecfbb560215b39bac1bb2667d2e04eb76fae182fbc33af16fc28af2e5.json diff --git a/server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json b/.sqlx/query-7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3.json similarity index 67% rename from server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json rename to .sqlx/query-7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3.json index 8461a2f..f603072 100644 --- a/server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json +++ b/.sqlx/query-7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n project_versions\n WHERE\n disabled_at IS NULL IS DISTINCT FROM $1\n AND project_id = $2 IS NOT FALSE\n AND platform_id = $3 IS NOT FALSE\n AND file_id = $4 IS NOT FALSE\n ", + "query": "SELECT * FROM project_versions WHERE TRUE AND (disabled_at IS NULL IS DISTINCT FROM $1) AND (project_id = $2 IS NOT FALSE) AND (platform_id = $3 IS NOT FALSE) AND (file_id = $4 IS NOT FALSE)", "describe": { "columns": [ { @@ -51,5 +51,5 @@ false ] }, - "hash": "767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38" + "hash": "7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3" } diff --git a/server/.sqlx/query-7d25b6a7d50f8cde47e1e7c38677a84e910024d0f8666dd30db4fc3a2e5527af.json b/.sqlx/query-7d25b6a7d50f8cde47e1e7c38677a84e910024d0f8666dd30db4fc3a2e5527af.json similarity index 100% rename from server/.sqlx/query-7d25b6a7d50f8cde47e1e7c38677a84e910024d0f8666dd30db4fc3a2e5527af.json rename to .sqlx/query-7d25b6a7d50f8cde47e1e7c38677a84e910024d0f8666dd30db4fc3a2e5527af.json diff --git a/server/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json b/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json similarity index 100% rename from server/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json rename to .sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json diff --git a/server/.sqlx/query-8660893ff85be731039fb2402bf7c47896571f1b0b9b844e84e298f87e3ed09b.json b/.sqlx/query-8660893ff85be731039fb2402bf7c47896571f1b0b9b844e84e298f87e3ed09b.json similarity index 100% rename from server/.sqlx/query-8660893ff85be731039fb2402bf7c47896571f1b0b9b844e84e298f87e3ed09b.json rename to .sqlx/query-8660893ff85be731039fb2402bf7c47896571f1b0b9b844e84e298f87e3ed09b.json diff --git a/server/.sqlx/query-0115ccc7673022c6b942fec6ce5fe24dd36f3b7bf5dfa7b6c56b0ec8f1fc4a6b.json b/.sqlx/query-a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2.json similarity index 69% rename from server/.sqlx/query-0115ccc7673022c6b942fec6ce5fe24dd36f3b7bf5dfa7b6c56b0ec8f1fc4a6b.json rename to .sqlx/query-a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2.json index 6abbd18..bec3d40 100644 --- a/server/.sqlx/query-0115ccc7673022c6b942fec6ce5fe24dd36f3b7bf5dfa7b6c56b0ec8f1fc4a6b.json +++ b/.sqlx/query-a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n users\n WHERE\n disabled_at IS NULL IS DISTINCT FROM $1\n ", + "query": "SELECT * FROM users WHERE TRUE AND (disabled_at IS NULL IS DISTINCT FROM $1)", "describe": { "columns": [ { @@ -36,5 +36,5 @@ false ] }, - "hash": "0115ccc7673022c6b942fec6ce5fe24dd36f3b7bf5dfa7b6c56b0ec8f1fc4a6b" + "hash": "a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2" } diff --git a/server/.sqlx/query-0d6d91c4e0acb78e4fda4df1804d430966e8ae83a168de0d3444bb8a4c7b1051.json b/.sqlx/query-b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874.json similarity index 80% rename from server/.sqlx/query-0d6d91c4e0acb78e4fda4df1804d430966e8ae83a168de0d3444bb8a4c7b1051.json rename to .sqlx/query-b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874.json index 5d88e99..8482544 100644 --- a/server/.sqlx/query-0d6d91c4e0acb78e4fda4df1804d430966e8ae83a168de0d3444bb8a4c7b1051.json +++ b/.sqlx/query-b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n tasks\n WHERE\n project_id = $1 IS NOT FALSE\n ", + "query": "SELECT * FROM tasks WHERE TRUE AND (project_id = $1 IS NOT FALSE)", "describe": { "columns": [ { @@ -60,5 +60,5 @@ false ] }, - "hash": "0d6d91c4e0acb78e4fda4df1804d430966e8ae83a168de0d3444bb8a4c7b1051" + "hash": "b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874" } diff --git a/server/.sqlx/query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json b/.sqlx/query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json similarity index 100% rename from server/.sqlx/query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json rename to .sqlx/query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json diff --git a/server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json b/.sqlx/query-c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794.json similarity index 75% rename from server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json rename to .sqlx/query-c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794.json index 4d6dbfb..b1790fd 100644 --- a/server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json +++ b/.sqlx/query-c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n files\n ", + "query": "SELECT * FROM files WHERE TRUE", "describe": { "columns": [ { @@ -34,5 +34,5 @@ false ] }, - "hash": "a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92" + "hash": "c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794" } diff --git a/server/.sqlx/query-ce2921487afc54738ea394ab248c5602d215db4e5cf61484cf7b80c84b0cfe5c.json b/.sqlx/query-ce2921487afc54738ea394ab248c5602d215db4e5cf61484cf7b80c84b0cfe5c.json similarity index 100% rename from server/.sqlx/query-ce2921487afc54738ea394ab248c5602d215db4e5cf61484cf7b80c84b0cfe5c.json rename to .sqlx/query-ce2921487afc54738ea394ab248c5602d215db4e5cf61484cf7b80c84b0cfe5c.json diff --git a/server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json b/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json similarity index 100% rename from server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json rename to .sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json diff --git a/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json b/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json similarity index 100% rename from server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json rename to .sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json diff --git a/server/.sqlx/query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json b/.sqlx/query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json similarity index 100% rename from server/.sqlx/query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json rename to .sqlx/query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json diff --git a/server/.sqlx/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json b/.sqlx/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json similarity index 100% rename from server/.sqlx/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json rename to .sqlx/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json diff --git a/server/.sqlx/query-deaec7b6fc41372bf2231ec85b57ecac61590f27b02ac8540f5de42e7bdb2064.json b/.sqlx/query-deaec7b6fc41372bf2231ec85b57ecac61590f27b02ac8540f5de42e7bdb2064.json similarity index 100% rename from server/.sqlx/query-deaec7b6fc41372bf2231ec85b57ecac61590f27b02ac8540f5de42e7bdb2064.json rename to .sqlx/query-deaec7b6fc41372bf2231ec85b57ecac61590f27b02ac8540f5de42e7bdb2064.json diff --git a/server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json b/.sqlx/query-e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f.json similarity index 70% rename from server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json rename to .sqlx/query-e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f.json index e4524c8..4f7b456 100644 --- a/server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json +++ b/.sqlx/query-e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n platforms\n WHERE\n file_id = $1 IS NOT FALSE\n ", + "query": "SELECT * FROM platforms WHERE TRUE AND (file_id = $1 IS NOT FALSE)", "describe": { "columns": [ { @@ -36,5 +36,5 @@ false ] }, - "hash": "2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881" + "hash": "e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f" } diff --git a/server/.sqlx/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json b/.sqlx/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json similarity index 100% rename from server/.sqlx/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json rename to .sqlx/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json diff --git a/server/.sqlx/query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json b/.sqlx/query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json similarity index 100% rename from server/.sqlx/query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json rename to .sqlx/query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json diff --git a/api/src/client.rs b/api/src/client.rs index 78fe40a..72836bc 100644 --- a/api/src/client.rs +++ b/api/src/client.rs @@ -1,6 +1,6 @@ use clusterizer_common::{ errors::{FetchTasksError, Infallible, NotFound, RegisterError, SubmitResultError}, - records::Task, + records::{Record, Task}, requests::{FetchTasksRequest, RegisterRequest, SubmitResultRequest}, responses::RegisterResponse, types::Id, @@ -8,10 +8,7 @@ use clusterizer_common::{ use reqwest::{IntoUrl, RequestBuilder, Response, header}; use serde::{Serialize, de::DeserializeOwned}; -use crate::{ - get::Get, - result::{ApiError, ApiResult}, -}; +use crate::result::{ApiError, ApiResult}; pub struct ApiClient { client: reqwest::Client, @@ -28,7 +25,7 @@ impl ApiClient { } } - pub async fn get_all( + pub async fn get_all( &self, filter: &T::Filter, ) -> ApiResult, Infallible> @@ -39,7 +36,7 @@ impl ApiClient { Ok(self.get_query(url, filter).await?.json().await?) } - pub async fn get_one(&self, id: Id) -> ApiResult { + pub async fn get_one(&self, id: Id) -> ApiResult { let url = format!("{}/{}/{}", self.url, T::PATH, id); Ok(self.get(url).await?.json().await?) } diff --git a/api/src/get.rs b/api/src/get.rs deleted file mode 100644 index 599999b..0000000 --- a/api/src/get.rs +++ /dev/null @@ -1,59 +0,0 @@ -use clusterizer_common::records::{ - Assignment, AssignmentFilter, File, FileFilter, Platform, PlatformFilter, Project, - ProjectFilter, ProjectVersion, ProjectVersionFilter, Result, ResultFilter, Task, TaskFilter, - User, UserFilter, -}; - -pub trait Get { - type Filter; - - const PATH: &str; -} - -impl Get for User { - type Filter = UserFilter; - - const PATH: &str = "users"; -} - -impl Get for Project { - type Filter = ProjectFilter; - - const PATH: &str = "projects"; -} - -impl Get for Platform { - type Filter = PlatformFilter; - - const PATH: &str = "platforms"; -} - -impl Get for ProjectVersion { - type Filter = ProjectVersionFilter; - - const PATH: &str = "project_versions"; -} - -impl Get for File { - type Filter = FileFilter; - - const PATH: &str = "files"; -} - -impl Get for Task { - type Filter = TaskFilter; - - const PATH: &str = "tasks"; -} - -impl Get for Assignment { - type Filter = AssignmentFilter; - - const PATH: &str = "assignments"; -} - -impl Get for Result { - type Filter = ResultFilter; - - const PATH: &str = "results"; -} diff --git a/api/src/lib.rs b/api/src/lib.rs index 3b64fa4..b1f7a21 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,3 +1,2 @@ pub mod client; -mod get; pub mod result; diff --git a/cli/src/client.rs b/cli/src/client.rs index f422aaf..4cbadbb 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -157,7 +157,7 @@ impl ClusterizerClient { } info!("No tasks found. Sleeping before attempting again."); - time::sleep(Duration::from_millis(15000)).await; + time::sleep(Duration::from_secs(15)).await; }; for TaskInfo { file, .. } in &tasks { diff --git a/common/src/records/assignment.rs b/common/src/records/assignment.rs index b96d617..08facb9 100644 --- a/common/src/records/assignment.rs +++ b/common/src/records/assignment.rs @@ -1,41 +1,29 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::{AssignmentState, Id}; +use crate::{ + records::{Task, User, record_impl}, + types::{AssignmentState, Id}, +}; -use super::{Task, User}; +record_impl! { + PATH = "assignments"; -#[derive(Clone, Hash, Debug, Serialize, Deserialize)] -pub struct Assignment { - pub id: Id, - pub created_at: DateTime, - pub deadline_at: DateTime, - pub task_id: Id, - pub user_id: Id, - pub state: AssignmentState, -} - -#[non_exhaustive] -#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct AssignmentFilter { - pub task_id: Option>, - pub user_id: Option>, - pub state: Option, -} - -impl AssignmentFilter { - pub fn task_id(mut self, task_id: Id) -> Self { - self.task_id = Some(task_id); - self - } - - pub fn user_id(mut self, user_id: Id) -> Self { - self.user_id = Some(user_id); - self + Assignment { + id: Id, + created_at: DateTime, + deadline_at: DateTime, + task_id: Id, + user_id: Id, + state: AssignmentState, } - pub fn state(mut self, state: AssignmentState) -> Self { - self.state = Some(state); - self + AssignmentFilter { + "task_id = $1 IS NOT FALSE" + task_id: Id, + "user_id = $2 IS NOT FALSE" + user_id: Id, + "state = $3 IS NOT FALSE" + state: AssignmentState, } } diff --git a/common/src/records/file.rs b/common/src/records/file.rs index 1efe33f..c743847 100644 --- a/common/src/records/file.rs +++ b/common/src/records/file.rs @@ -1,16 +1,17 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::Id; +use crate::{records::record_impl, types::Id}; -#[derive(Clone, Hash, Debug, Serialize, Deserialize)] -pub struct File { - pub id: Id, - pub created_at: DateTime, - pub url: String, - pub hash: Vec, -} +record_impl! { + PATH = "files"; + + File { + id: Id, + created_at: DateTime, + url: String, + hash: Vec, + } -#[non_exhaustive] -#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct FileFilter {} + FileFilter {} +} diff --git a/common/src/records/mod.rs b/common/src/records/mod.rs index d16c5f3..0daa525 100644 --- a/common/src/records/mod.rs +++ b/common/src/records/mod.rs @@ -15,3 +15,87 @@ pub use project_version::{ProjectVersion, ProjectVersionFilter}; pub use result::{Result, ResultFilter}; pub use task::{Task, TaskFilter}; pub use user::{User, UserFilter}; + +use crate::types::Id; + +#[cfg(feature = "sqlx")] +mod sqlx { + use sqlx::{ + Postgres, + postgres::{PgArguments, PgRow}, + }; + + pub type Map = + sqlx::query::Map<'static, Postgres, fn(PgRow) -> sqlx::Result, PgArguments>; +} + +pub trait Record: Sized { + type Filter; + + const PATH: &str; + + #[cfg(feature = "sqlx")] + fn select_all(filter: &Self::Filter) -> sqlx::Map; + + #[cfg(feature = "sqlx")] + fn select_one(id: Id) -> sqlx::Map; +} + +macro_rules! record_impl { + ( + PATH = $path_literal:literal; + + $record_ident:ident { + $($record_field_ident:ident: $record_field_ty:ty,)* + } + + $filter_ident:ident { + $( + $filter_condition_literal:literal + $filter_field_ident:ident: $filter_field_ty:ty, + )* + } + ) => { + #[derive(Clone, Hash, Debug, Serialize, Deserialize)] + pub struct $record_ident { + $(pub $record_field_ident: $record_field_ty,)* + } + + #[non_exhaustive] + #[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] + pub struct $filter_ident { + $(pub $filter_field_ident: Option<$filter_field_ty>,)* + } + + impl $filter_ident { + $( + pub fn $filter_field_ident(mut self, $filter_field_ident: impl Into>) -> Self { + self.$filter_field_ident = $filter_field_ident.into(); + self + } + )* + } + + impl $crate::records::Record for $record_ident { + type Filter = $filter_ident; + + const PATH: &str = $path_literal; + + #[cfg(feature = "sqlx")] + fn select_all(#[allow(unused_variables)] filter: &Self::Filter) -> $crate::records::sqlx::Map { + sqlx::query_as_unchecked!( + Self, + "SELECT * FROM " + $path_literal + " WHERE TRUE" $(+ " AND (" + $filter_condition_literal + ")")*, + $(filter.$filter_field_ident,)* + ) + } + + #[cfg(feature = "sqlx")] + fn select_one(id: $crate::types::Id) -> $crate::records::sqlx::Map { + sqlx::query_as_unchecked!(Self, "SELECT * FROM " + $path_literal + " WHERE id = $1", id) + } + } + }; +} + +use record_impl; diff --git a/common/src/records/platform.rs b/common/src/records/platform.rs index 81e1716..fa46e5a 100644 --- a/common/src/records/platform.rs +++ b/common/src/records/platform.rs @@ -1,25 +1,23 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::{records::File, types::Id}; +use crate::{ + records::{File, record_impl}, + types::Id, +}; -#[derive(Clone, Hash, Debug, Serialize, Deserialize)] -pub struct Platform { - pub id: Id, - pub created_at: DateTime, - pub name: String, - pub file_id: Id, -} +record_impl! { + PATH = "platforms"; -#[non_exhaustive] -#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct PlatformFilter { - pub file_id: Option>, -} + Platform { + id: Id, + created_at: DateTime, + name: String, + file_id: Id, + } -impl PlatformFilter { - pub fn file_id(mut self, file_id: Id) -> Self { - self.file_id = Some(file_id); - self + PlatformFilter { + "file_id = $1 IS NOT FALSE" + file_id: Id, } } diff --git a/common/src/records/project.rs b/common/src/records/project.rs index 8b6426a..a9eb5df 100644 --- a/common/src/records/project.rs +++ b/common/src/records/project.rs @@ -1,32 +1,26 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::{records::User, types::Id}; +use crate::{ + records::{User, record_impl}, + types::Id, +}; -#[derive(Clone, Hash, Debug, Serialize, Deserialize)] -pub struct Project { - pub id: Id, - pub created_at: DateTime, - pub created_by_user_id: Id, - pub disabled_at: Option>, - pub name: String, -} - -#[non_exhaustive] -#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct ProjectFilter { - pub created_by_user_id: Option>, - pub disabled: Option, -} +record_impl! { + PATH = "projects"; -impl ProjectFilter { - pub fn created_by_user_id(mut self, created_by_user_id: Id) -> Self { - self.created_by_user_id = Some(created_by_user_id); - self + Project { + id: Id, + created_at: DateTime, + created_by_user_id: Id, + disabled_at: Option>, + name: String, } - pub fn disabled(mut self, disabled: bool) -> Self { - self.disabled = Some(disabled); - self + ProjectFilter { + "created_by_user_id = $1 IS NOT FALSE" + created_by_user_id: Id, + "disabled_at IS NULL IS DISTINCT FROM $2" + disabled: bool, } } diff --git a/common/src/records/project_version.rs b/common/src/records/project_version.rs index e724e8a..7770b29 100644 --- a/common/src/records/project_version.rs +++ b/common/src/records/project_version.rs @@ -1,46 +1,31 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::Id; +use crate::{ + records::{File, Platform, Project, record_impl}, + types::Id, +}; -use super::{File, Platform, Project}; +record_impl! { + PATH = "project_versions"; -#[derive(Clone, Hash, Debug, Serialize, Deserialize)] -pub struct ProjectVersion { - pub id: Id, - pub created_at: DateTime, - pub disabled_at: Option>, - pub project_id: Id, - pub platform_id: Id, - pub file_id: Id, -} - -#[non_exhaustive] -#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct ProjectVersionFilter { - pub disabled: Option, - pub project_id: Option>, - pub platform_id: Option>, - pub file_id: Option>, -} - -impl ProjectVersionFilter { - pub fn disabled(mut self, disabled: bool) -> Self { - self.disabled = Some(disabled); - self + ProjectVersion { + id: Id, + created_at: DateTime, + disabled_at: Option>, + project_id: Id, + platform_id: Id, + file_id: Id, } - pub fn project_id(mut self, project_id: Id) -> Self { - self.project_id = Some(project_id); - self - } - - pub fn platform_id(mut self, platform_id: Id) -> Self { - self.platform_id = Some(platform_id); - self - } - pub fn file_id(mut self, file_id: Id) -> Self { - self.file_id = Some(file_id); - self + ProjectVersionFilter { + "disabled_at IS NULL IS DISTINCT FROM $1" + disabled: bool, + "project_id = $2 IS NOT FALSE" + project_id: Id, + "platform_id = $3 IS NOT FALSE" + platform_id: Id, + "file_id = $4 IS NOT FALSE" + file_id: Id, } } diff --git a/common/src/records/result.rs b/common/src/records/result.rs index 343ee43..7ac9ca5 100644 --- a/common/src/records/result.rs +++ b/common/src/records/result.rs @@ -1,43 +1,31 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::{Id, ResultState}; +use crate::{ + records::{Assignment, record_impl}, + types::{Id, ResultState}, +}; -use super::Assignment; +record_impl! { + PATH = "results"; -#[derive(Clone, Hash, Debug, Serialize, Deserialize)] -pub struct Result { - pub id: Id, - pub created_at: DateTime, - pub assignment_id: Id, - pub stdout: String, - pub stderr: String, - pub exit_code: Option, - pub group_result_id: Option>, - pub state: ResultState, -} - -#[non_exhaustive] -#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct ResultFilter { - pub assignment_id: Option>, - pub group_result_id: Option>, - pub state: Option, -} - -impl ResultFilter { - pub fn assignment_id(mut self, assignment_id: Id) -> Self { - self.assignment_id = Some(assignment_id); - self - } - - pub fn group_result_id(mut self, group_result_id: Id) -> Self { - self.group_result_id = Some(group_result_id); - self + Result { + id: Id, + created_at: DateTime, + assignment_id: Id, + stdout: String, + stderr: String, + exit_code: Option, + group_result_id: Option>, + state: ResultState, } - pub fn state(mut self, state: ResultState) -> Self { - self.state = Some(state); - self + ResultFilter { + "assignment_id = $1 IS NOT FALSE" + assignment_id: Id, + "group_result_id = $2 OR $2 IS NULL" + group_result_id: Id, + "state = $3 IS NOT FALSE" + state: ResultState, } } diff --git a/common/src/records/task.rs b/common/src/records/task.rs index 4983e6b..d7d6bd0 100644 --- a/common/src/records/task.rs +++ b/common/src/records/task.rs @@ -1,31 +1,27 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::{Id, Interval}; +use crate::{ + records::{Project, User, record_impl}, + types::{Id, Interval}, +}; -use super::{Project, User}; +record_impl! { + PATH = "tasks"; -#[derive(Clone, Hash, Debug, Serialize, Deserialize)] -pub struct Task { - pub id: Id, - pub created_at: DateTime, - pub deadline: Interval, - pub project_id: Id, - pub stdin: String, - pub assignments_needed: i32, - pub assignment_user_ids: Vec>, - pub quorum: i32, -} - -#[non_exhaustive] -#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct TaskFilter { - pub project_id: Option>, -} + Task { + id: Id, + created_at: DateTime, + deadline: Interval, + project_id: Id, + stdin: String, + assignments_needed: i32, + assignment_user_ids: Vec>, + quorum: i32, + } -impl TaskFilter { - pub fn project_id(mut self, project_id: Id) -> Self { - self.project_id = Some(project_id); - self + TaskFilter { + "project_id = $1 IS NOT FALSE" + project_id: Id, } } diff --git a/common/src/records/user.rs b/common/src/records/user.rs index 4ed4f04..2acb953 100644 --- a/common/src/records/user.rs +++ b/common/src/records/user.rs @@ -1,25 +1,20 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::Id; +use crate::{records::record_impl, types::Id}; -#[derive(Clone, Hash, Debug, Serialize, Deserialize)] -pub struct User { - pub id: Id, - pub created_at: DateTime, - pub disabled_at: Option>, - pub name: String, -} +record_impl! { + PATH = "users"; -#[non_exhaustive] -#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct UserFilter { - pub disabled: Option, -} + User { + id: Id, + created_at: DateTime, + disabled_at: Option>, + name: String, + } -impl UserFilter { - pub fn disabled(mut self, disabled: bool) -> Self { - self.disabled = Some(disabled); - self + UserFilter { + "disabled_at IS NULL IS DISTINCT FROM $1" + disabled: bool, } } diff --git a/server/example.env b/example.env similarity index 100% rename from server/example.env rename to example.env diff --git a/server/.sqlx/query-71c855f936697605ccfd2869c81843f1842eee48fbadd80550e982f0dd308a79.json b/server/.sqlx/query-71c855f936697605ccfd2869c81843f1842eee48fbadd80550e982f0dd308a79.json deleted file mode 100644 index 2694a48..0000000 --- a/server/.sqlx/query-71c855f936697605ccfd2869c81843f1842eee48fbadd80550e982f0dd308a79.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE\n assignments\n SET\n state = 'expired'\n WHERE\n state = 'init'\n AND deadline_at < now()\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "71c855f936697605ccfd2869c81843f1842eee48fbadd80550e982f0dd308a79" -} diff --git a/server/src/auth.rs b/server/src/auth.rs index 9766048..4586178 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -9,11 +9,14 @@ use axum_extra::{ headers::{Authorization, authorization::Bearer}, }; use base64::prelude::*; -use clusterizer_common::{records::User, types::Id}; +use clusterizer_common::{ + records::{Record, User}, + types::Id, +}; use hmac::{Hmac, KeyInit, Mac}; use sha2::Sha256; -use crate::{state::AppState, util::Select}; +use crate::state::AppState; pub struct Auth(pub Id); diff --git a/server/src/main.rs b/server/src/main.rs index fd285bc..d913366 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -2,22 +2,20 @@ mod auth; mod result; mod routes; mod state; -mod util; - -use std::time::Duration; +mod tasks; use axum::{ Router, routing::{get, post}, }; use clusterizer_common::records::{ - Assignment, File, Platform, Project, ProjectVersion, Result, Task, User, + Assignment, File, Platform, Project, ProjectVersion, Record, Result, Task, User, }; -use routes::{get_all, get_one}; +use serde::{Serialize, de::DeserializeOwned}; use sqlx::PgPool; use state::AppState; -use tokio::{net::TcpListener, time}; +use tokio::net::TcpListener; use tower_http::trace::TraceLayer; #[tokio::main] @@ -35,28 +33,20 @@ async fn main() { tokio::join!( serve_task(state.clone(), address), - update_expired_assignments_task(state.clone()), + tasks::update_expired_assignments(state.clone()), ); } async fn serve_task(state: AppState, address: String) { let app = Router::new() - .route("/files", get(get_all::)) - .route("/files/{id}", get(get_one::)) - .route("/users", get(get_all::)) - .route("/users/{id}", get(get_one::)) - .route("/projects", get(get_all::)) - .route("/projects/{id}", get(get_one::)) - .route("/platforms", get(get_all::)) - .route("/platforms/{id}", get(get_one::)) - .route("/project_versions", get(get_all::)) - .route("/project_versions/{id}", get(get_one::)) - .route("/tasks", get(get_all::)) - .route("/tasks/{id}", get(get_one::)) - .route("/assignments", get(get_all::)) - .route("/assignments/{id}", get(get_one::)) - .route("/results", get(get_all::)) - .route("/results/{id}", get(get_one::)) + .merge(record_router::()) + .merge(record_router::()) + .merge(record_router::()) + .merge(record_router::()) + .merge(record_router::()) + .merge(record_router::()) + .merge(record_router::()) + .merge(record_router::()) .route("/register", post(routes::register)) .route("/fetch_tasks", post(routes::fetch_tasks)) .route("/submit_result/{id}", post(routes::submit_result)) @@ -70,11 +60,12 @@ async fn serve_task(state: AppState, address: String) { axum::serve(listener, app).await.unwrap() } -async fn update_expired_assignments_task(state: AppState) { - let mut interval = time::interval(Duration::from_secs(60 * 15)); - - loop { - interval.tick().await; - util::update_expired_assignments(&state).await.unwrap(); - } +fn record_router() -> Router +where + T: Record + Send + Unpin + Serialize + 'static, + T::Filter: Send + DeserializeOwned, +{ + Router::new() + .route(&format!("/{}", T::PATH), get(routes::get_all::)) + .route(&format!("/{}/{{id}}", T::PATH), get(routes::get_one::)) } diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 2bdf9e2..0fab9ad 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -4,13 +4,13 @@ use axum::{ }; use clusterizer_common::{ errors::{Infallible, NotFound}, + records::Record, types::Id, }; use crate::{ result::{AppResult, ResultExt}, state::AppState, - util::Select, }; pub mod fetch_tasks; @@ -25,14 +25,14 @@ pub use submit_result::submit_result; pub use validate_fetch::validate_fetch; pub use validate_submit::validate_submit; -pub async fn get_all( +pub async fn get_all( State(state): State, Query(filter): Query, ) -> AppResult>, Infallible> { Ok(Json(T::select_all(&filter).fetch_all(&state.pool).await?)) } -pub async fn get_one( +pub async fn get_one( State(state): State, Path(id): Path>, ) -> AppResult, NotFound> { diff --git a/server/src/routes/validate_submit.rs b/server/src/routes/validate_submit.rs index fc3a503..43ffc0f 100644 --- a/server/src/routes/validate_submit.rs +++ b/server/src/routes/validate_submit.rs @@ -5,6 +5,7 @@ use clusterizer_common::{ requests::ValidateSubmitRequest, types::{Id, ResultState}, }; +use sqlx::{Postgres, postgres::PgArguments, query::Query}; use std::collections::HashMap; @@ -12,7 +13,6 @@ use crate::{ auth::Auth, result::{AppError, AppResult}, state::AppState, - util::set_result_state, }; pub async fn validate_submit( @@ -247,3 +247,21 @@ pub async fn validate_submit( Ok(()) } + +fn set_result_state( + result_ids: &[Id], + result_state: ResultState, +) -> Query<'static, Postgres, PgArguments> { + sqlx::query_unchecked!( + r#" + UPDATE + results + SET + state = $1 + WHERE + id = ANY($2) + "#, + result_state, + result_ids, + ) +} diff --git a/server/src/tasks/mod.rs b/server/src/tasks/mod.rs new file mode 100644 index 0000000..a67dffc --- /dev/null +++ b/server/src/tasks/mod.rs @@ -0,0 +1,3 @@ +pub mod update_expired_assignments; + +pub use update_expired_assignments::update_expired_assignments; diff --git a/server/src/tasks/update_expired_assignments.rs b/server/src/tasks/update_expired_assignments.rs new file mode 100644 index 0000000..aa6046a --- /dev/null +++ b/server/src/tasks/update_expired_assignments.rs @@ -0,0 +1,28 @@ +use std::time::Duration; + +use tokio::time; + +use crate::state::AppState; + +pub async fn update_expired_assignments(state: AppState) { + let mut interval = time::interval(Duration::from_secs(60)); + + loop { + interval.tick().await; + + sqlx::query_unchecked!( + r#" + UPDATE + assignments + SET + state = 'expired' + WHERE + state = 'init' + AND deadline_at < now() + "# + ) + .execute(&state.pool) + .await + .unwrap(); + } +} diff --git a/server/src/util/assignment_deadline.rs b/server/src/util/assignment_deadline.rs deleted file mode 100644 index 3db3538..0000000 --- a/server/src/util/assignment_deadline.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::state::AppState; - -pub async fn update_expired_assignments(state: &AppState) -> sqlx::Result<()> { - sqlx::query_unchecked!( - r#" - UPDATE - assignments - SET - state = 'expired' - WHERE - state = 'init' - AND deadline_at < now() - "# - ) - .execute(&state.pool) - .await?; - - Ok(()) -} diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs deleted file mode 100644 index 6c38313..0000000 --- a/server/src/util/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use sqlx::{ - Postgres, - postgres::{PgArguments, PgRow}, -}; - -pub mod assignment_deadline; -pub mod select; -// pub mod set_assignment_state; -pub mod set_result_state; - -pub use assignment_deadline::update_expired_assignments; -pub use select::Select; -// pub use set_assignment_state::set_assignment_state; -pub use set_result_state::set_result_state; - -type Query = sqlx::query::Query<'static, Postgres, PgArguments>; -// type QueryScalar = sqlx::query::QueryScalar<'static, Postgres, T, PgArguments>; -type Map = sqlx::query::Map<'static, Postgres, fn(PgRow) -> sqlx::Result, PgArguments>; diff --git a/server/src/util/select.rs b/server/src/util/select.rs deleted file mode 100644 index 5af08c0..0000000 --- a/server/src/util/select.rs +++ /dev/null @@ -1,214 +0,0 @@ -use clusterizer_common::{ - records::{ - Assignment, AssignmentFilter, File, FileFilter, Platform, PlatformFilter, Project, - ProjectFilter, ProjectVersion, ProjectVersionFilter, Result, ResultFilter, Task, - TaskFilter, User, UserFilter, - }, - types::Id, -}; - -use super::Map; - -pub trait Select: Sized { - type Filter; - - fn select_all(filter: &Self::Filter) -> Map; - fn select_one(id: Id) -> Map; -} - -impl Select for User { - type Filter = UserFilter; - - fn select_all(filter: &Self::Filter) -> Map { - sqlx::query_as_unchecked!( - Self, - r#" - SELECT - * - FROM - users - WHERE - disabled_at IS NULL IS DISTINCT FROM $1 - "#, - filter.disabled, - ) - } - - fn select_one(id: Id) -> Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM users WHERE id = $1", id) - } -} - -impl Select for Project { - type Filter = ProjectFilter; - - fn select_all(filter: &Self::Filter) -> Map { - sqlx::query_as_unchecked!( - Self, - r#" - SELECT - * - FROM - projects - WHERE - created_by_user_id = $1 IS NOT FALSE - AND disabled_at IS NULL IS DISTINCT FROM $2 - "#, - filter.created_by_user_id, - filter.disabled, - ) - } - - fn select_one(id: Id) -> Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM projects WHERE id = $1", id) - } -} - -impl Select for Platform { - type Filter = PlatformFilter; - - fn select_all(filter: &Self::Filter) -> Map { - sqlx::query_as_unchecked!( - Self, - r#" - SELECT - * - FROM - platforms - WHERE - file_id = $1 IS NOT FALSE - "#, - filter.file_id, - ) - } - - fn select_one(id: Id) -> Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM platforms WHERE id = $1", id) - } -} - -impl Select for ProjectVersion { - type Filter = ProjectVersionFilter; - - fn select_all(filter: &Self::Filter) -> Map { - sqlx::query_as_unchecked!( - Self, - r#" - SELECT - * - FROM - project_versions - WHERE - disabled_at IS NULL IS DISTINCT FROM $1 - AND project_id = $2 IS NOT FALSE - AND platform_id = $3 IS NOT FALSE - AND file_id = $4 IS NOT FALSE - "#, - filter.disabled, - filter.project_id, - filter.platform_id, - filter.file_id, - ) - } - - fn select_one(id: Id) -> Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM project_versions WHERE id = $1", id) - } -} - -impl Select for File { - type Filter = FileFilter; - - fn select_all(_: &Self::Filter) -> Map { - sqlx::query_as_unchecked!( - Self, - r#" - SELECT - * - FROM - files - "#, - ) - } - - fn select_one(id: Id) -> Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM files WHERE id = $1", id) - } -} - -impl Select for Task { - type Filter = TaskFilter; - - fn select_all(filter: &Self::Filter) -> Map { - sqlx::query_as_unchecked!( - Self, - r#" - SELECT - * - FROM - tasks - WHERE - project_id = $1 IS NOT FALSE - "#, - filter.project_id, - ) - } - - fn select_one(id: Id) -> Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM tasks WHERE id = $1", id) - } -} - -impl Select for Assignment { - type Filter = AssignmentFilter; - - fn select_all(filter: &Self::Filter) -> Map { - sqlx::query_as_unchecked!( - Self, - r#" - SELECT - * - FROM - assignments - WHERE - task_id = $1 IS NOT FALSE - AND user_id = $2 IS NOT FALSE - AND state = $3 IS NOT FALSE - "#, - filter.task_id, - filter.user_id, - filter.state, - ) - } - - fn select_one(id: Id) -> Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM assignments WHERE id = $1", id) - } -} - -impl Select for Result { - type Filter = ResultFilter; - - fn select_all(filter: &Self::Filter) -> Map { - sqlx::query_as_unchecked!( - Self, - r#" - SELECT - * - FROM - results - WHERE - assignment_id = $1 IS NOT FALSE - AND (group_result_id = $2 OR $2 IS NULL) - AND state = $3 IS NOT FALSE - "#, - filter.assignment_id, - filter.group_result_id, - filter.state, - ) - } - - fn select_one(id: Id) -> Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM results WHERE id = $1", id) - } -} diff --git a/server/src/util/set_assignment_state.rs b/server/src/util/set_assignment_state.rs deleted file mode 100644 index 54d891c..0000000 --- a/server/src/util/set_assignment_state.rs +++ /dev/null @@ -1,24 +0,0 @@ -use clusterizer_common::{ - records::Assignment, - types::{AssignmentState, Id}, -}; - -use super::Query; - -pub fn set_assignment_state( - assignment_ids: &[Id], - assignment_state: AssignmentState, -) -> Query { - sqlx::query_unchecked!( - r#" - UPDATE - assignments - SET - state = $1 - WHERE - id = ANY($2) - "#, - assignment_state, - assignment_ids, - ) -} diff --git a/server/src/util/set_result_state.rs b/server/src/util/set_result_state.rs deleted file mode 100644 index 75cdf49..0000000 --- a/server/src/util/set_result_state.rs +++ /dev/null @@ -1,21 +0,0 @@ -use clusterizer_common::{ - records::Result, - types::{Id, ResultState}, -}; - -use super::Query; - -pub fn set_result_state(result_ids: &[Id], result_state: ResultState) -> Query { - sqlx::query_unchecked!( - r#" - UPDATE - results - SET - state = $1 - WHERE - id = ANY($2) - "#, - result_state, - result_ids, - ) -} From 11f07564a083425c890103f34a47a38073afe034 Mon Sep 17 00:00:00 2001 From: Chen Steenvoorden Date: Sat, 4 Apr 2026 02:47:16 +0200 Subject: [PATCH 2/7] Record cleanup (2/?) --- ...6eb77bf7f6bca01fd9fba96c211dbc042853.json} | 8 +- ...741f9f0383a41f69f6b75d909d7bd3141664f.json | 15 ++ ...d1cd5021697fe5fee486a311d26195f35cce1.json | 23 +++ ...30c6482e68b4208de725d63cd6133e9e8b62e.json | 15 ++ ...0f6564320611495cae59db3a23036792e5b07.json | 16 -- ...0e0f127ea1e4abaee729ae374133c64826f8e.json | 28 ++++ ...1e8c4fadebc4a10f66ad0682469cacab60ff5.json | 52 +++++++ ...96458730b33190a8756dd3ad69b204d57efa7.json | 23 +++ ...ea69056ea4087c66501a5125be006d20928f.json} | 4 +- ...4bed55142f2e50d5dc47858529c77cda6079d.json | 25 +++ ...0ebb042fb163aa3b1de13e1749687f70e4be8.json | 24 +++ ...e176e5e763b21197c2b78e36a319f6d4d660e.json | 15 ++ ...68774d5c9484d6cdce61f6039b7fcb35cca4c.json | 17 --- ...9fdc174c51f82e596d5e6f69d7ec022a79c8.json} | 4 +- ...099f25db2fc1133858e6a9a0eb1ab993391e5.json | 15 ++ ...f02346ba4e5f6790e054973338314eda03f7b.json | 40 +++++ ...40679b2df3bdedb0268777db8f6a33dc6b2bb.json | 27 ++++ ...011c8820191f58c3b3727c9add6cc1dac0769.json | 64 ++++++++ ...b78dde00bc3d82221c1964e8ef831f3ef53b9.json | 77 ++++++++++ ...a7b2ac73fd31784a2323980025211ac8abfba.json | 64 ++++++++ ...fb723996ca83a11a29487a9b1c3a641f64473.json | 23 +++ ...dad562d8eecde82db258dc183868b211f4410.json | 15 -- ...631cc74373fcb3fd07816b38db2d8e08f6595.json | 27 ++++ ...489e756927d45b7963842215098d51bc3d0b.json} | 14 +- ...90ea823a60766527747e2c1f69a907b725e29.json | 23 +++ ...2e6fd3fabcc164935906cd974dabd677ec14f.json | 15 -- ...10bde1590afe8b1d427bc2d0f313c091d4627.json | 40 +++++ ...5e7f9d23b62b564ffde3f97342d3abb0fa7a1.json | 25 +++ common/src/records/assignment.rs | 11 ++ common/src/records/file.rs | 9 ++ common/src/records/mod.rs | 142 +++++++++++++++--- common/src/records/platform.rs | 9 ++ common/src/records/project.rs | 9 ++ common/src/records/project_version.rs | 11 ++ common/src/records/result.rs | 16 ++ common/src/records/task.rs | 15 ++ common/src/records/user.rs | 7 + server/migrations/20250426220809_init.sql | 44 ++++-- server/src/auth.rs | 7 +- server/src/main.rs | 10 +- server/src/routes/fetch_tasks.rs | 38 +---- server/src/routes/mod.rs | 16 +- server/src/routes/register.rs | 24 ++- server/src/routes/submit_result.rs | 30 ++-- server/src/routes/validate_fetch.rs | 22 +-- server/src/routes/validate_submit.rs | 78 ++-------- 46 files changed, 960 insertions(+), 276 deletions(-) rename .sqlx/{query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json => query-0c6016102ff4415efaaaea99a3f96eb77bf7f6bca01fd9fba96c211dbc042853.json} (63%) create mode 100644 .sqlx/query-0e1c419a5fa20618056ae7f7826741f9f0383a41f69f6b75d909d7bd3141664f.json create mode 100644 .sqlx/query-1a807533c739a2c943fcf0734bfd1cd5021697fe5fee486a311d26195f35cce1.json create mode 100644 .sqlx/query-1c4e52d997bb5605c374d0df70e30c6482e68b4208de725d63cd6133e9e8b62e.json delete mode 100644 .sqlx/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json create mode 100644 .sqlx/query-3020ffbe621685782ef71b231fe0e0f127ea1e4abaee729ae374133c64826f8e.json create mode 100644 .sqlx/query-40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5.json create mode 100644 .sqlx/query-4acbc34ffcfde7d19aa73c9c52b96458730b33190a8756dd3ad69b204d57efa7.json rename .sqlx/{query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json => query-4f5c09217b0c57883c611b888878ea69056ea4087c66501a5125be006d20928f.json} (77%) create mode 100644 .sqlx/query-53063f1034cb4c607ccfe321f134bed55142f2e50d5dc47858529c77cda6079d.json create mode 100644 .sqlx/query-59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8.json create mode 100644 .sqlx/query-5d25f8e70c6d71d49b9c36b12f5e176e5e763b21197c2b78e36a319f6d4d660e.json delete mode 100644 .sqlx/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json rename .sqlx/{query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json => query-76fb57cc292fea3d7244fa4e9a9c9fdc174c51f82e596d5e6f69d7ec022a79c8.json} (54%) create mode 100644 .sqlx/query-93255025cebd6426e06df0523d6099f25db2fc1133858e6a9a0eb1ab993391e5.json create mode 100644 .sqlx/query-9436da6c22ec3097de6000c2d5ff02346ba4e5f6790e054973338314eda03f7b.json create mode 100644 .sqlx/query-a53275a0415cd62e58f860b0d1e40679b2df3bdedb0268777db8f6a33dc6b2bb.json create mode 100644 .sqlx/query-a710a747947f4bf764b23b95e58011c8820191f58c3b3727c9add6cc1dac0769.json create mode 100644 .sqlx/query-af3cf413df39675f434e4fc57a7b78dde00bc3d82221c1964e8ef831f3ef53b9.json create mode 100644 .sqlx/query-c6e49c93d592c0d1864a653999ea7b2ac73fd31784a2323980025211ac8abfba.json create mode 100644 .sqlx/query-d99cad4526c095ff8076f7d728efb723996ca83a11a29487a9b1c3a641f64473.json delete mode 100644 .sqlx/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json create mode 100644 .sqlx/query-e032e44c9f2ae32f3f7106f88e4631cc74373fcb3fd07816b38db2d8e08f6595.json rename .sqlx/{query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json => query-e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b.json} (63%) create mode 100644 .sqlx/query-e66f8574a4feabd2d3563fbbea190ea823a60766527747e2c1f69a907b725e29.json delete mode 100644 .sqlx/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json create mode 100644 .sqlx/query-f2f8f268d019f662f8969127db610bde1590afe8b1d427bc2d0f313c091d4627.json create mode 100644 .sqlx/query-fefa7397e448c03b9ff39bf6bed5e7f9d23b62b564ffde3f97342d3abb0fa7a1.json diff --git a/.sqlx/query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json b/.sqlx/query-0c6016102ff4415efaaaea99a3f96eb77bf7f6bca01fd9fba96c211dbc042853.json similarity index 63% rename from .sqlx/query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json rename to .sqlx/query-0c6016102ff4415efaaaea99a3f96eb77bf7f6bca01fd9fba96c211dbc042853.json index f98d975..1cfd733 100644 --- a/.sqlx/query-bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1.json +++ b/.sqlx/query-0c6016102ff4415efaaaea99a3f96eb77bf7f6bca01fd9fba96c211dbc042853.json @@ -1,10 +1,11 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE \n results\n SET \n state = $1\n WHERE\n id = ANY($2)\n ", + "query": "UPDATE results SET state = $2 WHERE id = $1", "describe": { "columns": [], "parameters": { "Left": [ + "Int8", { "Custom": { "name": "result_state", @@ -18,11 +19,10 @@ ] } } - }, - "Int8Array" + } ] }, "nullable": [] }, - "hash": "bf6f3f4b26b32f1f446cf0fd67c462cd94a548cd83fbc39fb826ced7c0bce9f1" + "hash": "0c6016102ff4415efaaaea99a3f96eb77bf7f6bca01fd9fba96c211dbc042853" } diff --git a/.sqlx/query-0e1c419a5fa20618056ae7f7826741f9f0383a41f69f6b75d909d7bd3141664f.json b/.sqlx/query-0e1c419a5fa20618056ae7f7826741f9f0383a41f69f6b75d909d7bd3141664f.json new file mode 100644 index 0000000..48f4b45 --- /dev/null +++ b/.sqlx/query-0e1c419a5fa20618056ae7f7826741f9f0383a41f69f6b75d909d7bd3141664f.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE tasks SET assignments_needed = $2 WHERE id = ANY($1)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8Array", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "0e1c419a5fa20618056ae7f7826741f9f0383a41f69f6b75d909d7bd3141664f" +} diff --git a/.sqlx/query-1a807533c739a2c943fcf0734bfd1cd5021697fe5fee486a311d26195f35cce1.json b/.sqlx/query-1a807533c739a2c943fcf0734bfd1cd5021697fe5fee486a311d26195f35cce1.json new file mode 100644 index 0000000..4b2442c --- /dev/null +++ b/.sqlx/query-1a807533c739a2c943fcf0734bfd1cd5021697fe5fee486a311d26195f35cce1.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO platforms (name, file_id) VALUES ($1, $2) RETURNING id \"id: _\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: _", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "1a807533c739a2c943fcf0734bfd1cd5021697fe5fee486a311d26195f35cce1" +} diff --git a/.sqlx/query-1c4e52d997bb5605c374d0df70e30c6482e68b4208de725d63cd6133e9e8b62e.json b/.sqlx/query-1c4e52d997bb5605c374d0df70e30c6482e68b4208de725d63cd6133e9e8b62e.json new file mode 100644 index 0000000..46cfe2f --- /dev/null +++ b/.sqlx/query-1c4e52d997bb5605c374d0df70e30c6482e68b4208de725d63cd6133e9e8b62e.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE results SET group_result_id = $2 WHERE id = ANY($1)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8Array", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "1c4e52d997bb5605c374d0df70e30c6482e68b4208de725d63cd6133e9e8b62e" +} diff --git a/.sqlx/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json b/.sqlx/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json deleted file mode 100644 index 347f057..0000000 --- a/.sqlx/query-1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO assignments (\n task_id,\n user_id,\n deadline_at\n ) VALUES (\n $1,\n $2,\n now() + $3\n )\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Interval" - ] - }, - "nullable": [] - }, - "hash": "1dfaaf2d793b2d035cd52abd5290f6564320611495cae59db3a23036792e5b07" -} diff --git a/.sqlx/query-3020ffbe621685782ef71b231fe0e0f127ea1e4abaee729ae374133c64826f8e.json b/.sqlx/query-3020ffbe621685782ef71b231fe0e0f127ea1e4abaee729ae374133c64826f8e.json new file mode 100644 index 0000000..8230f35 --- /dev/null +++ b/.sqlx/query-3020ffbe621685782ef71b231fe0e0f127ea1e4abaee729ae374133c64826f8e.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE results SET state = $2 WHERE id = ANY($1)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8Array", + { + "Custom": { + "name": "result_state", + "kind": { + "Enum": [ + "init", + "valid", + "invalid", + "inconclusive", + "error" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "3020ffbe621685782ef71b231fe0e0f127ea1e4abaee729ae374133c64826f8e" +} diff --git a/.sqlx/query-40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5.json b/.sqlx/query-40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5.json new file mode 100644 index 0000000..a7d616c --- /dev/null +++ b/.sqlx/query-40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5.json @@ -0,0 +1,52 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM project_versions WHERE id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "disabled_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "project_id", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "platform_id", + "type_info": "Int8" + }, + { + "ordinal": 5, + "name": "file_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false + ] + }, + "hash": "40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5" +} diff --git a/.sqlx/query-4acbc34ffcfde7d19aa73c9c52b96458730b33190a8756dd3ad69b204d57efa7.json b/.sqlx/query-4acbc34ffcfde7d19aa73c9c52b96458730b33190a8756dd3ad69b204d57efa7.json new file mode 100644 index 0000000..997d081 --- /dev/null +++ b/.sqlx/query-4acbc34ffcfde7d19aa73c9c52b96458730b33190a8756dd3ad69b204d57efa7.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO files (url, hash) VALUES ($1, $2) RETURNING id \"id: _\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: _", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Bytea" + ] + }, + "nullable": [ + false + ] + }, + "hash": "4acbc34ffcfde7d19aa73c9c52b96458730b33190a8756dd3ad69b204d57efa7" +} diff --git a/.sqlx/query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json b/.sqlx/query-4f5c09217b0c57883c611b888878ea69056ea4087c66501a5125be006d20928f.json similarity index 77% rename from .sqlx/query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json rename to .sqlx/query-4f5c09217b0c57883c611b888878ea69056ea4087c66501a5125be006d20928f.json index 4e13ac0..fa53a93 100644 --- a/.sqlx/query-25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781.json +++ b/.sqlx/query-4f5c09217b0c57883c611b888878ea69056ea4087c66501a5125be006d20928f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n projects\n WHERE\n id = ANY($1)\n ", + "query": "SELECT * FROM projects WHERE id = ANY($1)", "describe": { "columns": [ { @@ -42,5 +42,5 @@ false ] }, - "hash": "25acb3a0a968c72a708d842e209a60e17066d80cd0a0c404a4031dae4df01781" + "hash": "4f5c09217b0c57883c611b888878ea69056ea4087c66501a5125be006d20928f" } diff --git a/.sqlx/query-53063f1034cb4c607ccfe321f134bed55142f2e50d5dc47858529c77cda6079d.json b/.sqlx/query-53063f1034cb4c607ccfe321f134bed55142f2e50d5dc47858529c77cda6079d.json new file mode 100644 index 0000000..9855355 --- /dev/null +++ b/.sqlx/query-53063f1034cb4c607ccfe321f134bed55142f2e50d5dc47858529c77cda6079d.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO results (assignment_id, stdout, stderr, exit_code) VALUES ($1, $2, $3, $4) RETURNING id \"id: _\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: _", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Text", + "Text", + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "53063f1034cb4c607ccfe321f134bed55142f2e50d5dc47858529c77cda6079d" +} diff --git a/.sqlx/query-59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8.json b/.sqlx/query-59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8.json new file mode 100644 index 0000000..a76923b --- /dev/null +++ b/.sqlx/query-59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO project_versions (project_id, platform_id, file_id) VALUES ($1, $2, $3) RETURNING id \"id: _\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: _", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8" +} diff --git a/.sqlx/query-5d25f8e70c6d71d49b9c36b12f5e176e5e763b21197c2b78e36a319f6d4d660e.json b/.sqlx/query-5d25f8e70c6d71d49b9c36b12f5e176e5e763b21197c2b78e36a319f6d4d660e.json new file mode 100644 index 0000000..c1258bf --- /dev/null +++ b/.sqlx/query-5d25f8e70c6d71d49b9c36b12f5e176e5e763b21197c2b78e36a319f6d4d660e.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE tasks SET assignments_needed = $2 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "5d25f8e70c6d71d49b9c36b12f5e176e5e763b21197c2b78e36a319f6d4d660e" +} diff --git a/.sqlx/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json b/.sqlx/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json deleted file mode 100644 index 075ea00..0000000 --- a/.sqlx/query-6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO results (\n assignment_id,\n stdout,\n stderr,\n exit_code\n ) VALUES (\n $1,\n $2,\n $3,\n $4\n )\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Text", - "Text", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "6126322672582fb1fd62833803f68774d5c9484d6cdce61f6039b7fcb35cca4c" -} diff --git a/.sqlx/query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json b/.sqlx/query-76fb57cc292fea3d7244fa4e9a9c9fdc174c51f82e596d5e6f69d7ec022a79c8.json similarity index 54% rename from .sqlx/query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json rename to .sqlx/query-76fb57cc292fea3d7244fa4e9a9c9fdc174c51f82e596d5e6f69d7ec022a79c8.json index 0aab76b..2d225f3 100644 --- a/.sqlx/query-d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851.json +++ b/.sqlx/query-76fb57cc292fea3d7244fa4e9a9c9fdc174c51f82e596d5e6f69d7ec022a79c8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO users (\n name\n ) VALUES (\n $1\n )\n RETURNING id \"id: _\"\n ", + "query": "INSERT INTO users (name) VALUES ($1) RETURNING id \"id: _\"", "describe": { "columns": [ { @@ -18,5 +18,5 @@ false ] }, - "hash": "d5c10440f611cbabf0630afc17a21e1264cb90f1da58b9220bce53371eebd851" + "hash": "76fb57cc292fea3d7244fa4e9a9c9fdc174c51f82e596d5e6f69d7ec022a79c8" } diff --git a/.sqlx/query-93255025cebd6426e06df0523d6099f25db2fc1133858e6a9a0eb1ab993391e5.json b/.sqlx/query-93255025cebd6426e06df0523d6099f25db2fc1133858e6a9a0eb1ab993391e5.json new file mode 100644 index 0000000..b496fd7 --- /dev/null +++ b/.sqlx/query-93255025cebd6426e06df0523d6099f25db2fc1133858e6a9a0eb1ab993391e5.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE results SET group_result_id = $2 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "93255025cebd6426e06df0523d6099f25db2fc1133858e6a9a0eb1ab993391e5" +} diff --git a/.sqlx/query-9436da6c22ec3097de6000c2d5ff02346ba4e5f6790e054973338314eda03f7b.json b/.sqlx/query-9436da6c22ec3097de6000c2d5ff02346ba4e5f6790e054973338314eda03f7b.json new file mode 100644 index 0000000..7e99413 --- /dev/null +++ b/.sqlx/query-9436da6c22ec3097de6000c2d5ff02346ba4e5f6790e054973338314eda03f7b.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM platforms WHERE id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "file_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "9436da6c22ec3097de6000c2d5ff02346ba4e5f6790e054973338314eda03f7b" +} diff --git a/.sqlx/query-a53275a0415cd62e58f860b0d1e40679b2df3bdedb0268777db8f6a33dc6b2bb.json b/.sqlx/query-a53275a0415cd62e58f860b0d1e40679b2df3bdedb0268777db8f6a33dc6b2bb.json new file mode 100644 index 0000000..1a4b778 --- /dev/null +++ b/.sqlx/query-a53275a0415cd62e58f860b0d1e40679b2df3bdedb0268777db8f6a33dc6b2bb.json @@ -0,0 +1,27 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE assignments SET state = $2 WHERE id = ANY($1)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8Array", + { + "Custom": { + "name": "assignment_state", + "kind": { + "Enum": [ + "init", + "canceled", + "expired", + "submitted" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "a53275a0415cd62e58f860b0d1e40679b2df3bdedb0268777db8f6a33dc6b2bb" +} diff --git a/.sqlx/query-a710a747947f4bf764b23b95e58011c8820191f58c3b3727c9add6cc1dac0769.json b/.sqlx/query-a710a747947f4bf764b23b95e58011c8820191f58c3b3727c9add6cc1dac0769.json new file mode 100644 index 0000000..271ba67 --- /dev/null +++ b/.sqlx/query-a710a747947f4bf764b23b95e58011c8820191f58c3b3727c9add6cc1dac0769.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM assignments WHERE id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "deadline_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "task_id", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "user_id", + "type_info": "Int8" + }, + { + "ordinal": 5, + "name": "state", + "type_info": { + "Custom": { + "name": "assignment_state", + "kind": { + "Enum": [ + "init", + "canceled", + "expired", + "submitted" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false + ] + }, + "hash": "a710a747947f4bf764b23b95e58011c8820191f58c3b3727c9add6cc1dac0769" +} diff --git a/.sqlx/query-af3cf413df39675f434e4fc57a7b78dde00bc3d82221c1964e8ef831f3ef53b9.json b/.sqlx/query-af3cf413df39675f434e4fc57a7b78dde00bc3d82221c1964e8ef831f3ef53b9.json new file mode 100644 index 0000000..d24bbac --- /dev/null +++ b/.sqlx/query-af3cf413df39675f434e4fc57a7b78dde00bc3d82221c1964e8ef831f3ef53b9.json @@ -0,0 +1,77 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM results WHERE id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "state", + "type_info": { + "Custom": { + "name": "result_state", + "kind": { + "Enum": [ + "init", + "valid", + "invalid", + "inconclusive", + "error" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "assignment_id", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "stdout", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "stderr", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "exit_code", + "type_info": "Int4" + }, + { + "ordinal": 7, + "name": "group_result_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + true + ] + }, + "hash": "af3cf413df39675f434e4fc57a7b78dde00bc3d82221c1964e8ef831f3ef53b9" +} diff --git a/.sqlx/query-c6e49c93d592c0d1864a653999ea7b2ac73fd31784a2323980025211ac8abfba.json b/.sqlx/query-c6e49c93d592c0d1864a653999ea7b2ac73fd31784a2323980025211ac8abfba.json new file mode 100644 index 0000000..da8498c --- /dev/null +++ b/.sqlx/query-c6e49c93d592c0d1864a653999ea7b2ac73fd31784a2323980025211ac8abfba.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM tasks WHERE id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "deadline", + "type_info": "Interval" + }, + { + "ordinal": 3, + "name": "project_id", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "stdin", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "assignments_needed", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "assignment_user_ids", + "type_info": "Int8Array" + }, + { + "ordinal": 7, + "name": "quorum", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "c6e49c93d592c0d1864a653999ea7b2ac73fd31784a2323980025211ac8abfba" +} diff --git a/.sqlx/query-d99cad4526c095ff8076f7d728efb723996ca83a11a29487a9b1c3a641f64473.json b/.sqlx/query-d99cad4526c095ff8076f7d728efb723996ca83a11a29487a9b1c3a641f64473.json new file mode 100644 index 0000000..caa07df --- /dev/null +++ b/.sqlx/query-d99cad4526c095ff8076f7d728efb723996ca83a11a29487a9b1c3a641f64473.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO assignments (task_id, user_id) VALUES ($1, $2) RETURNING id \"id: _\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: _", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "d99cad4526c095ff8076f7d728efb723996ca83a11a29487a9b1c3a641f64473" +} diff --git a/.sqlx/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json b/.sqlx/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json deleted file mode 100644 index 4a4102a..0000000 --- a/.sqlx/query-dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE\n results\n SET\n group_result_id = $1\n WHERE\n id = $2\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "dd2ccc411586e044836d4f5c9d3dad562d8eecde82db258dc183868b211f4410" -} diff --git a/.sqlx/query-e032e44c9f2ae32f3f7106f88e4631cc74373fcb3fd07816b38db2d8e08f6595.json b/.sqlx/query-e032e44c9f2ae32f3f7106f88e4631cc74373fcb3fd07816b38db2d8e08f6595.json new file mode 100644 index 0000000..47a605d --- /dev/null +++ b/.sqlx/query-e032e44c9f2ae32f3f7106f88e4631cc74373fcb3fd07816b38db2d8e08f6595.json @@ -0,0 +1,27 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE assignments SET state = $2 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + { + "Custom": { + "name": "assignment_state", + "kind": { + "Enum": [ + "init", + "canceled", + "expired", + "submitted" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "e032e44c9f2ae32f3f7106f88e4631cc74373fcb3fd07816b38db2d8e08f6595" +} diff --git a/.sqlx/query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json b/.sqlx/query-e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b.json similarity index 63% rename from .sqlx/query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json rename to .sqlx/query-e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b.json index c202aab..8249e46 100644 --- a/.sqlx/query-f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54.json +++ b/.sqlx/query-e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n projects\n WHERE\n id = $1\n ", + "query": "SELECT * FROM users WHERE id = ANY($1)", "describe": { "columns": [ { @@ -15,32 +15,26 @@ }, { "ordinal": 2, - "name": "created_by_user_id", - "type_info": "Int8" - }, - { - "ordinal": 3, "name": "disabled_at", "type_info": "Timestamptz" }, { - "ordinal": 4, + "ordinal": 3, "name": "name", "type_info": "Text" } ], "parameters": { "Left": [ - "Int8" + "Int8Array" ] }, "nullable": [ - false, false, false, true, false ] }, - "hash": "f7b43273fca1553daea58cb93f9b829c0a9d46d6dc4aa18e76f9fd309e207d54" + "hash": "e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b" } diff --git a/.sqlx/query-e66f8574a4feabd2d3563fbbea190ea823a60766527747e2c1f69a907b725e29.json b/.sqlx/query-e66f8574a4feabd2d3563fbbea190ea823a60766527747e2c1f69a907b725e29.json new file mode 100644 index 0000000..4cb0234 --- /dev/null +++ b/.sqlx/query-e66f8574a4feabd2d3563fbbea190ea823a60766527747e2c1f69a907b725e29.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO projects (created_by_user_id, name) VALUES ($1, $2) RETURNING id \"id: _\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: _", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "e66f8574a4feabd2d3563fbbea190ea823a60766527747e2c1f69a907b725e29" +} diff --git a/.sqlx/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json b/.sqlx/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json deleted file mode 100644 index 5b8d773..0000000 --- a/.sqlx/query-f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE\n tasks\n SET\n assignments_needed = $1\n WHERE\n id = $2\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "f0f7b39962dd47d3a523564bbe52e6fd3fabcc164935906cd974dabd677ec14f" -} diff --git a/.sqlx/query-f2f8f268d019f662f8969127db610bde1590afe8b1d427bc2d0f313c091d4627.json b/.sqlx/query-f2f8f268d019f662f8969127db610bde1590afe8b1d427bc2d0f313c091d4627.json new file mode 100644 index 0000000..1c4fc6c --- /dev/null +++ b/.sqlx/query-f2f8f268d019f662f8969127db610bde1590afe8b1d427bc2d0f313c091d4627.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM files WHERE id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "url", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "hash", + "type_info": "Bytea" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "f2f8f268d019f662f8969127db610bde1590afe8b1d427bc2d0f313c091d4627" +} diff --git a/.sqlx/query-fefa7397e448c03b9ff39bf6bed5e7f9d23b62b564ffde3f97342d3abb0fa7a1.json b/.sqlx/query-fefa7397e448c03b9ff39bf6bed5e7f9d23b62b564ffde3f97342d3abb0fa7a1.json new file mode 100644 index 0000000..c680fe6 --- /dev/null +++ b/.sqlx/query-fefa7397e448c03b9ff39bf6bed5e7f9d23b62b564ffde3f97342d3abb0fa7a1.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO tasks (deadline, project_id, stdin, quorum) VALUES ($1, $2, $3, $4) RETURNING id \"id: _\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: _", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Interval", + "Int8", + "Text", + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "fefa7397e448c03b9ff39bf6bed5e7f9d23b62b564ffde3f97342d3abb0fa7a1" +} diff --git a/common/src/records/assignment.rs b/common/src/records/assignment.rs index 08facb9..6389c0f 100644 --- a/common/src/records/assignment.rs +++ b/common/src/records/assignment.rs @@ -26,4 +26,15 @@ record_impl! { "state = $3 IS NOT FALSE" state: AssignmentState, } + + AssignmentBuilder { + "task_id" "$1" + task_id: Id, + "user_id" "$2" + user_id: Id, + } + + UpdateAssignment { + update_state("state" AssignmentState); + } } diff --git a/common/src/records/file.rs b/common/src/records/file.rs index c743847..b2a394b 100644 --- a/common/src/records/file.rs +++ b/common/src/records/file.rs @@ -14,4 +14,13 @@ record_impl! { } FileFilter {} + + FileBuilder { + "url" "$1" + url: String, + "hash" "$2" + hash: Vec, + } + + UpdateFile {} } diff --git a/common/src/records/mod.rs b/common/src/records/mod.rs index 0daa525..49cf4ac 100644 --- a/common/src/records/mod.rs +++ b/common/src/records/mod.rs @@ -7,16 +7,14 @@ pub mod result; pub mod task; pub mod user; -pub use assignment::{Assignment, AssignmentFilter}; -pub use file::{File, FileFilter}; -pub use platform::{Platform, PlatformFilter}; -pub use project::{Project, ProjectFilter}; -pub use project_version::{ProjectVersion, ProjectVersionFilter}; -pub use result::{Result, ResultFilter}; -pub use task::{Task, TaskFilter}; -pub use user::{User, UserFilter}; - -use crate::types::Id; +pub use assignment::{Assignment, AssignmentBuilder, AssignmentFilter}; +pub use file::{File, FileBuilder, FileFilter}; +pub use platform::{Platform, PlatformBuilder, PlatformFilter}; +pub use project::{Project, ProjectBuilder, ProjectFilter}; +pub use project_version::{ProjectVersion, ProjectVersionBuilder, ProjectVersionFilter}; +pub use result::{Result, ResultBuilder, ResultFilter}; +pub use task::{Task, TaskBuilder, TaskFilter}; +pub use user::{User, UserBuilder, UserFilter}; #[cfg(feature = "sqlx")] mod sqlx { @@ -25,6 +23,9 @@ mod sqlx { postgres::{PgArguments, PgRow}, }; + pub type Query = sqlx::query::Query<'static, Postgres, PgArguments>; + pub type QueryScalar = sqlx::query::QueryScalar<'static, Postgres, T, PgArguments>; + pub type Map = sqlx::query::Map<'static, Postgres, fn(PgRow) -> sqlx::Result, PgArguments>; } @@ -33,12 +34,20 @@ pub trait Record: Sized { type Filter; const PATH: &str; +} + +#[cfg(feature = "sqlx")] +pub trait Select { + type Record; + + fn select(&self) -> sqlx::Map; +} - #[cfg(feature = "sqlx")] - fn select_all(filter: &Self::Filter) -> sqlx::Map; +#[cfg(feature = "sqlx")] +pub trait Insert { + type Record; - #[cfg(feature = "sqlx")] - fn select_one(id: Id) -> sqlx::Map; + fn insert(&self) -> sqlx::QueryScalar>; } macro_rules! record_impl { @@ -51,11 +60,30 @@ macro_rules! record_impl { $filter_ident:ident { $( - $filter_condition_literal:literal + $filter_field_condition_literal:literal $filter_field_ident:ident: $filter_field_ty:ty, )* } + + $builder_ident:ident { + $builder_first_field_name_literal:literal + $builder_first_field_expression_literal:literal + $builder_first_field_ident:ident: $builder_first_field_ty:ty, + $( + $builder_field_name_literal:literal + $builder_field_expression_literal:literal + $builder_field_ident:ident: $builder_field_ty:ty, + )* + } + + $update_ident:ident { + $($update_fn_ident:ident($update_fn_name_literal:literal $update_fn_ty:ty);)* + } ) => { + pub trait $update_ident { + $(fn $update_fn_ident(&self, value: $update_fn_ty) -> $crate::records::sqlx::Query;)* + } + #[derive(Clone, Hash, Debug, Serialize, Deserialize)] pub struct $record_ident { $(pub $record_field_ident: $record_field_ty,)* @@ -67,6 +95,12 @@ macro_rules! record_impl { $(pub $filter_field_ident: Option<$filter_field_ty>,)* } + #[derive(Debug)] + pub struct $builder_ident { + pub $builder_first_field_ident: $builder_first_field_ty, + $(pub $builder_field_ident: $builder_field_ty,)* + } + impl $filter_ident { $( pub fn $filter_field_ident(mut self, $filter_field_ident: impl Into>) -> Self { @@ -80,21 +114,83 @@ macro_rules! record_impl { type Filter = $filter_ident; const PATH: &str = $path_literal; + } + + #[cfg(feature = "sqlx")] + impl $crate::records::Select for $filter_ident { + type Record = $record_ident; - #[cfg(feature = "sqlx")] - fn select_all(#[allow(unused_variables)] filter: &Self::Filter) -> $crate::records::sqlx::Map { + fn select(&self) -> $crate::records::sqlx::Map { sqlx::query_as_unchecked!( - Self, - "SELECT * FROM " + $path_literal + " WHERE TRUE" $(+ " AND (" + $filter_condition_literal + ")")*, - $(filter.$filter_field_ident,)* + Self::Record, + "SELECT * FROM " + $path_literal + " WHERE TRUE" $(+ " AND (" + $filter_field_condition_literal + ")")*, + $(self.$filter_field_ident,)* ) } + } - #[cfg(feature = "sqlx")] - fn select_one(id: $crate::types::Id) -> $crate::records::sqlx::Map { - sqlx::query_as_unchecked!(Self, "SELECT * FROM " + $path_literal + " WHERE id = $1", id) + #[cfg(feature = "sqlx")] + impl $crate::records::Select for $crate::types::Id<$record_ident> { + type Record = $record_ident; + + fn select(&self) -> $crate::records::sqlx::Map { + sqlx::query_as_unchecked!( + Self::Record, + "SELECT * FROM " + $path_literal + " WHERE id = $1", + self + ) + } + } + + #[cfg(feature = "sqlx")] + impl $crate::records::Select for [$crate::types::Id<$record_ident>] { + type Record = $record_ident; + + fn select(&self) -> $crate::records::sqlx::Map { + sqlx::query_as_unchecked!( + Self::Record, + "SELECT * FROM " + $path_literal + " WHERE id = ANY($1)", + self + ) } } + + #[cfg(feature = "sqlx")] + impl $crate::records::Insert for $builder_ident { + type Record = $record_ident; + + fn insert(&self) -> $crate::records::sqlx::QueryScalar<$crate::types::Id> { + sqlx::query_scalar_unchecked!( + "INSERT INTO " + $path_literal + " (" + $builder_first_field_name_literal $(+ ", " + $builder_field_name_literal)* + ") VALUES (" + $builder_first_field_expression_literal $(+ ", " + $builder_field_expression_literal)* + ") RETURNING id \"id: _\"", + self.$builder_first_field_ident, + $(self.$builder_field_ident,)* + ) + } + } + + impl $update_ident for $crate::types::Id<$record_ident> { + $( + fn $update_fn_ident(&self, value: $update_fn_ty) -> $crate::records::sqlx::Query { + sqlx::query_unchecked!( + "UPDATE " + $path_literal + " SET " + $update_fn_name_literal + " = $2 WHERE id = $1", + self, + value, + ) + } + )* + } + + impl $update_ident for [$crate::types::Id<$record_ident>] { + $( + fn $update_fn_ident(&self, value: $update_fn_ty) -> $crate::records::sqlx::Query { + sqlx::query_unchecked!( + "UPDATE " + $path_literal + " SET " + $update_fn_name_literal + " = $2 WHERE id = ANY($1)", + self, + value, + ) + } + )* + } }; } diff --git a/common/src/records/platform.rs b/common/src/records/platform.rs index fa46e5a..5c67699 100644 --- a/common/src/records/platform.rs +++ b/common/src/records/platform.rs @@ -20,4 +20,13 @@ record_impl! { "file_id = $1 IS NOT FALSE" file_id: Id, } + + PlatformBuilder { + "name" "$1" + name: String, + "file_id" "$2" + file_id: Id, + } + + UpdatePlatform {} } diff --git a/common/src/records/project.rs b/common/src/records/project.rs index a9eb5df..3f9fb1b 100644 --- a/common/src/records/project.rs +++ b/common/src/records/project.rs @@ -23,4 +23,13 @@ record_impl! { "disabled_at IS NULL IS DISTINCT FROM $2" disabled: bool, } + + ProjectBuilder { + "created_by_user_id" "$1" + created_by_user_id: Id, + "name" "$2" + name: String, + } + + UpdateProject {} } diff --git a/common/src/records/project_version.rs b/common/src/records/project_version.rs index 7770b29..f2fc231 100644 --- a/common/src/records/project_version.rs +++ b/common/src/records/project_version.rs @@ -28,4 +28,15 @@ record_impl! { "file_id = $4 IS NOT FALSE" file_id: Id, } + + ProjectVersionBuilder { + "project_id" "$1" + project_id: Id, + "platform_id" "$2" + platform_id: Id, + "file_id" "$3" + file_id: Id, + } + + UpdateProjectVersion {} } diff --git a/common/src/records/result.rs b/common/src/records/result.rs index 7ac9ca5..e86beba 100644 --- a/common/src/records/result.rs +++ b/common/src/records/result.rs @@ -28,4 +28,20 @@ record_impl! { "state = $3 IS NOT FALSE" state: ResultState, } + + ResultBuilder { + "assignment_id" "$1" + assignment_id: Id, + "stdout" "$2" + stdout: String, + "stderr" "$3" + stderr: String, + "exit_code" "$4" + exit_code: Option, + } + + UpdateResult { + update_group_result_id("group_result_id" Option>); + update_state("state" ResultState); + } } diff --git a/common/src/records/task.rs b/common/src/records/task.rs index d7d6bd0..8754a74 100644 --- a/common/src/records/task.rs +++ b/common/src/records/task.rs @@ -24,4 +24,19 @@ record_impl! { "project_id = $1 IS NOT FALSE" project_id: Id, } + + TaskBuilder { + "deadline" "$1" + deadline: Interval, + "project_id" "$2" + project_id: Id, + "stdin" "$3" + stdin: String, + "quorum" "$4" + quorum: i32, + } + + UpdateTask { + update_assignments_needed("assignments_needed" i32); + } } diff --git a/common/src/records/user.rs b/common/src/records/user.rs index 2acb953..4c21256 100644 --- a/common/src/records/user.rs +++ b/common/src/records/user.rs @@ -17,4 +17,11 @@ record_impl! { "disabled_at IS NULL IS DISTINCT FROM $1" disabled: bool, } + + UserBuilder { + "name" "$1" + name: String, + } + + UpdateUser {} } diff --git a/server/migrations/20250426220809_init.sql b/server/migrations/20250426220809_init.sql index 4fac810..8da7fb6 100644 --- a/server/migrations/20250426220809_init.sql +++ b/server/migrations/20250426220809_init.sql @@ -90,7 +90,7 @@ CREATE TABLE results ( ); -- update assignments.state -CREATE FUNCTION set_assignment_state_submitted() +CREATE FUNCTION set_assignments_state_submitted() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN @@ -104,14 +104,14 @@ AS $$ BEGIN RETURN NEW; END $$; -CREATE TRIGGER set_assignment_state_submitted_before_insert +CREATE TRIGGER set_assignments_state_submitted_before_insert BEFORE INSERT ON results FOR EACH ROW -EXECUTE FUNCTION set_assignment_state_submitted(); +EXECUTE FUNCTION set_assignments_state_submitted(); -- update tasks.assignment_user_ids -CREATE FUNCTION remove_assignment_user_id() +CREATE FUNCTION remove_tasks_assignment_user_id() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN @@ -125,7 +125,7 @@ AS $$ BEGIN RETURN NEW; END $$; -CREATE FUNCTION add_assignment_user_id() +CREATE FUNCTION add_tasks_assignment_user_id() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN @@ -139,29 +139,29 @@ AS $$ BEGIN RETURN NEW; END $$; -CREATE TRIGGER remove_assignment_user_id_before_update +CREATE TRIGGER remove_tasks_assignment_user_id_before_update BEFORE UPDATE OF state ON assignments FOR EACH ROW WHEN ((OLD.state = 'init' OR OLD.state = 'submitted') AND (NEW.state = 'canceled' OR NEW.state = 'expired')) -EXECUTE FUNCTION remove_assignment_user_id(); +EXECUTE FUNCTION remove_tasks_assignment_user_id(); -CREATE TRIGGER remove_assignment_user_id_before_delete +CREATE TRIGGER remove_tasks_assignment_user_id_before_delete BEFORE DELETE ON assignments FOR EACH ROW WHEN (OLD.state = 'init' OR OLD.state = 'submitted') -EXECUTE FUNCTION remove_assignment_user_id(); +EXECUTE FUNCTION remove_tasks_assignment_user_id(); -CREATE TRIGGER add_assignment_user_id_before_insert +CREATE TRIGGER add_tasks_assignment_user_id_before_insert BEFORE INSERT ON assignments FOR EACH ROW WHEN (NEW.state = 'init' OR NEW.state = 'submitted') -EXECUTE FUNCTION add_assignment_user_id(); +EXECUTE FUNCTION add_tasks_assignment_user_id(); -- set tasks.assignments_needed -CREATE FUNCTION set_assignments_needed() +CREATE FUNCTION set_tasks_assignments_needed() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN @@ -170,8 +170,24 @@ AS $$ BEGIN RETURN NEW; END $$; -CREATE TRIGGER set_assignments_needed_before_insert +CREATE TRIGGER set_tasks_assignments_needed_before_insert BEFORE INSERT ON tasks FOR EACH ROW -EXECUTE FUNCTION set_assignments_needed(); +EXECUTE FUNCTION set_tasks_assignments_needed(); + +-- set assignments.deadline_at +CREATE FUNCTION set_assignments_deadline_at() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ BEGIN + SELECT now() + deadline FROM tasks WHERE id = NEW.task_id INTO NEW.deadline_at; + + RETURN NEW; +END $$; + +CREATE TRIGGER set_assignments_deadline_at_before_insert +BEFORE INSERT +ON assignments +FOR EACH ROW +EXECUTE FUNCTION set_assignments_deadline_at(); diff --git a/server/src/auth.rs b/server/src/auth.rs index 4586178..2b2449e 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -10,7 +10,7 @@ use axum_extra::{ }; use base64::prelude::*; use clusterizer_common::{ - records::{Record, User}, + records::{Select, User}, types::Id, }; use hmac::{Hmac, KeyInit, Mac}; @@ -63,8 +63,9 @@ impl FromRequestParts for Auth { user_id_bytes.copy_from_slice(&api_key_bytes[..8]); - let user_id = i64::from_le_bytes(user_id_bytes).into(); - let user = User::select_one(user_id) + let user_id: Id = i64::from_le_bytes(user_id_bytes).into(); + let user = user_id + .select() .fetch_one(&state.pool) .await .map_err(|_| AuthRejection::BadApiKey)?; diff --git a/server/src/main.rs b/server/src/main.rs index d913366..0e51f0d 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -8,8 +8,11 @@ use axum::{ Router, routing::{get, post}, }; -use clusterizer_common::records::{ - Assignment, File, Platform, Project, ProjectVersion, Record, Result, Task, User, +use clusterizer_common::{ + records::{ + Assignment, File, Platform, Project, ProjectVersion, Record, Result, Select, Task, User, + }, + types::Id, }; use serde::{Serialize, de::DeserializeOwned}; @@ -63,7 +66,8 @@ async fn serve_task(state: AppState, address: String) { fn record_router() -> Router where T: Record + Send + Unpin + Serialize + 'static, - T::Filter: Send + DeserializeOwned, + T::Filter: Select + Send + DeserializeOwned, + Id: Select, { Router::new() .route(&format!("/{}", T::PATH), get(routes::get_all::)) diff --git a/server/src/routes/fetch_tasks.rs b/server/src/routes/fetch_tasks.rs index 03d346e..463453b 100644 --- a/server/src/routes/fetch_tasks.rs +++ b/server/src/routes/fetch_tasks.rs @@ -1,7 +1,7 @@ use axum::{Json, extract::State}; use clusterizer_common::{ errors::FetchTasksError, - records::{Project, Task}, + records::{AssignmentBuilder, Insert, Select, Task}, requests::FetchTasksRequest, }; @@ -18,20 +18,7 @@ pub async fn fetch_tasks( ) -> AppResult>, FetchTasksError> { let mut tx = state.pool.begin().await?; - let projects = sqlx::query_as_unchecked!( - Project, - r#" - SELECT - * - FROM - projects - WHERE - id = ANY($1) - "#, - request.project_ids, - ) - .fetch_all(&mut *tx) - .await?; + let projects = request.project_ids.select().fetch_all(&mut *tx).await?; if projects.len() != request.project_ids.len() { Err(AppError::Specific(FetchTasksError::InvalidProject))?; @@ -65,23 +52,12 @@ pub async fn fetch_tasks( .await?; for task in &tasks { - sqlx::query_unchecked!( - r#" - INSERT INTO assignments ( - task_id, - user_id, - deadline_at - ) VALUES ( - $1, - $2, - now() + $3 - ) - "#, - task.id, + AssignmentBuilder { + task_id: task.id, user_id, - task.deadline, - ) - .execute(&mut *tx) + } + .insert() + .fetch_one(&mut *tx) .await?; } diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 0fab9ad..3b59dd1 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -4,7 +4,7 @@ use axum::{ }; use clusterizer_common::{ errors::{Infallible, NotFound}, - records::Record, + records::{Record, Select}, types::Id, }; @@ -28,16 +28,22 @@ pub use validate_submit::validate_submit; pub async fn get_all( State(state): State, Query(filter): Query, -) -> AppResult>, Infallible> { - Ok(Json(T::select_all(&filter).fetch_all(&state.pool).await?)) +) -> AppResult>, Infallible> +where + T::Filter: Select, +{ + Ok(Json(filter.select().fetch_all(&state.pool).await?)) } pub async fn get_one( State(state): State, Path(id): Path>, -) -> AppResult, NotFound> { +) -> AppResult, NotFound> +where + Id: Select, +{ Ok(Json( - T::select_one(id) + id.select() .fetch_one(&state.pool) .await .map_not_found(NotFound)?, diff --git a/server/src/routes/register.rs b/server/src/routes/register.rs index d5d7c90..2750c66 100644 --- a/server/src/routes/register.rs +++ b/server/src/routes/register.rs @@ -1,6 +1,9 @@ use axum::{Json, extract::State}; use clusterizer_common::{ - errors::RegisterError, requests::RegisterRequest, responses::RegisterResponse, + errors::RegisterError, + records::{Insert, user::UserBuilder}, + requests::RegisterRequest, + responses::RegisterResponse, }; use crate::{ @@ -29,20 +32,11 @@ pub async fn register( Err(AppError::Specific(RegisterError::InvalidCharacter))?; } - let user_id = sqlx::query_scalar_unchecked!( - r#" - INSERT INTO users ( - name - ) VALUES ( - $1 - ) - RETURNING id "id: _" - "#, - request.name, - ) - .fetch_one(&state.pool) - .await - .map_unique_violation(RegisterError::AlreadyExists)?; + let user_id = UserBuilder { name: request.name } + .insert() + .fetch_one(&state.pool) + .await + .map_unique_violation(RegisterError::AlreadyExists)?; Ok(Json(RegisterResponse { api_key: auth::api_key(&state, user_id), diff --git a/server/src/routes/submit_result.rs b/server/src/routes/submit_result.rs index b55d50b..ffc8d9b 100644 --- a/server/src/routes/submit_result.rs +++ b/server/src/routes/submit_result.rs @@ -4,7 +4,7 @@ use axum::{ }; use clusterizer_common::{ errors::SubmitResultError, - records::{Assignment, Task}, + records::{Assignment, Insert, ResultBuilder, Task}, requests::SubmitResultRequest, types::{AssignmentState, Id}, }; @@ -50,26 +50,14 @@ pub async fn submit_result( Err(AppError::Specific(SubmitResultError::AssignmentExpired))?; } - sqlx::query_unchecked!( - r#" - INSERT INTO results ( - assignment_id, - stdout, - stderr, - exit_code - ) VALUES ( - $1, - $2, - $3, - $4 - ) - "#, - assignment.id, - request.stdout, - request.stderr, - request.exit_code, - ) - .execute(&mut *tx) + ResultBuilder { + assignment_id: assignment.id, + stdout: request.stdout, + stderr: request.stderr, + exit_code: request.exit_code, + } + .insert() + .fetch_one(&mut *tx) .await .map_unique_violation(SubmitResultError::AlreadyExists)?; diff --git a/server/src/routes/validate_fetch.rs b/server/src/routes/validate_fetch.rs index 875c7e2..6754121 100644 --- a/server/src/routes/validate_fetch.rs +++ b/server/src/routes/validate_fetch.rs @@ -4,7 +4,7 @@ use axum::{ }; use clusterizer_common::{ errors::ValidateFetchError, - records::{Project, Task}, + records::{Project, Select, Task}, types::Id, }; @@ -19,21 +19,11 @@ pub async fn validate_fetch( Path(project_id): Path>, Auth(user_id): Auth, ) -> AppResult>, ValidateFetchError> { - let project = sqlx::query_as_unchecked!( - Project, - r#" - SELECT - * - FROM - projects - WHERE - id = $1 - "#, - project_id, - ) - .fetch_one(&state.pool) - .await - .map_not_found(ValidateFetchError::InvalidProject)?; + let project = project_id + .select() + .fetch_one(&state.pool) + .await + .map_not_found(ValidateFetchError::InvalidProject)?; if project.created_by_user_id != user_id { Err(AppError::Specific(ValidateFetchError::Forbidden))?; diff --git a/server/src/routes/validate_submit.rs b/server/src/routes/validate_submit.rs index 43ffc0f..b447860 100644 --- a/server/src/routes/validate_submit.rs +++ b/server/src/routes/validate_submit.rs @@ -1,11 +1,10 @@ use axum::{Json, extract::State}; use clusterizer_common::{ errors::ValidateSubmitError, - records::{Project, Result, Task}, + records::{Result, Select, Task, result::UpdateResult, task::UpdateTask}, requests::ValidateSubmitRequest, types::{Id, ResultState}, }; -use sqlx::{Postgres, postgres::PgArguments, query::Query}; use std::collections::HashMap; @@ -73,20 +72,7 @@ pub async fn validate_submit( .await?; // Check project permissions. - let project = sqlx::query_as_unchecked!( - Project, - r#" - SELECT - * - FROM - projects - WHERE - id = $1 - "#, - task.project_id - ) - .fetch_one(&mut *tx) - .await?; + let project = task.project_id.select().fetch_one(&mut *tx).await?; if project.created_by_user_id != user_id { Err(AppError::Specific(ValidateSubmitError::Forbidden))?; @@ -151,27 +137,18 @@ pub async fn validate_submit( } // Update state of error results. - set_result_state(&error_result_ids, ResultState::Error) + error_result_ids + .update_state(ResultState::Error) .execute(&mut *tx) .await?; // Update group ids. for (&result_id, &group_id) in &request.results { if let Some(group_id) = group_id { - sqlx::query_unchecked!( - r#" - UPDATE - results - SET - group_result_id = $1 - WHERE - id = $2 - "#, - group_id, - result_id, - ) - .execute(&mut *tx) - .await?; + result_id + .update_group_result_id(Some(group_id)) + .execute(&mut *tx) + .await?; } } @@ -214,7 +191,8 @@ pub async fn validate_submit( .map(|(&result_id, _)| result_id) .collect(); - set_result_state(&inconclusive_result_ids, ResultState::Inconclusive) + inconclusive_result_ids + .update_state(ResultState::Inconclusive) .execute(&mut *tx) .await?; @@ -227,41 +205,13 @@ pub async fn validate_submit( let assignments_needed = (results.len() - largest_inconclusive_group.len()) as i32 + task.quorum; - sqlx::query_unchecked!( - r#" - UPDATE - tasks - SET - assignments_needed = $1 - WHERE - id = $2 - "#, - assignments_needed, - task.id, - ) - .execute(&mut *tx) - .await?; + task.id + .update_assignments_needed(assignments_needed) + .execute(&mut *tx) + .await?; } tx.commit().await?; Ok(()) } - -fn set_result_state( - result_ids: &[Id], - result_state: ResultState, -) -> Query<'static, Postgres, PgArguments> { - sqlx::query_unchecked!( - r#" - UPDATE - results - SET - state = $1 - WHERE - id = ANY($2) - "#, - result_state, - result_ids, - ) -} From c808d99f34f7489e0cd5c3efaf1870d9fc07eb9c Mon Sep 17 00:00:00 2001 From: Chen Steenvoorden Date: Wed, 8 Apr 2026 00:44:55 +0200 Subject: [PATCH 3/7] Refactor filters --- ...e25fc66087bb0657240e9631f2b271f35a17.json} | 36 ++++++++----- ...5db66f9b5ca8368464612f8ffd7948dc6de0.json} | 18 +++---- ...5cfe718e153af822a2b6dfee60554202face.json} | 14 +++--- ...c0d8ad485676124b25bc1e9fa6d77f419d77.json} | 11 ++-- ...5f60fb2209a53e8360ea5f1675b99667306b2.json | 40 --------------- ...064ba6a25617d20b8319f44214da55dade8e.json} | 12 +++-- ...20494e71265b8fa0ea4d20ab62d185469bdc.json} | 32 ++++++++---- ...b56c8fc876cef5d63547d11661c28980e2813.json | 50 +++++++++++++++++++ ...44c37feb4a71ded16f2ada5406d47616f57d.json} | 9 ++-- Cargo.lock | 1 + api/Cargo.toml | 2 +- api/src/client.rs | 41 ++++----------- cli/src/client.rs | 13 ++--- common/Cargo.toml | 2 + common/src/records/assignment.rs | 18 ++++--- common/src/records/file.rs | 11 +++- common/src/records/mod.rs | 47 ++++++++++++++--- common/src/records/platform.rs | 10 +++- common/src/records/project.rs | 14 ++++-- common/src/records/project_version.rs | 20 +++++--- common/src/records/result.rs | 22 +++++--- common/src/records/task.rs | 16 +++++- common/src/records/user.rs | 10 +++- 23 files changed, 280 insertions(+), 169 deletions(-) rename .sqlx/{query-381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168.json => query-39478ab35c0d7baea1f5196e2a9be25fc66087bb0657240e9631f2b271f35a17.json} (52%) rename .sqlx/{query-32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b.json => query-3f3a8f21e93fbce600343a1f65c85db66f9b5ca8368464612f8ffd7948dc6de0.json} (53%) rename .sqlx/{query-7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3.json => query-6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face.json} (53%) rename .sqlx/{query-c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794.json => query-8e2039aab05638ef6385546cbf7ec0d8ad485676124b25bc1e9fa6d77f419d77.json} (52%) delete mode 100644 .sqlx/query-a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2.json rename .sqlx/{query-b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874.json => query-baee122ebee44d542b6051d8fcf9064ba6a25617d20b8319f44214da55dade8e.json} (57%) rename .sqlx/{query-60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291.json => query-beb0b066198833e63448ee67a1a720494e71265b8fa0ea4d20ab62d185469bdc.json} (51%) create mode 100644 .sqlx/query-f1f21e98b11d601b686e3db1f18b56c8fc876cef5d63547d11661c28980e2813.json rename .sqlx/{query-e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f.json => query-f86f80ca744ddd5e17edc82fa2c244c37feb4a71ded16f2ada5406d47616f57d.json} (54%) diff --git a/.sqlx/query-381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168.json b/.sqlx/query-39478ab35c0d7baea1f5196e2a9be25fc66087bb0657240e9631f2b271f35a17.json similarity index 52% rename from .sqlx/query-381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168.json rename to .sqlx/query-39478ab35c0d7baea1f5196e2a9be25fc66087bb0657240e9631f2b271f35a17.json index 4e06eaf..7ba89f6 100644 --- a/.sqlx/query-381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168.json +++ b/.sqlx/query-39478ab35c0d7baea1f5196e2a9be25fc66087bb0657240e9631f2b271f35a17.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM results WHERE TRUE AND (assignment_id = $1 IS NOT FALSE) AND (group_result_id = $2 OR $2 IS NULL) AND (state = $3 IS NOT FALSE)", + "query": "SELECT * FROM results WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::int8[] IS NULL OR array_position($3, assignment_id) IS NOT NULL) AND ($4::text[] IS NULL OR array_position($4, stdout) IS NOT NULL) AND ($5::text[] IS NULL OR array_position($5, stderr) IS NOT NULL) AND ($6::int4[] IS NULL OR array_position($6, exit_code) IS NOT NULL) AND ($7::int8[] IS NULL OR array_position($7, group_result_id) IS NOT NULL) AND ($8::result_state[] IS NULL OR array_position($8, state) IS NOT NULL)", "describe": { "columns": [ { @@ -59,19 +59,31 @@ ], "parameters": { "Left": [ - "Int8", - "Int8", + "Int8Array", + "TimestamptzArray", + "Int8Array", + "TextArray", + "TextArray", + "Int4Array", + "Int8Array", { "Custom": { - "name": "result_state", + "name": "result_state[]", "kind": { - "Enum": [ - "init", - "valid", - "invalid", - "inconclusive", - "error" - ] + "Array": { + "Custom": { + "name": "result_state", + "kind": { + "Enum": [ + "init", + "valid", + "invalid", + "inconclusive", + "error" + ] + } + } + } } } } @@ -88,5 +100,5 @@ true ] }, - "hash": "381bf1e7d5d223ccb3183e7dbc1d99a87b879306f730d728f6887fd80a25d168" + "hash": "39478ab35c0d7baea1f5196e2a9be25fc66087bb0657240e9631f2b271f35a17" } diff --git a/.sqlx/query-32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b.json b/.sqlx/query-3f3a8f21e93fbce600343a1f65c85db66f9b5ca8368464612f8ffd7948dc6de0.json similarity index 53% rename from .sqlx/query-32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b.json rename to .sqlx/query-3f3a8f21e93fbce600343a1f65c85db66f9b5ca8368464612f8ffd7948dc6de0.json index 0c9571b..162a6e5 100644 --- a/.sqlx/query-32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b.json +++ b/.sqlx/query-3f3a8f21e93fbce600343a1f65c85db66f9b5ca8368464612f8ffd7948dc6de0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM projects WHERE TRUE AND (created_by_user_id = $1 IS NOT FALSE) AND (disabled_at IS NULL IS DISTINCT FROM $2)", + "query": "SELECT * FROM users WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::timestamptz[] IS NULL OR array_position($3, disabled_at) IS NOT NULL) AND ($4::text[] IS NULL OR array_position($4, name) IS NOT NULL)", "describe": { "columns": [ { @@ -15,33 +15,29 @@ }, { "ordinal": 2, - "name": "created_by_user_id", - "type_info": "Int8" - }, - { - "ordinal": 3, "name": "disabled_at", "type_info": "Timestamptz" }, { - "ordinal": 4, + "ordinal": 3, "name": "name", "type_info": "Text" } ], "parameters": { "Left": [ - "Int8", - "Bool" + "Int8Array", + "TimestamptzArray", + "TimestamptzArray", + "TextArray" ] }, "nullable": [ - false, false, false, true, false ] }, - "hash": "32b0fcf070acf4ed126d94ab9173c60fd9160da037b78741eab97ca4ea214f0b" + "hash": "3f3a8f21e93fbce600343a1f65c85db66f9b5ca8368464612f8ffd7948dc6de0" } diff --git a/.sqlx/query-7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3.json b/.sqlx/query-6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face.json similarity index 53% rename from .sqlx/query-7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3.json rename to .sqlx/query-6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face.json index f603072..e2f51df 100644 --- a/.sqlx/query-7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3.json +++ b/.sqlx/query-6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM project_versions WHERE TRUE AND (disabled_at IS NULL IS DISTINCT FROM $1) AND (project_id = $2 IS NOT FALSE) AND (platform_id = $3 IS NOT FALSE) AND (file_id = $4 IS NOT FALSE)", + "query": "SELECT * FROM project_versions WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::timestamptz[] IS NULL OR array_position($3, disabled_at) IS NOT NULL) AND ($4::int8[] IS NULL OR array_position($4, project_id) IS NOT NULL) AND ($5::int8[] IS NULL OR array_position($5, platform_id) IS NOT NULL) AND ($6::int8[] IS NULL OR array_position($6, file_id) IS NOT NULL)", "describe": { "columns": [ { @@ -36,10 +36,12 @@ ], "parameters": { "Left": [ - "Bool", - "Int8", - "Int8", - "Int8" + "Int8Array", + "TimestamptzArray", + "TimestamptzArray", + "Int8Array", + "Int8Array", + "Int8Array" ] }, "nullable": [ @@ -51,5 +53,5 @@ false ] }, - "hash": "7c9e5a3333f4c09bef3f6b965139d27b565235a41e7d747d41e49238674fb5c3" + "hash": "6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face" } diff --git a/.sqlx/query-c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794.json b/.sqlx/query-8e2039aab05638ef6385546cbf7ec0d8ad485676124b25bc1e9fa6d77f419d77.json similarity index 52% rename from .sqlx/query-c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794.json rename to .sqlx/query-8e2039aab05638ef6385546cbf7ec0d8ad485676124b25bc1e9fa6d77f419d77.json index b1790fd..226ce8c 100644 --- a/.sqlx/query-c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794.json +++ b/.sqlx/query-8e2039aab05638ef6385546cbf7ec0d8ad485676124b25bc1e9fa6d77f419d77.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM files WHERE TRUE", + "query": "SELECT * FROM files WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::text[] IS NULL OR array_position($3, url) IS NOT NULL) AND ($4::bytea[] IS NULL OR array_position($4, hash) IS NOT NULL)", "describe": { "columns": [ { @@ -25,7 +25,12 @@ } ], "parameters": { - "Left": [] + "Left": [ + "Int8Array", + "TimestamptzArray", + "TextArray", + "ByteaArray" + ] }, "nullable": [ false, @@ -34,5 +39,5 @@ false ] }, - "hash": "c53a49ded378be1c8d67295ceff9b2532cd7d4b2dfc33d4a5735c33f2be64794" + "hash": "8e2039aab05638ef6385546cbf7ec0d8ad485676124b25bc1e9fa6d77f419d77" } diff --git a/.sqlx/query-a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2.json b/.sqlx/query-a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2.json deleted file mode 100644 index bec3d40..0000000 --- a/.sqlx/query-a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM users WHERE TRUE AND (disabled_at IS NULL IS DISTINCT FROM $1)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 2, - "name": "disabled_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 3, - "name": "name", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Bool" - ] - }, - "nullable": [ - false, - false, - true, - false - ] - }, - "hash": "a297b902a5104729e1f79046e235f60fb2209a53e8360ea5f1675b99667306b2" -} diff --git a/.sqlx/query-b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874.json b/.sqlx/query-baee122ebee44d542b6051d8fcf9064ba6a25617d20b8319f44214da55dade8e.json similarity index 57% rename from .sqlx/query-b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874.json rename to .sqlx/query-baee122ebee44d542b6051d8fcf9064ba6a25617d20b8319f44214da55dade8e.json index 8482544..8e7c71d 100644 --- a/.sqlx/query-b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874.json +++ b/.sqlx/query-baee122ebee44d542b6051d8fcf9064ba6a25617d20b8319f44214da55dade8e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM tasks WHERE TRUE AND (project_id = $1 IS NOT FALSE)", + "query": "SELECT * FROM tasks WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::interval[] IS NULL OR array_position($3, deadline) IS NOT NULL) AND ($4::int8[] IS NULL OR array_position($4, project_id) IS NOT NULL) AND ($5::text[] IS NULL OR array_position($5, stdin) IS NOT NULL) AND ($6::int4[] IS NULL OR array_position($6, assignments_needed) IS NOT NULL) AND ($7::int8[] IS NULL OR array_position($7, quorum) IS NOT NULL)", "describe": { "columns": [ { @@ -46,7 +46,13 @@ ], "parameters": { "Left": [ - "Int8" + "Int8Array", + "TimestamptzArray", + "IntervalArray", + "Int8Array", + "TextArray", + "Int4Array", + "Int8Array" ] }, "nullable": [ @@ -60,5 +66,5 @@ false ] }, - "hash": "b85bc5afd1df3128dbf21042dd08370944664024942cc5276a17cb79385be874" + "hash": "baee122ebee44d542b6051d8fcf9064ba6a25617d20b8319f44214da55dade8e" } diff --git a/.sqlx/query-60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291.json b/.sqlx/query-beb0b066198833e63448ee67a1a720494e71265b8fa0ea4d20ab62d185469bdc.json similarity index 51% rename from .sqlx/query-60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291.json rename to .sqlx/query-beb0b066198833e63448ee67a1a720494e71265b8fa0ea4d20ab62d185469bdc.json index 6bc2050..8c59307 100644 --- a/.sqlx/query-60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291.json +++ b/.sqlx/query-beb0b066198833e63448ee67a1a720494e71265b8fa0ea4d20ab62d185469bdc.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM assignments WHERE TRUE AND (task_id = $1 IS NOT FALSE) AND (user_id = $2 IS NOT FALSE) AND (state = $3 IS NOT FALSE)", + "query": "SELECT * FROM assignments WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::timestamptz[] IS NULL OR array_position($3, deadline_at) IS NOT NULL) AND ($4::int8[] IS NULL OR array_position($4, task_id) IS NOT NULL) AND ($5::int8[] IS NULL OR array_position($5, user_id) IS NOT NULL) AND ($6::assignment_state[] IS NULL OR array_position($6, state) IS NOT NULL)", "describe": { "columns": [ { @@ -48,18 +48,28 @@ ], "parameters": { "Left": [ - "Int8", - "Int8", + "Int8Array", + "TimestamptzArray", + "TimestamptzArray", + "Int8Array", + "Int8Array", { "Custom": { - "name": "assignment_state", + "name": "assignment_state[]", "kind": { - "Enum": [ - "init", - "canceled", - "expired", - "submitted" - ] + "Array": { + "Custom": { + "name": "assignment_state", + "kind": { + "Enum": [ + "init", + "canceled", + "expired", + "submitted" + ] + } + } + } } } } @@ -74,5 +84,5 @@ false ] }, - "hash": "60a864bebcfac7a40865006046d0845a788f13b570be1e71ce9ff40de6035291" + "hash": "beb0b066198833e63448ee67a1a720494e71265b8fa0ea4d20ab62d185469bdc" } diff --git a/.sqlx/query-f1f21e98b11d601b686e3db1f18b56c8fc876cef5d63547d11661c28980e2813.json b/.sqlx/query-f1f21e98b11d601b686e3db1f18b56c8fc876cef5d63547d11661c28980e2813.json new file mode 100644 index 0000000..c49dd9e --- /dev/null +++ b/.sqlx/query-f1f21e98b11d601b686e3db1f18b56c8fc876cef5d63547d11661c28980e2813.json @@ -0,0 +1,50 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM projects WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::int8[] IS NULL OR array_position($3, created_by_user_id) IS NOT NULL) AND ($4::timestamptz[] IS NULL OR array_position($4, disabled_at) IS NOT NULL) AND ($5::text[] IS NULL OR array_position($5, name) IS NOT NULL)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "created_by_user_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "disabled_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "name", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int8Array", + "TimestamptzArray", + "Int8Array", + "TimestamptzArray", + "TextArray" + ] + }, + "nullable": [ + false, + false, + false, + true, + false + ] + }, + "hash": "f1f21e98b11d601b686e3db1f18b56c8fc876cef5d63547d11661c28980e2813" +} diff --git a/.sqlx/query-e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f.json b/.sqlx/query-f86f80ca744ddd5e17edc82fa2c244c37feb4a71ded16f2ada5406d47616f57d.json similarity index 54% rename from .sqlx/query-e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f.json rename to .sqlx/query-f86f80ca744ddd5e17edc82fa2c244c37feb4a71ded16f2ada5406d47616f57d.json index 4f7b456..5da80aa 100644 --- a/.sqlx/query-e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f.json +++ b/.sqlx/query-f86f80ca744ddd5e17edc82fa2c244c37feb4a71ded16f2ada5406d47616f57d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM platforms WHERE TRUE AND (file_id = $1 IS NOT FALSE)", + "query": "SELECT * FROM platforms WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::text[] IS NULL OR array_position($3, name) IS NOT NULL) AND ($4::int8[] IS NULL OR array_position($4, file_id) IS NOT NULL)", "describe": { "columns": [ { @@ -26,7 +26,10 @@ ], "parameters": { "Left": [ - "Int8" + "Int8Array", + "TimestamptzArray", + "TextArray", + "Int8Array" ] }, "nullable": [ @@ -36,5 +39,5 @@ false ] }, - "hash": "e8f8fca5ca65083d670152567a5338dbc7ed6bed3ac6cfa696391dc95e69c18f" + "hash": "f86f80ca744ddd5e17edc82fa2c244c37feb4a71ded16f2ada5406d47616f57d" } diff --git a/Cargo.lock b/Cargo.lock index 2e9bb8a..3c4aba2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -410,6 +410,7 @@ name = "clusterizer-common" version = "0.1.0" dependencies = [ "chrono", + "reqwest", "serde", "sqlx", "thiserror 2.0.18", diff --git a/api/Cargo.toml b/api/Cargo.toml index 7383a8a..070c1eb 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] -clusterizer-common = { version = "0.1.0", path = "../common" } +clusterizer-common = { version = "0.1.0", path = "../common", features = ["reqwest"] } reqwest = { version = "0.13.2", features = ["json", "query"] } serde = "1.0.228" diff --git a/api/src/client.rs b/api/src/client.rs index 72836bc..b43d38d 100644 --- a/api/src/client.rs +++ b/api/src/client.rs @@ -1,6 +1,6 @@ use clusterizer_common::{ - errors::{FetchTasksError, Infallible, NotFound, RegisterError, SubmitResultError}, - records::{Record, Task}, + errors::{FetchTasksError, RegisterError, SubmitResultError}, + records::{Get, Task}, requests::{FetchTasksRequest, RegisterRequest, SubmitResultRequest}, responses::RegisterResponse, types::Id, @@ -25,20 +25,9 @@ impl ApiClient { } } - pub async fn get_all( - &self, - filter: &T::Filter, - ) -> ApiResult, Infallible> - where - T::Filter: Serialize, - { - let url = format!("{}/{}", self.url, T::PATH); - Ok(self.get_query(url, filter).await?.json().await?) - } - - pub async fn get_one(&self, id: Id) -> ApiResult { - let url = format!("{}/{}/{}", self.url, T::PATH, id); - Ok(self.get(url).await?.json().await?) + pub async fn get(&self, by: &T) -> ApiResult { + let request = by.get(&self.client, &self.url); + Ok(self.send(request).await?.json().await?) } pub async fn register( @@ -46,7 +35,7 @@ impl ApiClient { request: &RegisterRequest, ) -> ApiResult { let url = format!("{}/register", self.url); - Ok(self.post(url, request).await?.json().await?) + Ok(self.send_post(url, request).await?.json().await?) } pub async fn fetch_tasks( @@ -54,7 +43,7 @@ impl ApiClient { request: &FetchTasksRequest, ) -> ApiResult, FetchTasksError> { let url = format!("{}/fetch_tasks", self.url); - Ok(self.post(url, request).await?.json().await?) + Ok(self.send_post(url, request).await?.json().await?) } pub async fn submit_result( @@ -63,23 +52,11 @@ impl ApiClient { request: &SubmitResultRequest, ) -> ApiResult<(), SubmitResultError> { let url = format!("{}/submit_result/{task_id}", self.url); - self.post(url, request).await?; + self.send_post(url, request).await?; Ok(()) } - async fn get(&self, url: impl IntoUrl) -> ApiResult { - self.send(self.client.get(url)).await - } - - async fn get_query( - &self, - url: impl IntoUrl, - query: &impl Serialize, - ) -> ApiResult { - self.send(self.client.get(url).query(query)).await - } - - async fn post( + async fn send_post( &self, url: impl IntoUrl, request: &impl Serialize, diff --git a/cli/src/client.rs b/cli/src/client.rs index 4cbadbb..a31e727 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -96,7 +96,7 @@ impl ClusterizerClient { let tasks = loop { let project_versions_by_project_id: HashMap<_, _> = self .client - .get_all::(&ProjectVersionFilter::default().disabled(false)) + .get(&ProjectVersionFilter::default().disabled_at(vec![None])) .await? .into_iter() .filter(|project_version| self.platform_ids.contains(&project_version.platform_id)) @@ -105,7 +105,7 @@ impl ClusterizerClient { let projects_by_project_id: HashMap<_, _> = self .client - .get_all::(&ProjectFilter::default().disabled(false)) + .get(&ProjectFilter::default().disabled_at(vec![None])) .await? .into_iter() .filter(|project| project_versions_by_project_id.contains_key(&project.id)) @@ -114,7 +114,7 @@ impl ClusterizerClient { let files_by_file_id: HashMap<_, _> = self .client - .get_all::(&FileFilter::default()) + .get(&FileFilter::default()) .await? .into_iter() .map(|file| (file.id, file)) @@ -241,11 +241,8 @@ pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { let mut platform_ids = Vec::new(); let mut platform_names = Vec::new(); - for platform in client - .get_all::(&PlatformFilter::default()) - .await? - { - let file = client.get_one(platform.file_id).await?; + for platform in client.get(&PlatformFilter::default()).await? { + let file = client.get(&platform.file_id).await?; debug!( "Platform id: {}, tester archive url: {}", diff --git a/common/Cargo.toml b/common/Cargo.toml index ac689f5..1c391d9 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -5,9 +5,11 @@ edition = "2024" [dependencies] chrono = { version = "0.4.44", features = ["serde"] } +reqwest = { version = "0.13.2", optional = true, features = ["query"] } serde = { version = "1.0.228", features = ["derive"] } sqlx = { version = "0.8.6", optional = true, features = ["postgres"]} thiserror = "2.0.18" [features] sqlx = ["dep:sqlx"] +reqwest = ["dep:reqwest"] diff --git a/common/src/records/assignment.rs b/common/src/records/assignment.rs index 6389c0f..252437f 100644 --- a/common/src/records/assignment.rs +++ b/common/src/records/assignment.rs @@ -19,12 +19,18 @@ record_impl! { } AssignmentFilter { - "task_id = $1 IS NOT FALSE" - task_id: Id, - "user_id = $2 IS NOT FALSE" - user_id: Id, - "state = $3 IS NOT FALSE" - state: AssignmentState, + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::timestamptz[] IS NULL OR array_position($3, deadline_at) IS NOT NULL" + deadline_at: Vec, + "$4::int8[] IS NULL OR array_position($4, task_id) IS NOT NULL" + task_id: Vec>, + "$5::int8[] IS NULL OR array_position($5, user_id) IS NOT NULL" + user_id: Vec>, + "$6::assignment_state[] IS NULL OR array_position($6, state) IS NOT NULL" + state: Vec, } AssignmentBuilder { diff --git a/common/src/records/file.rs b/common/src/records/file.rs index b2a394b..93e5522 100644 --- a/common/src/records/file.rs +++ b/common/src/records/file.rs @@ -13,7 +13,16 @@ record_impl! { hash: Vec, } - FileFilter {} + FileFilter { + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::text[] IS NULL OR array_position($3, url) IS NOT NULL" + url: Vec, + "$4::bytea[] IS NULL OR array_position($4, hash) IS NOT NULL" + hash: Vec>, + } FileBuilder { "url" "$1" diff --git a/common/src/records/mod.rs b/common/src/records/mod.rs index 49cf4ac..7d52ecc 100644 --- a/common/src/records/mod.rs +++ b/common/src/records/mod.rs @@ -36,6 +36,14 @@ pub trait Record: Sized { const PATH: &str; } +#[cfg(feature = "reqwest")] +pub trait Get { + type Ok: serde::de::DeserializeOwned; + type Err: serde::de::DeserializeOwned; + + fn get(&self, client: &reqwest::Client, url: &str) -> reqwest::RequestBuilder; +} + #[cfg(feature = "sqlx")] pub trait Select { type Record; @@ -52,7 +60,7 @@ pub trait Insert { macro_rules! record_impl { ( - PATH = $path_literal:literal; + PATH = $table_name_literal:literal; $record_ident:ident { $($record_field_ident:ident: $record_field_ty:ty,)* @@ -80,6 +88,7 @@ macro_rules! record_impl { $($update_fn_ident:ident($update_fn_name_literal:literal $update_fn_ty:ty);)* } ) => { + #[cfg(feature = "sqlx")] pub trait $update_ident { $(fn $update_fn_ident(&self, value: $update_fn_ty) -> $crate::records::sqlx::Query;)* } @@ -113,7 +122,27 @@ macro_rules! record_impl { impl $crate::records::Record for $record_ident { type Filter = $filter_ident; - const PATH: &str = $path_literal; + const PATH: &str = $table_name_literal; + } + + #[cfg(feature = "reqwest")] + impl $crate::records::Get for $filter_ident { + type Ok = Vec<$record_ident>; + type Err = $crate::errors::Infallible; + + fn get(&self, client: &::reqwest::Client, url: &str) -> ::reqwest::RequestBuilder { + client.get(format!("{}/{}", url, $table_name_literal)).query(self) + } + } + + #[cfg(feature = "reqwest")] + impl $crate::records::Get for $crate::types::Id<$record_ident> { + type Ok = $record_ident; + type Err = $crate::errors::NotFound; + + fn get(&self, client: &::reqwest::Client, url: &str) -> ::reqwest::RequestBuilder { + client.get(format!("{}/{}/{}", url, $table_name_literal, self)) + } } #[cfg(feature = "sqlx")] @@ -123,7 +152,7 @@ macro_rules! record_impl { fn select(&self) -> $crate::records::sqlx::Map { sqlx::query_as_unchecked!( Self::Record, - "SELECT * FROM " + $path_literal + " WHERE TRUE" $(+ " AND (" + $filter_field_condition_literal + ")")*, + "SELECT * FROM " + $table_name_literal + " WHERE TRUE" $(+ " AND (" + $filter_field_condition_literal + ")")*, $(self.$filter_field_ident,)* ) } @@ -136,7 +165,7 @@ macro_rules! record_impl { fn select(&self) -> $crate::records::sqlx::Map { sqlx::query_as_unchecked!( Self::Record, - "SELECT * FROM " + $path_literal + " WHERE id = $1", + "SELECT * FROM " + $table_name_literal + " WHERE id = $1", self ) } @@ -149,7 +178,7 @@ macro_rules! record_impl { fn select(&self) -> $crate::records::sqlx::Map { sqlx::query_as_unchecked!( Self::Record, - "SELECT * FROM " + $path_literal + " WHERE id = ANY($1)", + "SELECT * FROM " + $table_name_literal + " WHERE id = ANY($1)", self ) } @@ -161,18 +190,19 @@ macro_rules! record_impl { fn insert(&self) -> $crate::records::sqlx::QueryScalar<$crate::types::Id> { sqlx::query_scalar_unchecked!( - "INSERT INTO " + $path_literal + " (" + $builder_first_field_name_literal $(+ ", " + $builder_field_name_literal)* + ") VALUES (" + $builder_first_field_expression_literal $(+ ", " + $builder_field_expression_literal)* + ") RETURNING id \"id: _\"", + "INSERT INTO " + $table_name_literal + " (" + $builder_first_field_name_literal $(+ ", " + $builder_field_name_literal)* + ") VALUES (" + $builder_first_field_expression_literal $(+ ", " + $builder_field_expression_literal)* + ") RETURNING id \"id: _\"", self.$builder_first_field_ident, $(self.$builder_field_ident,)* ) } } + #[cfg(feature = "sqlx")] impl $update_ident for $crate::types::Id<$record_ident> { $( fn $update_fn_ident(&self, value: $update_fn_ty) -> $crate::records::sqlx::Query { sqlx::query_unchecked!( - "UPDATE " + $path_literal + " SET " + $update_fn_name_literal + " = $2 WHERE id = $1", + "UPDATE " + $table_name_literal + " SET " + $update_fn_name_literal + " = $2 WHERE id = $1", self, value, ) @@ -180,11 +210,12 @@ macro_rules! record_impl { )* } + #[cfg(feature = "sqlx")] impl $update_ident for [$crate::types::Id<$record_ident>] { $( fn $update_fn_ident(&self, value: $update_fn_ty) -> $crate::records::sqlx::Query { sqlx::query_unchecked!( - "UPDATE " + $path_literal + " SET " + $update_fn_name_literal + " = $2 WHERE id = ANY($1)", + "UPDATE " + $table_name_literal + " SET " + $update_fn_name_literal + " = $2 WHERE id = ANY($1)", self, value, ) diff --git a/common/src/records/platform.rs b/common/src/records/platform.rs index 5c67699..cfe254d 100644 --- a/common/src/records/platform.rs +++ b/common/src/records/platform.rs @@ -17,8 +17,14 @@ record_impl! { } PlatformFilter { - "file_id = $1 IS NOT FALSE" - file_id: Id, + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::text[] IS NULL OR array_position($3, name) IS NOT NULL" + name: Vec, + "$4::int8[] IS NULL OR array_position($4, file_id) IS NOT NULL" + file_id: Vec>, } PlatformBuilder { diff --git a/common/src/records/project.rs b/common/src/records/project.rs index 3f9fb1b..229f8ae 100644 --- a/common/src/records/project.rs +++ b/common/src/records/project.rs @@ -18,10 +18,16 @@ record_impl! { } ProjectFilter { - "created_by_user_id = $1 IS NOT FALSE" - created_by_user_id: Id, - "disabled_at IS NULL IS DISTINCT FROM $2" - disabled: bool, + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::int8[] IS NULL OR array_position($3, created_by_user_id) IS NOT NULL" + created_by_user_id: Vec>, + "$4::timestamptz[] IS NULL OR array_position($4, disabled_at) IS NOT NULL" + disabled_at: Vec>>, + "$5::text[] IS NULL OR array_position($5, name) IS NOT NULL" + name: Vec, } ProjectBuilder { diff --git a/common/src/records/project_version.rs b/common/src/records/project_version.rs index f2fc231..c407620 100644 --- a/common/src/records/project_version.rs +++ b/common/src/records/project_version.rs @@ -19,14 +19,18 @@ record_impl! { } ProjectVersionFilter { - "disabled_at IS NULL IS DISTINCT FROM $1" - disabled: bool, - "project_id = $2 IS NOT FALSE" - project_id: Id, - "platform_id = $3 IS NOT FALSE" - platform_id: Id, - "file_id = $4 IS NOT FALSE" - file_id: Id, + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::timestamptz[] IS NULL OR array_position($3, disabled_at) IS NOT NULL" + disabled_at: Vec>>, + "$4::int8[] IS NULL OR array_position($4, project_id) IS NOT NULL" + project_id: Vec>, + "$5::int8[] IS NULL OR array_position($5, platform_id) IS NOT NULL" + platform_id: Vec>, + "$6::int8[] IS NULL OR array_position($6, file_id) IS NOT NULL" + file_id: Vec>, } ProjectVersionBuilder { diff --git a/common/src/records/result.rs b/common/src/records/result.rs index e86beba..e3964e0 100644 --- a/common/src/records/result.rs +++ b/common/src/records/result.rs @@ -21,12 +21,22 @@ record_impl! { } ResultFilter { - "assignment_id = $1 IS NOT FALSE" - assignment_id: Id, - "group_result_id = $2 OR $2 IS NULL" - group_result_id: Id, - "state = $3 IS NOT FALSE" - state: ResultState, + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::int8[] IS NULL OR array_position($3, assignment_id) IS NOT NULL" + assignment_id: Vec>, + "$4::text[] IS NULL OR array_position($4, stdout) IS NOT NULL" + stdout: Vec, + "$5::text[] IS NULL OR array_position($5, stderr) IS NOT NULL" + stderr: Vec, + "$6::int4[] IS NULL OR array_position($6, exit_code) IS NOT NULL" + exit_code: Vec>, + "$7::int8[] IS NULL OR array_position($7, group_result_id) IS NOT NULL" + group_result_id: Vec>>, + "$8::result_state[] IS NULL OR array_position($8, state) IS NOT NULL" + state: Vec, } ResultBuilder { diff --git a/common/src/records/task.rs b/common/src/records/task.rs index 8754a74..7d88475 100644 --- a/common/src/records/task.rs +++ b/common/src/records/task.rs @@ -21,8 +21,20 @@ record_impl! { } TaskFilter { - "project_id = $1 IS NOT FALSE" - project_id: Id, + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::interval[] IS NULL OR array_position($3, deadline) IS NOT NULL" + deadline: Vec, + "$4::int8[] IS NULL OR array_position($4, project_id) IS NOT NULL" + project_id: Vec>, + "$5::text[] IS NULL OR array_position($5, stdin) IS NOT NULL" + stdin: Vec, + "$6::int4[] IS NULL OR array_position($6, assignments_needed) IS NOT NULL" + assignments_needed: Vec, + "$7::int8[] IS NULL OR array_position($7, quorum) IS NOT NULL" + quorum: Vec, } TaskBuilder { diff --git a/common/src/records/user.rs b/common/src/records/user.rs index 4c21256..dcf6620 100644 --- a/common/src/records/user.rs +++ b/common/src/records/user.rs @@ -14,8 +14,14 @@ record_impl! { } UserFilter { - "disabled_at IS NULL IS DISTINCT FROM $1" - disabled: bool, + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::timestamptz[] IS NULL OR array_position($3, disabled_at) IS NOT NULL" + disabled_at: Vec>>, + "$4::text[] IS NULL OR array_position($4, name) IS NOT NULL" + name: Vec, } UserBuilder { From b97f5f75c27762fd95206374d030e39953820ad7 Mon Sep 17 00:00:00 2001 From: Chen Steenvoorden Date: Sat, 11 Apr 2026 21:05:53 +0200 Subject: [PATCH 4/7] Fix query string issues --- Cargo.lock | 15 +++++++++++++++ common/Cargo.toml | 3 ++- common/src/records/mod.rs | 6 +++++- server/Cargo.toml | 1 + server/src/routes/mod.rs | 5 +++-- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c4aba2..29989c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,6 +412,7 @@ dependencies = [ "chrono", "reqwest", "serde", + "serde_qs", "sqlx", "thiserror 2.0.18", ] @@ -427,6 +428,7 @@ dependencies = [ "dotenvy", "hmac 0.13.0", "serde", + "serde_qs", "sha2 0.11.0", "sqlx", "tokio", @@ -2249,6 +2251,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_qs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2316d01592c3382277c5062105510e35e0a6bfb2851e30028485f7af8cf1240" +dependencies = [ + "axum", + "itoa", + "percent-encoding", + "ryu", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" diff --git a/common/Cargo.toml b/common/Cargo.toml index 1c391d9..b7b98b7 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -7,9 +7,10 @@ edition = "2024" chrono = { version = "0.4.44", features = ["serde"] } reqwest = { version = "0.13.2", optional = true, features = ["query"] } serde = { version = "1.0.228", features = ["derive"] } +serde_qs = { version = "1.1.1", optional = true } sqlx = { version = "0.8.6", optional = true, features = ["postgres"]} thiserror = "2.0.18" [features] sqlx = ["dep:sqlx"] -reqwest = ["dep:reqwest"] +reqwest = ["dep:reqwest", "dep:serde_qs"] diff --git a/common/src/records/mod.rs b/common/src/records/mod.rs index 7d52ecc..32db6c2 100644 --- a/common/src/records/mod.rs +++ b/common/src/records/mod.rs @@ -131,7 +131,11 @@ macro_rules! record_impl { type Err = $crate::errors::Infallible; fn get(&self, client: &::reqwest::Client, url: &str) -> ::reqwest::RequestBuilder { - client.get(format!("{}/{}", url, $table_name_literal)).query(self) + let url = format!("{}/{}", url, $table_name_literal); + let mut url = ::reqwest::Url::parse(&url).unwrap(); + let query = ::serde_qs::to_string(self).unwrap(); + url.set_query(Some(&query)); + client.get(url) } } diff --git a/server/Cargo.toml b/server/Cargo.toml index c875c6c..c180f8a 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,6 +11,7 @@ clusterizer-common = { version = "0.1.0", path = "../common", features = ["sqlx" dotenvy = "0.15.7" hmac = "0.13.0" serde = "1.0.228" +serde_qs = { version = "1.1.1", features = ["axum"] } sha2 = "0.11.0" sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "chrono"] } tokio = { version = "1.50.0", features = ["full"] } diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 3b59dd1..133dad7 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -1,12 +1,13 @@ use axum::{ Json, - extract::{Path, Query, State}, + extract::{Path, State}, }; use clusterizer_common::{ errors::{Infallible, NotFound}, records::{Record, Select}, types::Id, }; +use serde_qs::web::QsQuery; use crate::{ result::{AppResult, ResultExt}, @@ -27,7 +28,7 @@ pub use validate_submit::validate_submit; pub async fn get_all( State(state): State, - Query(filter): Query, + QsQuery(filter): QsQuery, ) -> AppResult>, Infallible> where T::Filter: Select, From e270010e349c0a63e977f4bee74e5bd8aed8a97d Mon Sep 17 00:00:00 2001 From: Chen Steenvoorden Date: Thu, 2 Apr 2026 18:33:54 +0200 Subject: [PATCH 5/7] Rename project versions to project runners --- ...8855b02d886a95365c8b1a4a5ccfba1ca398.json} | 4 +-- ...9bacbea9998f02d2486c9365efc3a37a1cbb.json} | 4 +-- ...3ba6e084336fa9831babc36cd551e71288fd.json} | 4 +-- ...3d85842fe0e1fbef0c0fc8b63a3964442538.json} | 4 +-- cli/src/client.rs | 26 +++++++++---------- common/src/records/mod.rs | 4 +-- .../{project_version.rs => project_runner.rs} | 14 +++++----- server/migrations/20250426220809_init.sql | 2 +- server/src/main.rs | 4 +-- 9 files changed, 33 insertions(+), 33 deletions(-) rename .sqlx/{query-6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face.json => query-3040ca82a2a0bc2b828df2e89ed28855b02d886a95365c8b1a4a5ccfba1ca398.json} (62%) rename .sqlx/{query-59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8.json => query-781c42f7107328996a9eb4a88d8f9bacbea9998f02d2486c9365efc3a37a1cbb.json} (60%) rename .sqlx/{query-40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5.json => query-a406c9c198acece62a210855dcc73ba6e084336fa9831babc36cd551e71288fd.json} (85%) rename .sqlx/{query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json => query-d04ebea2c51ce254c8e49fad15683d85842fe0e1fbef0c0fc8b63a3964442538.json} (85%) rename common/src/records/{project_version.rs => project_runner.rs} (85%) diff --git a/.sqlx/query-6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face.json b/.sqlx/query-3040ca82a2a0bc2b828df2e89ed28855b02d886a95365c8b1a4a5ccfba1ca398.json similarity index 62% rename from .sqlx/query-6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face.json rename to .sqlx/query-3040ca82a2a0bc2b828df2e89ed28855b02d886a95365c8b1a4a5ccfba1ca398.json index e2f51df..418c399 100644 --- a/.sqlx/query-6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face.json +++ b/.sqlx/query-3040ca82a2a0bc2b828df2e89ed28855b02d886a95365c8b1a4a5ccfba1ca398.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM project_versions WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::timestamptz[] IS NULL OR array_position($3, disabled_at) IS NOT NULL) AND ($4::int8[] IS NULL OR array_position($4, project_id) IS NOT NULL) AND ($5::int8[] IS NULL OR array_position($5, platform_id) IS NOT NULL) AND ($6::int8[] IS NULL OR array_position($6, file_id) IS NOT NULL)", + "query": "SELECT * FROM project_runners WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::timestamptz[] IS NULL OR array_position($3, disabled_at) IS NOT NULL) AND ($4::int8[] IS NULL OR array_position($4, project_id) IS NOT NULL) AND ($5::int8[] IS NULL OR array_position($5, platform_id) IS NOT NULL) AND ($6::int8[] IS NULL OR array_position($6, file_id) IS NOT NULL)", "describe": { "columns": [ { @@ -53,5 +53,5 @@ false ] }, - "hash": "6575005fcd566adcd6f630ce06b15cfe718e153af822a2b6dfee60554202face" + "hash": "3040ca82a2a0bc2b828df2e89ed28855b02d886a95365c8b1a4a5ccfba1ca398" } diff --git a/.sqlx/query-59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8.json b/.sqlx/query-781c42f7107328996a9eb4a88d8f9bacbea9998f02d2486c9365efc3a37a1cbb.json similarity index 60% rename from .sqlx/query-59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8.json rename to .sqlx/query-781c42f7107328996a9eb4a88d8f9bacbea9998f02d2486c9365efc3a37a1cbb.json index a76923b..63f4037 100644 --- a/.sqlx/query-59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8.json +++ b/.sqlx/query-781c42f7107328996a9eb4a88d8f9bacbea9998f02d2486c9365efc3a37a1cbb.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO project_versions (project_id, platform_id, file_id) VALUES ($1, $2, $3) RETURNING id \"id: _\"", + "query": "INSERT INTO project_runners (project_id, platform_id, file_id) VALUES ($1, $2, $3) RETURNING id \"id: _\"", "describe": { "columns": [ { @@ -20,5 +20,5 @@ false ] }, - "hash": "59e05c1bca439b7fb68f8e262510ebb042fb163aa3b1de13e1749687f70e4be8" + "hash": "781c42f7107328996a9eb4a88d8f9bacbea9998f02d2486c9365efc3a37a1cbb" } diff --git a/.sqlx/query-40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5.json b/.sqlx/query-a406c9c198acece62a210855dcc73ba6e084336fa9831babc36cd551e71288fd.json similarity index 85% rename from .sqlx/query-40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5.json rename to .sqlx/query-a406c9c198acece62a210855dcc73ba6e084336fa9831babc36cd551e71288fd.json index a7d616c..e7b8c42 100644 --- a/.sqlx/query-40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5.json +++ b/.sqlx/query-a406c9c198acece62a210855dcc73ba6e084336fa9831babc36cd551e71288fd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM project_versions WHERE id = ANY($1)", + "query": "SELECT * FROM project_runners WHERE id = ANY($1)", "describe": { "columns": [ { @@ -48,5 +48,5 @@ false ] }, - "hash": "40fa22a7baa364bba68d97c54eb1e8c4fadebc4a10f66ad0682469cacab60ff5" + "hash": "a406c9c198acece62a210855dcc73ba6e084336fa9831babc36cd551e71288fd" } diff --git a/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json b/.sqlx/query-d04ebea2c51ce254c8e49fad15683d85842fe0e1fbef0c0fc8b63a3964442538.json similarity index 85% rename from .sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json rename to .sqlx/query-d04ebea2c51ce254c8e49fad15683d85842fe0e1fbef0c0fc8b63a3964442538.json index 1b32efe..6a397dc 100644 --- a/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json +++ b/.sqlx/query-d04ebea2c51ce254c8e49fad15683d85842fe0e1fbef0c0fc8b63a3964442538.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM project_versions WHERE id = $1", + "query": "SELECT * FROM project_runners WHERE id = $1", "describe": { "columns": [ { @@ -48,5 +48,5 @@ false ] }, - "hash": "60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a" + "hash": "d04ebea2c51ce254c8e49fad15683d85842fe0e1fbef0c0fc8b63a3964442538" } diff --git a/cli/src/client.rs b/cli/src/client.rs index a31e727..61406e2 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -16,8 +16,8 @@ use clusterizer_client::result::ClientResult; use clusterizer_common::{ errors::SubmitResultError, records::{ - File, FileFilter, Platform, PlatformFilter, Project, ProjectFilter, ProjectVersion, - ProjectVersionFilter, Task, + File, FileFilter, Platform, PlatformFilter, Project, ProjectFilter, ProjectRunner, + ProjectRunnerFilter, Task, }, requests::{FetchTasksRequest, SubmitResultRequest}, types::Id, @@ -38,7 +38,7 @@ struct ClusterizerClient { struct TaskInfo { task: Task, project: Project, - project_version: ProjectVersion, + project_runner: ProjectRunner, file: File, } @@ -94,13 +94,13 @@ impl ClusterizerClient { async fn fetch_tasks(self: Arc) -> ClientResult { let tasks = loop { - let project_versions_by_project_id: HashMap<_, _> = self + let project_runners_by_project_id: HashMap<_, _> = self .client - .get(&ProjectVersionFilter::default().disabled_at(vec![None])) + .get(&ProjectRunnerFilter::default().disabled_at(vec![None])) .await? .into_iter() - .filter(|project_version| self.platform_ids.contains(&project_version.platform_id)) - .map(|project_version| (project_version.project_id, project_version)) + .filter(|project_runner| self.platform_ids.contains(&project_runner.platform_id)) + .map(|project_runner| (project_runner.project_id, project_runner)) .collect(); let projects_by_project_id: HashMap<_, _> = self @@ -108,7 +108,7 @@ impl ClusterizerClient { .get(&ProjectFilter::default().disabled_at(vec![None])) .await? .into_iter() - .filter(|project| project_versions_by_project_id.contains_key(&project.id)) + .filter(|project| project_runners_by_project_id.contains_key(&project.id)) .map(|project| (project.id, project)) .collect(); @@ -122,14 +122,14 @@ impl ClusterizerClient { let get_task_info = |task: &Task| { let project = projects_by_project_id.get(&task.project_id)?; - let project_version = project_versions_by_project_id.get(&task.project_id)?; - let file = files_by_file_id.get(&project_version.file_id)?; + let project_runner = project_runners_by_project_id.get(&task.project_id)?; + let file = files_by_file_id.get(&project_runner.file_id)?; Some(TaskInfo { task: task.clone(), file: file.clone(), project: project.clone(), - project_version: project_version.clone(), + project_runner: project_runner.clone(), }) }; @@ -171,7 +171,7 @@ impl ClusterizerClient { self: Arc, TaskInfo { task, - project_version, + project_runner, project, file, }: TaskInfo, @@ -183,7 +183,7 @@ impl ClusterizerClient { "Project id: {}, Project name: {}", task.project_id, project.name ); - debug!("Platform id: {}", project_version.platform_id); + debug!("Platform id: {}", project_runner.platform_id); debug!("Slot dir: {}", slot_dir.path().display()); let program = self diff --git a/common/src/records/mod.rs b/common/src/records/mod.rs index 32db6c2..d9cb99e 100644 --- a/common/src/records/mod.rs +++ b/common/src/records/mod.rs @@ -2,7 +2,7 @@ pub mod assignment; pub mod file; pub mod platform; pub mod project; -pub mod project_version; +pub mod project_runner; pub mod result; pub mod task; pub mod user; @@ -11,7 +11,7 @@ pub use assignment::{Assignment, AssignmentBuilder, AssignmentFilter}; pub use file::{File, FileBuilder, FileFilter}; pub use platform::{Platform, PlatformBuilder, PlatformFilter}; pub use project::{Project, ProjectBuilder, ProjectFilter}; -pub use project_version::{ProjectVersion, ProjectVersionBuilder, ProjectVersionFilter}; +pub use project_runner::{ProjectRunner, ProjectRunnerBuilder, ProjectRunnerFilter}; pub use result::{Result, ResultBuilder, ResultFilter}; pub use task::{Task, TaskBuilder, TaskFilter}; pub use user::{User, UserBuilder, UserFilter}; diff --git a/common/src/records/project_version.rs b/common/src/records/project_runner.rs similarity index 85% rename from common/src/records/project_version.rs rename to common/src/records/project_runner.rs index c407620..7b4909d 100644 --- a/common/src/records/project_version.rs +++ b/common/src/records/project_runner.rs @@ -7,10 +7,10 @@ use crate::{ }; record_impl! { - PATH = "project_versions"; + PATH = "project_runners"; - ProjectVersion { - id: Id, + ProjectRunner { + id: Id, created_at: DateTime, disabled_at: Option>, project_id: Id, @@ -18,9 +18,9 @@ record_impl! { file_id: Id, } - ProjectVersionFilter { + ProjectRunnerFilter { "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" - id: Vec>, + id: Vec>, "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" created_at: Vec>, "$3::timestamptz[] IS NULL OR array_position($3, disabled_at) IS NOT NULL" @@ -33,7 +33,7 @@ record_impl! { file_id: Vec>, } - ProjectVersionBuilder { + ProjectRunnerBuilder { "project_id" "$1" project_id: Id, "platform_id" "$2" @@ -42,5 +42,5 @@ record_impl! { file_id: Id, } - UpdateProjectVersion {} + UpdateProjectRunner {} } diff --git a/server/migrations/20250426220809_init.sql b/server/migrations/20250426220809_init.sql index 8da7fb6..ba37f06 100644 --- a/server/migrations/20250426220809_init.sql +++ b/server/migrations/20250426220809_init.sql @@ -30,7 +30,7 @@ CREATE TABLE platforms ( file_id int8 NOT NULL REFERENCES files(id) ON DELETE RESTRICT ON UPDATE RESTRICT ); -CREATE TABLE project_versions ( +CREATE TABLE project_runners ( id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT now(), disabled_at timestamptz, diff --git a/server/src/main.rs b/server/src/main.rs index 0e51f0d..35de324 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -10,7 +10,7 @@ use axum::{ }; use clusterizer_common::{ records::{ - Assignment, File, Platform, Project, ProjectVersion, Record, Result, Select, Task, User, + Assignment, File, Platform, Project, ProjectRunner, Record, Result, Select, Task, User, }, types::Id, }; @@ -46,7 +46,7 @@ async fn serve_task(state: AppState, address: String) { .merge(record_router::()) .merge(record_router::()) .merge(record_router::()) - .merge(record_router::()) + .merge(record_router::()) .merge(record_router::()) .merge(record_router::()) .merge(record_router::()) From 5dfc6f5b9502c0d0e605661da90065d3825b6496 Mon Sep 17 00:00:00 2001 From: Chen Steenvoorden Date: Thu, 2 Apr 2026 19:47:52 +0200 Subject: [PATCH 6/7] Add platform runners api --- ...66fa2348409ba9c7a20cf18a7613bdfb0ae89.json | 43 +++++++++++++++++++ ...22bcbfa228d5f655c6f9cc783119e706a9206.json | 23 ++++++++++ ...1ca9164eb74493a155561b47263274d2b98f9.json | 40 +++++++++++++++++ ...150b5b5c7755c25c8e0dd2a759692d98aac1d.json | 40 +++++++++++++++++ common/src/records/mod.rs | 2 + common/src/records/platform_runner.rs | 38 ++++++++++++++++ server/migrations/20250426220809_init.sql | 7 +++ server/src/main.rs | 4 +- 8 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 .sqlx/query-1766e5a8871d12054550bceb42466fa2348409ba9c7a20cf18a7613bdfb0ae89.json create mode 100644 .sqlx/query-5f568cb0d5f215c590f1ac4881a22bcbfa228d5f655c6f9cc783119e706a9206.json create mode 100644 .sqlx/query-6bdc70e2c27f350648875584a1f1ca9164eb74493a155561b47263274d2b98f9.json create mode 100644 .sqlx/query-b2922b902d1e423d8928526ef7f150b5b5c7755c25c8e0dd2a759692d98aac1d.json create mode 100644 common/src/records/platform_runner.rs diff --git a/.sqlx/query-1766e5a8871d12054550bceb42466fa2348409ba9c7a20cf18a7613bdfb0ae89.json b/.sqlx/query-1766e5a8871d12054550bceb42466fa2348409ba9c7a20cf18a7613bdfb0ae89.json new file mode 100644 index 0000000..80dac1c --- /dev/null +++ b/.sqlx/query-1766e5a8871d12054550bceb42466fa2348409ba9c7a20cf18a7613bdfb0ae89.json @@ -0,0 +1,43 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM platform_runners WHERE TRUE AND ($1::int8[] IS NULL OR array_position($1, id) IS NOT NULL) AND ($2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL) AND ($3::int8[] IS NULL OR array_position($3, platform_id) IS NOT NULL) AND ($4::int8[] IS NULL OR array_position($4, file_id) IS NOT NULL)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "platform_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "file_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8Array", + "TimestamptzArray", + "Int8Array", + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "1766e5a8871d12054550bceb42466fa2348409ba9c7a20cf18a7613bdfb0ae89" +} diff --git a/.sqlx/query-5f568cb0d5f215c590f1ac4881a22bcbfa228d5f655c6f9cc783119e706a9206.json b/.sqlx/query-5f568cb0d5f215c590f1ac4881a22bcbfa228d5f655c6f9cc783119e706a9206.json new file mode 100644 index 0000000..c18ca48 --- /dev/null +++ b/.sqlx/query-5f568cb0d5f215c590f1ac4881a22bcbfa228d5f655c6f9cc783119e706a9206.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO platform_runners (platform_id, file_id) VALUES ($1, $2) RETURNING id \"id: _\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: _", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "5f568cb0d5f215c590f1ac4881a22bcbfa228d5f655c6f9cc783119e706a9206" +} diff --git a/.sqlx/query-6bdc70e2c27f350648875584a1f1ca9164eb74493a155561b47263274d2b98f9.json b/.sqlx/query-6bdc70e2c27f350648875584a1f1ca9164eb74493a155561b47263274d2b98f9.json new file mode 100644 index 0000000..aa8b3bb --- /dev/null +++ b/.sqlx/query-6bdc70e2c27f350648875584a1f1ca9164eb74493a155561b47263274d2b98f9.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM platform_runners WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "platform_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "file_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "6bdc70e2c27f350648875584a1f1ca9164eb74493a155561b47263274d2b98f9" +} diff --git a/.sqlx/query-b2922b902d1e423d8928526ef7f150b5b5c7755c25c8e0dd2a759692d98aac1d.json b/.sqlx/query-b2922b902d1e423d8928526ef7f150b5b5c7755c25c8e0dd2a759692d98aac1d.json new file mode 100644 index 0000000..1d0f6a6 --- /dev/null +++ b/.sqlx/query-b2922b902d1e423d8928526ef7f150b5b5c7755c25c8e0dd2a759692d98aac1d.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM platform_runners WHERE id = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "platform_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "file_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "b2922b902d1e423d8928526ef7f150b5b5c7755c25c8e0dd2a759692d98aac1d" +} diff --git a/common/src/records/mod.rs b/common/src/records/mod.rs index d9cb99e..3b7c06f 100644 --- a/common/src/records/mod.rs +++ b/common/src/records/mod.rs @@ -1,6 +1,7 @@ pub mod assignment; pub mod file; pub mod platform; +pub mod platform_runner; pub mod project; pub mod project_runner; pub mod result; @@ -10,6 +11,7 @@ pub mod user; pub use assignment::{Assignment, AssignmentBuilder, AssignmentFilter}; pub use file::{File, FileBuilder, FileFilter}; pub use platform::{Platform, PlatformBuilder, PlatformFilter}; +pub use platform_runner::{PlatformRunner, PlatformRunnerBuilder, PlatformRunnerFilter}; pub use project::{Project, ProjectBuilder, ProjectFilter}; pub use project_runner::{ProjectRunner, ProjectRunnerBuilder, ProjectRunnerFilter}; pub use result::{Result, ResultBuilder, ResultFilter}; diff --git a/common/src/records/platform_runner.rs b/common/src/records/platform_runner.rs new file mode 100644 index 0000000..243a31c --- /dev/null +++ b/common/src/records/platform_runner.rs @@ -0,0 +1,38 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::{ + records::{File, Platform, record_impl}, + types::Id, +}; + +record_impl! { + PATH = "platform_runners"; + + PlatformRunner { + id: Id, + created_at: DateTime, + platform_id: Id, + file_id: Id, + } + + PlatformRunnerFilter { + "$1::int8[] IS NULL OR array_position($1, id) IS NOT NULL" + id: Vec>, + "$2::timestamptz[] IS NULL OR array_position($2, created_at) IS NOT NULL" + created_at: Vec>, + "$3::int8[] IS NULL OR array_position($3, platform_id) IS NOT NULL" + platform_id: Vec>, + "$4::int8[] IS NULL OR array_position($4, file_id) IS NOT NULL" + file_id: Vec>, + } + + PlatformRunnerBuilder { + "platform_id" "$1" + platform_id: Id, + "file_id" "$2" + file_id: Id, + } + + UpdatePlatformRunner {} +} diff --git a/server/migrations/20250426220809_init.sql b/server/migrations/20250426220809_init.sql index ba37f06..fa267ef 100644 --- a/server/migrations/20250426220809_init.sql +++ b/server/migrations/20250426220809_init.sql @@ -39,6 +39,13 @@ CREATE TABLE project_runners ( file_id int8 NOT NULL REFERENCES files(id) ON DELETE RESTRICT ON UPDATE RESTRICT ); +CREATE TABLE platform_runners ( + id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY, + created_at timestamptz NOT NULL DEFAULT now(), + platform_id int8 NOT NULL REFERENCES platforms(id) ON DELETE RESTRICT ON UPDATE RESTRICT, + file_id int8 NOT NULL REFERENCES files(id) ON DELETE RESTRICT ON UPDATE RESTRICT +); + CREATE TABLE tasks ( id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT now(), diff --git a/server/src/main.rs b/server/src/main.rs index 35de324..3614ddb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -10,7 +10,8 @@ use axum::{ }; use clusterizer_common::{ records::{ - Assignment, File, Platform, Project, ProjectRunner, Record, Result, Select, Task, User, + Assignment, File, Platform, PlatformRunner, Project, ProjectRunner, Record, Result, Select, + Task, User, }, types::Id, }; @@ -47,6 +48,7 @@ async fn serve_task(state: AppState, address: String) { .merge(record_router::()) .merge(record_router::()) .merge(record_router::()) + .merge(record_router::()) .merge(record_router::()) .merge(record_router::()) .merge(record_router::()) From f02399ff0415f5c93d75334df7868180f3cac96a Mon Sep 17 00:00:00 2001 From: Chen Steenvoorden Date: Wed, 15 Apr 2026 02:48:47 +0200 Subject: [PATCH 7/7] Implement platform detection with runners --- Cargo.lock | 6 +- cli/Cargo.toml | 2 - cli/src/args.rs | 10 -- cli/src/client.rs | 208 ++++-------------------------- cli/src/main.rs | 9 +- client/Cargo.toml | 4 + client/src/lib.rs | 133 +++++++++++++++++++ client/src/supported_platforms.rs | 156 ++++++++++++++++++++++ 8 files changed, 331 insertions(+), 197 deletions(-) create mode 100644 client/src/supported_platforms.rs diff --git a/Cargo.lock b/Cargo.lock index 29989c7..bdea941 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,14 +384,12 @@ dependencies = [ "clusterizer-api", "clusterizer-client", "clusterizer-common", - "clusterizer-util", "dirs", "reqwest", "tempfile", "tokio", "tracing", "tracing-subscriber", - "zip", ] [[package]] @@ -399,9 +397,13 @@ name = "clusterizer-client" version = "0.1.0" dependencies = [ "clusterizer-api", + "clusterizer-common", + "clusterizer-util", "reqwest", + "tempfile", "thiserror 2.0.18", "tokio", + "tracing", "zip", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5b0543b..2491b3d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,11 +8,9 @@ clap = { version = "4.6.0", features = ["derive", "string"] } clusterizer-api = { version = "0.1.0", path = "../api" } clusterizer-client = { version = "0.1.0", path = "../client" } clusterizer-common = { version = "0.1.0", path = "../common" } -clusterizer-util = { version = "0.1.0", path = "../util" } dirs = "6.0.0" reqwest = { version = "0.13.2" } tempfile = "3.27.0" tokio = { version = "1.50.0", features = ["full"] } tracing = "0.1.44" tracing-subscriber = "0.3.23" -zip = "8.5.0" diff --git a/cli/src/args.rs b/cli/src/args.rs index 50d54d9..003d036 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -42,16 +42,6 @@ pub struct RunArgs { pub queue: usize, } -impl RunArgs { - pub fn binaries_dir(&self) -> PathBuf { - self.cache_dir.join("bin") - } - - pub fn temp_dir(&self) -> PathBuf { - self.cache_dir.join("tmp") - } -} - fn cache_dir() -> Resettable { dirs::cache_dir() .map(|path| path.join("clusterizer").into_os_string().into()) diff --git a/cli/src/client.rs b/cli/src/client.rs index 61406e2..7166d44 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -1,45 +1,24 @@ use std::{ - collections::{HashMap, VecDeque}, - env, - ffi::OsString, - fs, - io::{Cursor, ErrorKind}, - iter::{self, Empty}, - path::PathBuf, + collections::VecDeque, process::{Output, Stdio}, sync::Arc, time::Duration, }; use clusterizer_api::{client::ApiClient, result::ApiError}; -use clusterizer_client::result::ClientResult; +use clusterizer_client::{TaskInfo, result::ClientResult, supported_platforms::SupportedPlatforms}; use clusterizer_common::{ - errors::SubmitResultError, - records::{ - File, FileFilter, Platform, PlatformFilter, Project, ProjectFilter, ProjectRunner, - ProjectRunnerFilter, Task, - }, - requests::{FetchTasksRequest, SubmitResultRequest}, - types::Id, + errors::SubmitResultError, records::Task, requests::SubmitResultRequest, types::Id, }; -use clusterizer_util::Hex; -use tokio::{io::AsyncWriteExt, process::Command, task::JoinSet, time}; -use tracing::{debug, info, warn}; -use zip::ZipArchive; +use tokio::{io::AsyncWriteExt, task::JoinSet, time}; +use tracing::{debug, info}; use crate::args::RunArgs; struct ClusterizerClient { client: ApiClient, args: RunArgs, - platform_ids: Vec>, -} - -struct TaskInfo { - task: Task, - project: Project, - project_runner: ProjectRunner, - file: File, + supported_platforms: SupportedPlatforms, } enum Return { @@ -93,110 +72,43 @@ impl ClusterizerClient { } async fn fetch_tasks(self: Arc) -> ClientResult { - let tasks = loop { - let project_runners_by_project_id: HashMap<_, _> = self - .client - .get(&ProjectRunnerFilter::default().disabled_at(vec![None])) - .await? - .into_iter() - .filter(|project_runner| self.platform_ids.contains(&project_runner.platform_id)) - .map(|project_runner| (project_runner.project_id, project_runner)) - .collect(); - - let projects_by_project_id: HashMap<_, _> = self - .client - .get(&ProjectFilter::default().disabled_at(vec![None])) - .await? - .into_iter() - .filter(|project| project_runners_by_project_id.contains_key(&project.id)) - .map(|project| (project.id, project)) - .collect(); - - let files_by_file_id: HashMap<_, _> = self - .client - .get(&FileFilter::default()) - .await? - .into_iter() - .map(|file| (file.id, file)) - .collect(); - - let get_task_info = |task: &Task| { - let project = projects_by_project_id.get(&task.project_id)?; - let project_runner = project_runners_by_project_id.get(&task.project_id)?; - let file = files_by_file_id.get(&project_runner.file_id)?; - - Some(TaskInfo { - task: task.clone(), - file: file.clone(), - project: project.clone(), - project_runner: project_runner.clone(), - }) - }; - - let tasks: Vec<_> = self - .client - .fetch_tasks(&FetchTasksRequest { - project_ids: projects_by_project_id.keys().copied().collect(), - limit: self.args.threads, - }) - .await? - .into_iter() - .filter_map(|task| { - let info = get_task_info(&task); - - if info.is_none() { - warn!("Unwanted task received from server."); - } - - info - }) - .collect(); + loop { + let tasks = clusterizer_client::fetch_tasks( + &self.args.cache_dir, + &self.client, + &self.supported_platforms, + self.args.threads, + ) + .await?; if !tasks.is_empty() { - break tasks; + info!("Fetched {} tasks.", tasks.len()); + break Ok(Return::FetchTasks(tasks)); } info!("No tasks found. Sleeping before attempting again."); time::sleep(Duration::from_secs(15)).await; - }; - - for TaskInfo { file, .. } in &tasks { - download_archive(file, &self.args).await?; } - - Ok(Return::FetchTasks(tasks)) } async fn execute_task( self: Arc, TaskInfo { task, - project_runner, - project, - file, + file_path, + platform_id, }: TaskInfo, ) -> ClientResult { let slot_dir = tempfile::tempdir()?; - info!("Task id: {}, stdin: {}", task.id, task.stdin); - info!( - "Project id: {}, Project name: {}", - task.project_id, project.name - ); - debug!("Platform id: {}", project_runner.platform_id); + info!("Task id: {}", task.id); + debug!("Project id: {}", task.project_id); + debug!("Platform id: {}", platform_id); debug!("Slot dir: {}", slot_dir.path().display()); - let program = self - .args - .binaries_dir() - .join(format!("{}", Hex(&file.hash))) - .join(format!("main{}", env::consts::EXE_SUFFIX)) - .canonicalize()?; - - let args: Empty = iter::empty(); - - let mut child = Command::new(program) - .args(args) + let mut child = self + .supported_platforms + .get_command(&file_path, platform_id) .current_dir(&slot_dir) .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -235,81 +147,13 @@ impl ClusterizerClient { } pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { - fs::create_dir_all(args.binaries_dir())?; - fs::create_dir_all(args.temp_dir())?; - - let mut platform_ids = Vec::new(); - let mut platform_names = Vec::new(); - - for platform in client.get(&PlatformFilter::default()).await? { - let file = client.get(&platform.file_id).await?; - - debug!( - "Platform id: {}, tester archive url: {}", - platform.id, file.url - ); - - let platform_tester_dir = download_archive(&file, &args).await?; - - let slot_dir = tempfile::tempdir()?; - - debug!("Slot dir: {}", slot_dir.path().display()); - - let program = match platform_tester_dir - .join(format!("main{}", env::consts::EXE_SUFFIX)) - .canonicalize() - { - Err(err) if err.kind() == ErrorKind::NotFound => continue, - result => result, - }?; - - let args: Empty = iter::empty(); - - let status = Command::new(program) - .args(args) - .current_dir(&slot_dir) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .await?; - - if status.success() { - platform_ids.push(platform.id); - platform_names.push(platform.name); - } - } - - info!("Supported platforms: {}", platform_names.join(", ")); + let supported_platforms = SupportedPlatforms::detect(&args.cache_dir, &client).await?; Arc::new(ClusterizerClient { client, args, - platform_ids, + supported_platforms, }) .run() .await } - -async fn download_archive(file: &File, args: &RunArgs) -> ClientResult { - let dir = args.binaries_dir().join(format!("{}", Hex(&file.hash))); - - if dir.exists() { - debug!("Archive {} was cached.", dir.display()); - } else { - debug!("Archive {} is not cached.", dir.display()); - - let bytes = reqwest::get(&file.url) - .await? - .error_for_status()? - .bytes() - .await?; - - let extract_dir = tempfile::tempdir_in(args.temp_dir())?; - - ZipArchive::new(Cursor::new(bytes))?.extract(&extract_dir)?; - fs::rename(&extract_dir, &dir)?; - } - - Ok(dir) -} diff --git a/cli/src/main.rs b/cli/src/main.rs index e0b5599..acd97f4 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,3 +1,5 @@ +use std::fs; + use args::{ClusterizerArgs, Commands}; use clap::Parser; use clusterizer_api::client::ApiClient; @@ -31,7 +33,12 @@ async fn run() -> ClientResult<()> { println!("{}", response.api_key); } - Commands::Run(args) => client::run(client, args).await?, + Commands::Run(mut args) => { + fs::create_dir_all(&args.cache_dir)?; + args.cache_dir = args.cache_dir.canonicalize()?; + + client::run(client, args).await? + } } Ok(()) diff --git a/client/Cargo.toml b/client/Cargo.toml index f4c3912..accc06d 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,7 +5,11 @@ edition = "2024" [dependencies] clusterizer-api = { version = "0.1.0", path = "../api" } +clusterizer-common = { version = "0.1.0", path = "../common" } +clusterizer-util = { version = "0.1.0", path = "../util" } reqwest = { version = "0.13.2" } +tempfile = "3.27.0" thiserror = "2.0.18" tokio = { version = "1.50.0", features = ["full"] } +tracing = "0.1.44" zip = "8.5.0" diff --git a/client/src/lib.rs b/client/src/lib.rs index d4cfe2f..a01b423 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1 +1,134 @@ pub mod result; +pub mod supported_platforms; + +use std::{ + collections::{HashMap, HashSet}, + fs, + io::Cursor, + path::{Path, PathBuf}, +}; + +use clusterizer_api::client::ApiClient; +use clusterizer_common::{ + records::{File, FileFilter, Platform, ProjectRunnerFilter, Task}, + requests::FetchTasksRequest, + types::Id, +}; +use clusterizer_util::Hex; +use tokio::task::{self, JoinHandle}; +use tracing::{debug, info}; +use zip::ZipArchive; + +use crate::{result::ClientResult, supported_platforms::SupportedPlatforms}; + +#[derive(Debug)] +pub struct TaskInfo { + pub task: Task, + pub file_path: PathBuf, + pub platform_id: Id, +} + +pub async fn fetch_tasks( + cache_dir: &Path, + client: &ApiClient, + supported_platforms: &SupportedPlatforms, + limit: usize, +) -> ClientResult> { + let project_runners: HashMap<_, _> = client + .get( + &ProjectRunnerFilter::default() + .disabled_at(vec![None]) + .platform_id(supported_platforms.platform_ids().collect::>()), + ) + .await? + .into_iter() + .map(|project_runner| (project_runner.project_id, project_runner)) + .collect(); + + let tasks = client + .fetch_tasks(&FetchTasksRequest { + project_ids: project_runners.keys().copied().collect(), + limit, + }) + .await?; + + let project_ids: HashSet<_> = tasks.iter().map(|task| task.project_id).collect(); + + let files = download_archives( + cache_dir, + client, + project_ids + .into_iter() + // TODO: don't panic + .map(|project_id| project_runners[&project_id].file_id) + .collect(), + ) + .await?; + + Ok(tasks + .into_iter() + .map(|task| { + // TODO: don't panic + let project_runner = &project_runners[&task.project_id]; + + TaskInfo { + task, + // TODO: don't panic + file_path: files[&project_runner.file_id].clone(), + platform_id: project_runner.platform_id, + } + }) + .collect()) +} + +pub async fn download_archives( + cache_dir: &Path, + client: &ApiClient, + file_ids: Vec>, +) -> ClientResult, PathBuf>> { + let binaries_dir = cache_dir.join("bin"); + let temp_dir = cache_dir.join("tmp"); + + fs::create_dir_all(&binaries_dir)?; + fs::create_dir_all(&temp_dir)?; + + let tasks: Vec<_> = client + .get(&FileFilter::default().id(file_ids)) + .await? + .into_iter() + .map(|file| -> JoinHandle> { + let dir = binaries_dir.join(format!("{}", Hex(&file.hash))); + let temp_dir = temp_dir.clone(); + + task::spawn(async move { + if !dir.exists() { + info!("Downloading archive {}", file.url); + + let bytes = reqwest::get(&file.url) + .await? + .error_for_status()? + .bytes() + .await?; + + let extract_dir = tempfile::tempdir_in(temp_dir)?; + + ZipArchive::new(Cursor::new(bytes))?.extract(&extract_dir)?; + fs::rename(&extract_dir, &dir)?; + } else { + debug!("Archive {} was cached", file.url); + } + + Ok((file.id, dir)) + }) + }) + .collect(); + + let mut files = HashMap::new(); + + for task in tasks { + let (file_id, dir) = task.await??; + files.insert(file_id, dir); + } + + Ok(files) +} diff --git a/client/src/supported_platforms.rs b/client/src/supported_platforms.rs new file mode 100644 index 0000000..51b1b3a --- /dev/null +++ b/client/src/supported_platforms.rs @@ -0,0 +1,156 @@ +use std::{ + collections::HashMap, + env, + path::{Path, PathBuf}, + process::Stdio, +}; + +use clusterizer_api::client::ApiClient; +use clusterizer_common::{ + records::{Platform, PlatformFilter, PlatformRunnerFilter}, + types::Id, +}; +use tokio::{ + process::Command, + task::{self, JoinHandle}, +}; +use tracing::info; + +use crate::result::ClientResult; + +#[derive(Debug, Clone)] +enum PlatformStrategy { + Native, + Wrapper { + file_path: PathBuf, + platform_id: Id, + }, +} + +#[derive(Debug, Clone)] +pub struct SupportedPlatforms { + platform_strategies: HashMap, PlatformStrategy>, +} + +impl SupportedPlatforms { + fn new() -> Self { + Self { + platform_strategies: HashMap::new(), + } + } + + fn insert(&mut self, platform_id: Id, strategy: PlatformStrategy) { + self.platform_strategies.insert(platform_id, strategy); + } + + pub fn platform_ids(&self) -> impl Iterator> { + self.platform_strategies.keys().copied() + } + + pub fn get_command(&self, file_path: &Path, platform_id: Id) -> Command { + self.get_command_with_strategy(file_path, &self.platform_strategies[&platform_id]) + } + + fn get_command_with_strategy(&self, file_path: &Path, strategy: &PlatformStrategy) -> Command { + match strategy { + PlatformStrategy::Native => { + Command::new(file_path.join(format!("main{}", env::consts::EXE_SUFFIX))) + } + PlatformStrategy::Wrapper { + file_path: wrapper_file_path, + platform_id: wrapper_platform_id, + } => { + let mut command = self.get_command(wrapper_file_path, *wrapper_platform_id); + command.arg(file_path); + command + } + } + } + + pub async fn detect(cache_dir: &Path, client: &ApiClient) -> ClientResult { + let mut strategy_queue = vec![PlatformStrategy::Native]; + let mut supported_platforms = Self::new(); + let mut platforms = client.get(&PlatformFilter::default()).await?; + + let files = crate::download_archives( + cache_dir, + client, + platforms.iter().map(|platform| platform.file_id).collect(), + ) + .await?; + + while !strategy_queue.is_empty() { + let tasks: Vec<_> = platforms + .drain(..) + .map(|platform| -> JoinHandle> { + let strategy_queue = strategy_queue.clone(); + let supported_platforms = supported_platforms.clone(); + let files = files.clone(); + + task::spawn(async move { + for strategy in strategy_queue { + let slot_dir = tempfile::tempdir()?; + + if supported_platforms + // TODO: don't panic + .get_command_with_strategy(&files[&platform.file_id], &strategy) + .current_dir(&slot_dir) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await + .is_ok_and(|status| status.success()) + { + return Ok((platform, Some(strategy))); + } + } + + Ok((platform, None)) + }) + }) + .collect(); + + let mut new_platforms = Vec::new(); + + for task in tasks { + let (platform, strategy) = task.await??; + + if let Some(strategy) = strategy { + info!("Supported platform: {}", platform.name); + supported_platforms.insert(platform.id, strategy); + new_platforms.push(platform.id); + } else { + platforms.push(platform); + } + } + + let new_platform_runners = client + .get(&PlatformRunnerFilter::default().platform_id(new_platforms)) + .await?; + + let mut files = crate::download_archives( + cache_dir, + client, + new_platform_runners + .iter() + .map(|platform_runner| platform_runner.file_id) + .collect(), + ) + .await?; + + strategy_queue.splice( + .., + new_platform_runners + .into_iter() + .map(|platform_runner| PlatformStrategy::Wrapper { + // TODO: don't panic + file_path: files.remove(&platform_runner.file_id).unwrap(), + platform_id: platform_runner.platform_id, + }), + ); + } + + Ok(supported_platforms) + } +}