Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions api/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use clusterizer_common::{
errors::{FetchTasksError, RegisterError, SubmitResultError},
records::{Get, Task},
requests::{FetchTasksRequest, RegisterRequest, SubmitResultRequest},
errors::{CreateFileError, FetchTasksError, RegisterError, SubmitResultError},
records::{File, Get, Task},
requests::{CreateFileRequest, FetchTasksRequest, RegisterRequest, SubmitResultRequest},
responses::RegisterResponse,
types::Id,
};
Expand Down Expand Up @@ -56,6 +56,14 @@ impl ApiClient {
Ok(())
}

pub async fn create_file(
&self,
request: &CreateFileRequest,
) -> ApiResult<Id<File>, CreateFileError> {
let url = format!("{}/files", self.url);
Ok(self.send_post(url, request).await?.json().await?)
}

async fn send_post<Error: DeserializeOwned>(
&self,
url: impl IntoUrl,
Expand Down
1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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" }
sha2 = "0.11.0"
tempfile = "3.27.0"
tokio = { version = "1.50.0", features = ["full"] }
tracing = "0.1.44"
Expand Down
8 changes: 8 additions & 0 deletions cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub enum Commands {
Register(RegisterArgs),
/// Start crunching on clusterizer
Run(RunArgs),
/// Create a new file on the server
CreateFile(CreateFileArgs),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -52,6 +54,12 @@ impl RunArgs {
}
}

#[derive(Debug, Args)]
pub struct CreateFileArgs {
#[arg(long, short)]
pub url: String,
}

fn cache_dir() -> Resettable<OsStr> {
dirs::cache_dir()
.map(|path| path.join("clusterizer").into_os_string().into())
Expand Down
24 changes: 22 additions & 2 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use args::{ClusterizerArgs, Commands};
use clap::Parser;
use clusterizer_api::client::ApiClient;
use clusterizer_client::result::ClientResult;
use clusterizer_common::requests::RegisterRequest;
use tracing::{debug, error};
use clusterizer_common::requests::{CreateFileRequest, RegisterRequest};
use sha2::{Digest, Sha256};
use tracing::{debug, error, info};

mod args;
mod client;
Expand Down Expand Up @@ -32,6 +33,25 @@ async fn run() -> ClientResult<()> {
println!("{}", response.api_key);
}
Commands::Run(args) => client::run(client, args).await?,
Commands::CreateFile(args) => {
debug!("Creating new file...");
let bytes = reqwest::get(&args.url)
.await?
.error_for_status()?
.bytes()
.await?;
let hash = Sha256::digest(bytes).0;

let response = client
.create_file(&CreateFileRequest {
url: args.url,
hash,
})
.await?;

println!("{}", response);
info!("Successfully created new file with ID: {}", response);
}
}

Ok(())
Expand Down
10 changes: 10 additions & 0 deletions common/src/errors/create_file_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Clone, Hash, Debug, Serialize, Deserialize, Error)]
pub enum CreateFileError {
#[error("forbidden")]
Forbidden,
#[error("url is invalid")]
InvalidUrl,
}
2 changes: 2 additions & 0 deletions common/src/errors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod create_file_error;
pub mod fetch_tasks_error;
pub mod infallible;
pub mod not_found;
Expand All @@ -6,6 +7,7 @@ pub mod submit_result_error;
pub mod validate_fetch_error;
pub mod validate_submit_error;

pub use create_file_error::CreateFileError;
pub use fetch_tasks_error::FetchTasksError;
pub use infallible::Infallible;
pub use not_found::NotFound;
Expand Down
6 changes: 3 additions & 3 deletions common/src/records/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ record_impl! {
id: Id<File>,
created_at: DateTime<Utc>,
url: String,
hash: Vec<u8>,
hash: [u8; 32],
}

