diff --git a/Cargo.lock b/Cargo.lock index 1c29e6e..2e9bb8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,6 +384,7 @@ dependencies = [ "clusterizer-api", "clusterizer-client", "clusterizer-common", + "clusterizer-util", "dirs", "reqwest", "tempfile", @@ -433,6 +434,10 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "clusterizer-util" +version = "0.1.0" + [[package]] name = "cmake" version = "0.1.58" diff --git a/Cargo.toml b/Cargo.toml index 24bbbeb..0b6c6a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["api", "cli", "client", "common", "server"] +members = ["api", "cli", "client", "common", "server", "util"] diff --git a/api/src/get.rs b/api/src/get.rs index be0e29c..599999b 100644 --- a/api/src/get.rs +++ b/api/src/get.rs @@ -1,6 +1,7 @@ use clusterizer_common::records::{ - Assignment, AssignmentFilter, Platform, PlatformFilter, Project, ProjectFilter, ProjectVersion, - ProjectVersionFilter, Result, ResultFilter, Task, TaskFilter, User, UserFilter, + Assignment, AssignmentFilter, File, FileFilter, Platform, PlatformFilter, Project, + ProjectFilter, ProjectVersion, ProjectVersionFilter, Result, ResultFilter, Task, TaskFilter, + User, UserFilter, }; pub trait Get { @@ -33,6 +34,12 @@ impl Get for ProjectVersion { const PATH: &str = "project_versions"; } +impl Get for File { + type Filter = FileFilter; + + const PATH: &str = "files"; +} + impl Get for Task { type Filter = TaskFilter; diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b0a448f..5b0543b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,6 +8,7 @@ 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" diff --git a/cli/src/args.rs b/cli/src/args.rs index a23614b..50d54d9 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -43,12 +43,12 @@ pub struct RunArgs { } impl RunArgs { - pub fn project_versions_dir(&self) -> PathBuf { - self.cache_dir.join("project_versions") + pub fn binaries_dir(&self) -> PathBuf { + self.cache_dir.join("bin") } - pub fn platform_testers_dir(&self) -> PathBuf { - self.cache_dir.join("platform_testers") + pub fn temp_dir(&self) -> PathBuf { + self.cache_dir.join("tmp") } } diff --git a/cli/src/client.rs b/cli/src/client.rs index e4d7d9a..f422aaf 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -5,7 +5,7 @@ use std::{ fs, io::{Cursor, ErrorKind}, iter::{self, Empty}, - path::Path, + path::PathBuf, process::{Output, Stdio}, sync::Arc, time::Duration, @@ -16,12 +16,13 @@ use clusterizer_client::result::ClientResult; use clusterizer_common::{ errors::SubmitResultError, records::{ - Platform, PlatformFilter, Project, ProjectFilter, ProjectVersion, ProjectVersionFilter, - Task, + File, FileFilter, Platform, PlatformFilter, Project, ProjectFilter, ProjectVersion, + ProjectVersionFilter, Task, }, requests::{FetchTasksRequest, SubmitResultRequest}, types::Id, }; +use clusterizer_util::Hex; use tokio::{io::AsyncWriteExt, process::Command, task::JoinSet, time}; use tracing::{debug, info, warn}; use zip::ZipArchive; @@ -38,6 +39,7 @@ struct TaskInfo { task: Task, project: Project, project_version: ProjectVersion, + file: File, } enum Return { @@ -92,43 +94,55 @@ impl ClusterizerClient { async fn fetch_tasks(self: Arc) -> ClientResult { let tasks = loop { - let mut projects: HashMap<_, _> = self + let project_versions_by_project_id: HashMap<_, _> = self .client - .get_all::(&ProjectFilter::default()) + .get_all::(&ProjectVersionFilter::default().disabled(false)) + .await? + .into_iter() + .filter(|project_version| self.platform_ids.contains(&project_version.platform_id)) + .map(|project_version| (project_version.project_id, project_version)) + .collect(); + + let projects_by_project_id: HashMap<_, _> = self + .client + .get_all::(&ProjectFilter::default().disabled(false)) .await? .into_iter() + .filter(|project| project_versions_by_project_id.contains_key(&project.id)) .map(|project| (project.id, project)) .collect(); - let projects: HashMap<_, _> = self + let files_by_file_id: HashMap<_, _> = self .client - .get_all::(&ProjectVersionFilter::default().disabled(false)) + .get_all::(&FileFilter::default()) .await? .into_iter() - .filter(|project_version| self.platform_ids.contains(&project_version.platform_id)) - .filter_map(|project_version| { - projects - .remove(&project_version.project_id) - .map(|project| (project.id, (project, project_version))) - }) + .map(|file| (file.id, file)) .collect(); + 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)?; + + Some(TaskInfo { + task: task.clone(), + file: file.clone(), + project: project.clone(), + project_version: project_version.clone(), + }) + }; + let tasks: Vec<_> = self .client .fetch_tasks(&FetchTasksRequest { - project_ids: projects.keys().copied().collect(), + project_ids: projects_by_project_id.keys().copied().collect(), limit: self.args.threads, }) .await? .into_iter() .filter_map(|task| { - let info = projects - .get(&task.project_id) - .map(|(project, project_version)| TaskInfo { - task, - project: project.clone(), - project_version: project_version.clone(), - }); + let info = get_task_info(&task); if info.is_none() { warn!("Unwanted task received from server."); @@ -146,21 +160,8 @@ impl ClusterizerClient { time::sleep(Duration::from_millis(15000)).await; }; - for TaskInfo { - project_version, .. - } in &tasks - { - let project_version_dir = self - .args - .project_versions_dir() - .join(project_version.id.to_string()); - - download_archive( - &project_version.archive_url, - &project_version_dir, - &self.args.cache_dir, - ) - .await?; + for TaskInfo { file, .. } in &tasks { + download_archive(file, &self.args).await?; } Ok(Return::FetchTasks(tasks)) @@ -170,26 +171,25 @@ impl ClusterizerClient { self: Arc, TaskInfo { task, - project, project_version, + project, + file, }: TaskInfo, ) -> ClientResult { let slot_dir = tempfile::tempdir()?; info!("Task id: {}, stdin: {}", task.id, task.stdin); - info!("Project id: {}, name: {}", project.id, project.name); - debug!( - "Project version id: {}, archive url: {}", - project_version.id, project_version.archive_url + info!( + "Project id: {}, Project name: {}", + task.project_id, project.name ); + debug!("Platform id: {}", project_version.platform_id); debug!("Slot dir: {}", slot_dir.path().display()); - let project_version_dir = self + let program = self .args - .project_versions_dir() - .join(project_version.id.to_string()); - - let program = project_version_dir + .binaries_dir() + .join(format!("{}", Hex(&file.hash))) .join(format!("main{}", env::consts::EXE_SUFFIX)) .canonicalize()?; @@ -235,8 +235,8 @@ impl ClusterizerClient { } pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { - fs::create_dir_all(args.project_versions_dir())?; - fs::create_dir_all(args.platform_testers_dir())?; + 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(); @@ -245,19 +245,14 @@ pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { .get_all::(&PlatformFilter::default()) .await? { + let file = client.get_one(platform.file_id).await?; + debug!( "Platform id: {}, tester archive url: {}", - platform.id, platform.tester_archive_url + platform.id, file.url ); - let platform_tester_dir = args.platform_testers_dir().join(platform.id.to_string()); - - download_archive( - &platform.tester_archive_url, - &platform_tester_dir, - &args.cache_dir, - ) - .await?; + let platform_tester_dir = download_archive(&file, &args).await?; let slot_dir = tempfile::tempdir()?; @@ -299,18 +294,25 @@ pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { .await } -async fn download_archive(url: &str, dir: &Path, cache_dir: &Path) -> ClientResult<()> { +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(url).await?.error_for_status()?.bytes().await?; - let extract_dir = tempfile::tempdir_in(cache_dir)?; + 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)?; + fs::rename(&extract_dir, &dir)?; } - Ok(()) + Ok(dir) } diff --git a/common/src/records/file.rs b/common/src/records/file.rs new file mode 100644 index 0000000..1efe33f --- /dev/null +++ b/common/src/records/file.rs @@ -0,0 +1,16 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::Id; + +#[derive(Clone, Hash, Debug, Serialize, Deserialize)] +pub struct File { + pub id: Id, + pub created_at: DateTime, + pub url: String, + pub hash: Vec, +} + +#[non_exhaustive] +#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] +pub struct FileFilter {} diff --git a/common/src/records/mod.rs b/common/src/records/mod.rs index c04af48..d16c5f3 100644 --- a/common/src/records/mod.rs +++ b/common/src/records/mod.rs @@ -1,4 +1,5 @@ pub mod assignment; +pub mod file; pub mod platform; pub mod project; pub mod project_version; @@ -7,6 +8,7 @@ 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}; diff --git a/common/src/records/platform.rs b/common/src/records/platform.rs index c1ac995..81e1716 100644 --- a/common/src/records/platform.rs +++ b/common/src/records/platform.rs @@ -1,16 +1,25 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::Id; +use crate::{records::File, types::Id}; #[derive(Clone, Hash, Debug, Serialize, Deserialize)] pub struct Platform { pub id: Id, pub created_at: DateTime, pub name: String, - pub tester_archive_url: String, + pub file_id: Id, } #[non_exhaustive] #[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct PlatformFilter {} +pub struct PlatformFilter { + pub file_id: Option>, +} + +impl PlatformFilter { + pub fn file_id(mut self, file_id: Id) -> Self { + self.file_id = Some(file_id); + self + } +} diff --git a/common/src/records/project_version.rs b/common/src/records/project_version.rs index 54f509c..e724e8a 100644 --- a/common/src/records/project_version.rs +++ b/common/src/records/project_version.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::types::Id; -use super::{Platform, Project}; +use super::{File, Platform, Project}; #[derive(Clone, Hash, Debug, Serialize, Deserialize)] pub struct ProjectVersion { @@ -12,7 +12,7 @@ pub struct ProjectVersion { pub disabled_at: Option>, pub project_id: Id, pub platform_id: Id, - pub archive_url: String, + pub file_id: Id, } #[non_exhaustive] @@ -21,6 +21,7 @@ pub struct ProjectVersionFilter { pub disabled: Option, pub project_id: Option>, pub platform_id: Option>, + pub file_id: Option>, } impl ProjectVersionFilter { @@ -38,4 +39,8 @@ impl ProjectVersionFilter { self.platform_id = Some(platform_id); self } + pub fn file_id(mut self, file_id: Id) -> Self { + self.file_id = Some(file_id); + self + } } diff --git a/server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json b/server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json new file mode 100644 index 0000000..e4524c8 --- /dev/null +++ b/server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n *\n FROM\n platforms\n WHERE\n file_id = $1 IS NOT FALSE\n ", + "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": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881" +} diff --git a/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json b/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json index da9a4b6..1b32efe 100644 --- a/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json +++ b/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json @@ -30,8 +30,8 @@ }, { "ordinal": 5, - "name": "archive_url", - "type_info": "Text" + "name": "file_id", + "type_info": "Int8" } ], "parameters": { diff --git a/server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json b/server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json similarity index 81% rename from server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json rename to server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json index dd04b0a..8461a2f 100644 --- a/server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json +++ b/server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.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 ", + "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 ", "describe": { "columns": [ { @@ -30,14 +30,15 @@ }, { "ordinal": 5, - "name": "archive_url", - "type_info": "Text" + "name": "file_id", + "type_info": "Int8" } ], "parameters": { "Left": [ "Bool", "Int8", + "Int8", "Int8" ] }, @@ -50,5 +51,5 @@ false ] }, - "hash": "f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff" + "hash": "767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38" } diff --git a/server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json b/server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json similarity index 72% rename from server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json rename to server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json index 16e3dd1..4d6dbfb 100644 --- a/server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json +++ b/server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n platforms\n ", + "query": "\n SELECT\n *\n FROM\n files\n ", "describe": { "columns": [ { @@ -15,13 +15,13 @@ }, { "ordinal": 2, - "name": "name", + "name": "url", "type_info": "Text" }, { "ordinal": 3, - "name": "tester_archive_url", - "type_info": "Text" + "name": "hash", + "type_info": "Bytea" } ], "parameters": { @@ -34,5 +34,5 @@ false ] }, - "hash": "110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521" + "hash": "a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92" } diff --git a/server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json b/server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json new file mode 100644 index 0000000..5c85d36 --- /dev/null +++ b/server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM files WHERE id = $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": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1" +} diff --git a/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json b/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json index 5bd55d1..f788679 100644 --- a/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json +++ b/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json @@ -20,8 +20,8 @@ }, { "ordinal": 3, - "name": "tester_archive_url", - "type_info": "Text" + "name": "file_id", + "type_info": "Int8" } ], "parameters": { diff --git a/server/migrations/20250426220809_init.sql b/server/migrations/20250426220809_init.sql index 5a24c59..4fac810 100644 --- a/server/migrations/20250426220809_init.sql +++ b/server/migrations/20250426220809_init.sql @@ -16,11 +16,18 @@ CREATE TABLE projects ( name text NOT NULL ); +CREATE TABLE files ( + id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY, + created_at timestamptz NOT NULL DEFAULT now(), + url text NOT NULL, + hash bytea NOT NULL +); + CREATE TABLE platforms ( id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT now(), name text NOT NULL, - tester_archive_url text NOT NULL + file_id int8 NOT NULL REFERENCES files(id) ON DELETE RESTRICT ON UPDATE RESTRICT ); CREATE TABLE project_versions ( @@ -29,7 +36,7 @@ CREATE TABLE project_versions ( disabled_at timestamptz, project_id int8 NOT NULL REFERENCES projects(id) ON DELETE CASCADE ON UPDATE CASCADE, platform_id int8 NOT NULL REFERENCES platforms(id) ON DELETE RESTRICT ON UPDATE RESTRICT, - archive_url text NOT NULL + file_id int8 NOT NULL REFERENCES files(id) ON DELETE RESTRICT ON UPDATE RESTRICT ); CREATE TABLE tasks ( diff --git a/server/src/main.rs b/server/src/main.rs index 1aa9526..fd285bc 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -11,7 +11,7 @@ use axum::{ routing::{get, post}, }; use clusterizer_common::records::{ - Assignment, Platform, Project, ProjectVersion, Result, Task, User, + Assignment, File, Platform, Project, ProjectVersion, Result, Task, User, }; use routes::{get_all, get_one}; @@ -41,6 +41,8 @@ async fn main() { 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::)) diff --git a/server/src/util/select.rs b/server/src/util/select.rs index 7bb2ef9..5af08c0 100644 --- a/server/src/util/select.rs +++ b/server/src/util/select.rs @@ -1,8 +1,8 @@ use clusterizer_common::{ records::{ - Assignment, AssignmentFilter, Platform, PlatformFilter, Project, ProjectFilter, - ProjectVersion, ProjectVersionFilter, Result, ResultFilter, Task, TaskFilter, User, - UserFilter, + Assignment, AssignmentFilter, File, FileFilter, Platform, PlatformFilter, Project, + ProjectFilter, ProjectVersion, ProjectVersionFilter, Result, ResultFilter, Task, + TaskFilter, User, UserFilter, }, types::Id, }; @@ -67,7 +67,7 @@ impl Select for Project { impl Select for Platform { type Filter = PlatformFilter; - fn select_all(_: &Self::Filter) -> Map { + fn select_all(filter: &Self::Filter) -> Map { sqlx::query_as_unchecked!( Self, r#" @@ -75,7 +75,10 @@ impl Select for Platform { * FROM platforms + WHERE + file_id = $1 IS NOT FALSE "#, + filter.file_id, ) } @@ -99,10 +102,12 @@ impl Select for ProjectVersion { 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, ) } @@ -111,6 +116,26 @@ impl Select for ProjectVersion { } } +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; diff --git a/util/Cargo.toml b/util/Cargo.toml new file mode 100644 index 0000000..22462ae --- /dev/null +++ b/util/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "clusterizer-util" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/util/src/lib.rs b/util/src/lib.rs new file mode 100644 index 0000000..75d8265 --- /dev/null +++ b/util/src/lib.rs @@ -0,0 +1,14 @@ +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug)] +pub struct Hex<'a>(pub &'a [u8]); + +impl Display for Hex<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for byte in self.0 { + write!(f, "{byte:02x}")?; + } + + Ok(()) + } +}