FileFilter {
Expand All @@ -21,14 +21,14 @@ record_impl! {
"$3::text[] IS NULL OR array_position($3, url) IS NOT NULL"
url: Vec<String>,
"$4::bytea[] IS NULL OR array_position($4, hash) IS NOT NULL"
hash: Vec<Vec<u8>>,
hash: Vec<[u8; 32]>,
}

FileBuilder {
"url" "$1"
url: String,
"hash" "$2"
hash: Vec<u8>,
hash: [u8; 32],
}

UpdateFile {}
Expand Down
3 changes: 3 additions & 0 deletions common/src/records/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ record_impl! {
created_at: DateTime<Utc>,
disabled_at: Option<DateTime<Utc>>,
name: String,
is_admin: bool,
}

UserFilter {
Expand All @@ -22,6 +23,8 @@ record_impl! {
disabled_at: Vec<Option<DateTime<Utc>>>,
"$4::text[] IS NULL OR array_position($4, name) IS NOT NULL"
name: Vec<String>,
"$5::bool[] IS NULL OR array_position($5, is_admin) IS NOT NULL"
is_admin: bool,
}

UserBuilder {
Expand Down
7 changes: 7 additions & 0 deletions common/src/requests/create_file_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};

#[derive(Clone, Hash, Debug, Serialize, Deserialize)]
pub struct CreateFileRequest {
pub url: String,
pub hash: [u8; 32],
}
2 changes: 2 additions & 0 deletions common/src/requests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub mod create_file_request;
pub mod fetch_tasks_request;
pub mod register_request;
pub mod submit_result_request;
pub mod validate_submit_request;

pub use create_file_request::CreateFileRequest;
pub use fetch_tasks_request::FetchTasksRequest;
pub use register_request::RegisterRequest;
pub use submit_result_request::SubmitResultRequest;
Expand Down
1 change: 1 addition & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ tokio = { version = "1.50.0", features = ["full"] }
tower-http = { version = "0.6.8", features = ["trace"] }
tracing = "0.1.44"
tracing-subscriber = "0.3.23"
url = "2.5.8"
3 changes: 2 additions & 1 deletion server/migrations/20250426220809_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ CREATE TABLE users (
id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT now(),
disabled_at timestamptz,
name text NOT NULL
name text NOT NULL,
is_admin boolean NOT NULL DEFAULT false
);

CREATE UNIQUE INDEX users_name_key
Expand Down
1 change: 1 addition & 0 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ async fn serve_task(state: AppState, address: String) {
.route("/submit_result/{id}", post(routes::submit_result))
.route("/validate_fetch/{id}", get(routes::validate_fetch))
.route("/validate_submit", post(routes::validate_submit))
.route("/files", post(routes::create_file))
.layer(TraceLayer::new_for_http())
.with_state(state);

Expand Down
13 changes: 11 additions & 2 deletions server/src/result/status.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use axum::http::StatusCode;
use clusterizer_common::errors::{
FetchTasksError, Infallible, NotFound, RegisterError, SubmitResultError, ValidateFetchError,
ValidateSubmitError,
CreateFileError, FetchTasksError, Infallible, NotFound, RegisterError, SubmitResultError,
ValidateFetchError, ValidateSubmitError,
};

pub trait Status {
Expand Down Expand Up @@ -55,3 +55,12 @@ impl Status for ValidateSubmitError {
}
}
}

impl Status for CreateFileError {
fn status(&self) -> StatusCode {
match self {
Self::Forbidden => StatusCode::FORBIDDEN,
_ => StatusCode::BAD_REQUEST,
}
}
}
38 changes: 38 additions & 0 deletions server/src/routes/create_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use axum::{Json, extract::State};
use clusterizer_common::{
errors::CreateFileError,
records::{File, FileBuilder, Insert, Select},
requests::CreateFileRequest,
types::Id,
};
use url::Url;

use crate::{
auth::Auth,
result::{AppError, AppResult},
state::AppState,
};

pub async fn create_file(
State(state): State<AppState>,
Auth(user_id): Auth,
Json(request): Json<CreateFileRequest>,
) -> AppResult<Json<Id<File>>, CreateFileError> {
let user = user_id.select().fetch_one(&state.pool).await?;

if !user.is_admin {
Err(AppError::Specific(CreateFileError::Forbidden))?;
}

Url::parse(&request.url).map_err(|_| AppError::Specific(CreateFileError::InvalidUrl))?;

let file_id = FileBuilder {
url: request.url,
hash: request.hash,
}
.insert()
.fetch_one(&state.pool)
.await?;

Ok(Json(file_id))
}
2 changes: 2 additions & 0 deletions server/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ use crate::{
state::AppState,
};

pub mod create_file;
pub mod fetch_tasks;
pub mod register;
pub mod submit_result;
pub mod validate_fetch;
pub mod validate_submit;

pub use create_file::create_file;
pub use fetch_tasks::fetch_tasks;
pub use register::register;
pub use submit_result::submit_result;
Expand Down
Loading