From faf6fb8d947d24e018695ea7d3e7e078dd762029 Mon Sep 17 00:00:00 2001 From: rawdaGastan Date: Thu, 22 Aug 2024 13:09:15 +0300 Subject: [PATCH] edit preview endpoint --- Cargo.lock | 14 ++++ fl-server/Cargo.toml | 1 + fl-server/src/auth.rs | 2 +- fl-server/src/handlers.rs | 148 +++++++++++++++++++++++++++++++++- fl-server/src/main.rs | 1 + fl-server/src/response.rs | 8 +- fl-server/src/serve_flists.rs | 66 --------------- 7 files changed, 167 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a2ebd2..f1bf5a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1048,6 +1048,7 @@ dependencies = [ "rfs", "serde", "serde_json", + "sha256", "simple_logger", "tempdir", "thiserror", @@ -3080,6 +3081,19 @@ dependencies = [ "digest", ] +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" diff --git a/fl-server/Cargo.toml b/fl-server/Cargo.toml index 3475ad8..2111174 100644 --- a/fl-server/Cargo.toml +++ b/fl-server/Cargo.toml @@ -48,3 +48,4 @@ utoipa-swagger-ui = { version = "7", features = ["axum"] } thiserror = "1.0.63" hostname-validator = "1.1.1" walkdir = "2.5.0" +sha256 = "1.5.0" diff --git a/fl-server/src/auth.rs b/fl-server/src/auth.rs index 238e376..9cb96e6 100644 --- a/fl-server/src/auth.rs +++ b/fl-server/src/auth.rs @@ -78,7 +78,7 @@ pub async fn sign_in_handler( })) } -fn get_user_by_username(users: Vec, username: &str) -> Option { +pub fn get_user_by_username(users: Vec, username: &str) -> Option { let user = users.iter().find(|u| u.username == username)?; Some(user.clone()) } diff --git a/fl-server/src/handlers.rs b/fl-server/src/handlers.rs index 9b640c8..3ff53cc 100644 --- a/fl-server/src/handlers.rs +++ b/fl-server/src/handlers.rs @@ -10,24 +10,31 @@ use std::{ sync::{mpsc, Arc}, }; use tokio::io; +use walkdir::WalkDir; use bollard::auth::DockerCredentials; use serde::{Deserialize, Serialize}; -use crate::auth::{SignInBody, SignInResponse, __path_sign_in_handler}; +use crate::{ + auth::{SignInBody, SignInResponse, __path_sign_in_handler, get_user_by_username}, + response::{DirListTemplate, DirLister, ErrorTemplate, TemplateErr}, +}; use crate::{ config::{self, Job}, response::{FileInfo, ResponseError, ResponseResult}, serve_flists::visit_dir_one_level, }; -use rfs::fungi::Writer; +use rfs::{ + cache, + fungi::{Reader, Writer}, +}; use utoipa::{OpenApi, ToSchema}; use uuid::Uuid; #[derive(OpenApi)] #[openapi( - paths(health_check_handler, create_flist_handler, get_flist_state_handler, list_flists_handler, sign_in_handler), - components(schemas(FlistBody, Job, ResponseError, ResponseResult, FileInfo, SignInBody, FlistState, SignInResponse, FlistStateInfo)), + paths(health_check_handler, create_flist_handler, get_flist_state_handler, preview_flist_handler, list_flists_handler, sign_in_handler), + components(schemas(DirListTemplate, DirLister, FlistBody, Job, ResponseError, ErrorTemplate, TemplateErr, ResponseResult, FileInfo, SignInBody, FlistState, SignInResponse, FlistStateInfo, PreviewResponse, PreviewBody)), tags( (name = "fl-server", description = "Flist conversion API") ) @@ -48,6 +55,18 @@ pub struct FlistBody { pub registry_token: Option, } +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +pub struct PreviewBody { + pub flist_path: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +pub struct PreviewResponse { + pub content: Vec, + pub metadata: String, + pub checksum: String, +} + #[derive(Debug, Clone, Serialize, PartialEq, ToSchema)] pub enum FlistState { Accepted(String), @@ -356,6 +375,127 @@ pub async fn list_flists_handler( Ok(ResponseResult::Flists(flists)) } +#[utoipa::path( + get, + path = "/v1/api/fl/preview", + request_body = PreviewBody, + responses( + (status = 200, description = "Flist preview result", body = PreviewResponse), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized user"), + (status = 403, description = "Forbidden"), + (status = 500, description = "Internal server error"), + ) +)] +#[debug_handler] +pub async fn preview_flist_handler( + Extension(cfg): Extension, + Json(body): Json, +) -> impl IntoResponse { + let fl_path = body.flist_path; + + // Validations + if fl_path.starts_with("/") { + return Err(ResponseError::BadRequest(format!( + "invalid flist path '{}', shouldn't start with '/'", + fl_path + ))); + } + + let parts: Vec<_> = fl_path.split("/").collect(); + if parts.len() != 3 { + return Err(ResponseError::BadRequest(format!("invalid flist path '{}', should consist of 3 parts [parent directory, username and flist name", fl_path))); + } + + if parts[0] != cfg.flist_dir { + return Err(ResponseError::BadRequest(format!( + "invalid flist path '{}', parent directory should be '{}'", + fl_path, cfg.flist_dir + ))); + } + + match get_user_by_username(cfg.users, parts[1]) { + Some(_) => (), + None => { + return Err(ResponseError::BadRequest(format!( + "invalid flist path '{}', username '{}' doesn't exist", + fl_path, parts[1] + ))); + } + }; + + let meta = match Reader::new(&fl_path).await { + Ok(reader) => reader, + Err(err) => { + log::error!( + "failed to initialize metadata database for flist `{}` with error {}", + fl_path, + err + ); + return Err(ResponseError::InternalServerError); + } + }; + + let router = match rfs::store::get_router(&meta).await { + Ok(r) => r, + Err(err) => { + log::error!("failed to get router with error {}", err); + return Err(ResponseError::InternalServerError); + } + }; + + let cache = cache::Cache::new(String::from("/tmp/cache"), router); + let tmp_target = match tempdir::TempDir::new("target") { + Ok(dir) => dir, + Err(err) => { + log::error!("failed to create tmp dir with error {}", err); + return Err(ResponseError::InternalServerError); + } + }; + let tmp_target_path = tmp_target.path().to_owned(); + + match rfs::unpack(&meta, &cache, &tmp_target_path, false).await { + Ok(_) => (), + Err(err) => { + log::error!("failed to unpack flist {} with error {}", fl_path, err); + return Err(ResponseError::InternalServerError); + } + }; + + let mut paths = Vec::new(); + for file in WalkDir::new(tmp_target_path.clone()) + .into_iter() + .filter_map(|file| file.ok()) + { + let mut path = file.path().to_string_lossy().to_string(); + + path = path + .strip_prefix(&tmp_target_path.to_string_lossy().to_string()) + .unwrap() + .to_string(); + paths.push(path); + } + + let bytes = match std::fs::read(&fl_path) { + Ok(b) => b, + Err(err) => { + log::error!( + "failed to read flist '{}' into bytes with error {}", + fl_path, + err + ); + return Err(ResponseError::InternalServerError); + } + }; + let hash = sha256::digest(&bytes); + + Ok(ResponseResult::PreviewFlist(PreviewResponse { + content: paths, + metadata: cfg.store_url.join("-"), + checksum: hash, + })) +} + pub async fn flist_exists(dir_path: &std::path::Path, flist_name: &String) -> io::Result { let mut dir = tokio::fs::read_dir(dir_path).await?; diff --git a/fl-server/src/main.rs b/fl-server/src/main.rs index ecf3780..30361c4 100644 --- a/fl-server/src/main.rs +++ b/fl-server/src/main.rs @@ -98,6 +98,7 @@ async fn app() -> Result<()> { auth::authorize, )), ) + .route("/v1/api/fl/preview", get(handlers::preview_flist_handler)) .route("/v1/api/fl", get(handlers::list_flists_handler)) .route("/*path", get(serve_flists::serve_flists)); diff --git a/fl-server/src/response.rs b/fl-server/src/response.rs index 4f15379..124b1a0 100644 --- a/fl-server/src/response.rs +++ b/fl-server/src/response.rs @@ -10,7 +10,11 @@ use axum::{ use serde::Serialize; use utoipa::ToSchema; -use crate::{auth::SignInResponse, config::Job, handlers::FlistState}; +use crate::{ + auth::SignInResponse, + config::Job, + handlers::{FlistState, PreviewResponse}, +}; #[derive(Serialize, ToSchema)] pub enum ResponseError { @@ -75,7 +79,7 @@ pub enum ResponseResult { FlistCreated(Job), FlistState(FlistState), Flists(HashMap>), - PreviewFlist(Vec), + PreviewFlist(PreviewResponse), SignedIn(SignInResponse), DirTemplate(DirListTemplate), Res(hyper::Response), diff --git a/fl-server/src/serve_flists.rs b/fl-server/src/serve_flists.rs index dcf5b8f..48c08a8 100644 --- a/fl-server/src/serve_flists.rs +++ b/fl-server/src/serve_flists.rs @@ -3,7 +3,6 @@ use std::{path::PathBuf, sync::Arc}; use tokio::io; use tower::util::ServiceExt; use tower_http::services::ServeDir; -use walkdir::WalkDir; use axum::{ body::Body, @@ -12,7 +11,6 @@ use axum::{ }; use axum_macros::debug_handler; use percent_encoding::percent_decode; -use rfs::{cache, fungi::Reader}; use crate::{ config, @@ -29,13 +27,6 @@ pub async fn serve_flists( ) -> impl IntoResponse { let path = req.uri().path().to_string(); - if path.ends_with(".md") { - match preview_flist(&path).await { - Ok(res) => return Ok(res), - Err(err) => return Err(err), - }; - } - return match ServeDir::new("").oneshot(req).await { Ok(res) => { let status = res.status(); @@ -145,60 +136,3 @@ pub async fn visit_dir_one_level( Ok(files) } - -async fn preview_flist(path: &String) -> Result { - if !path.ends_with(".md") { - return Err(ResponseError::BadRequest( - "flist path is invalid".to_string(), - )); - } - - let mut fl_path: String = path.strip_suffix(".md").unwrap().to_string(); - fl_path = fl_path.strip_prefix("/").unwrap().to_string(); - let meta = match Reader::new(&fl_path).await { - Ok(reader) => reader, - Err(err) => { - log::error!( - "failed to initialize metadata database for flist `{}` with error {}", - fl_path, - err - ); - return Err(ResponseError::InternalServerError); - } - }; - - let router = match rfs::store::get_router(&meta).await { - Ok(r) => r, - Err(err) => { - log::error!("failed to get router with error {}", err); - return Err(ResponseError::InternalServerError); - } - }; - - let cache = cache::Cache::new(String::from("/tmp/cache"), router); - let tmp_target = tempdir::TempDir::new("target").unwrap(); - let tmp_target_path = tmp_target.path().to_owned(); - - match rfs::unpack(&meta, &cache, &tmp_target_path, false).await { - Ok(_) => (), - Err(err) => { - log::error!("failed to unpack flist {} with error {}", fl_path, err); - return Err(ResponseError::InternalServerError); - } - }; - - let mut paths = Vec::new(); - for file in WalkDir::new(tmp_target_path.clone()) - .into_iter() - .filter_map(|file| file.ok()) - { - let mut path = file.path().to_string_lossy().to_string(); - path = path - .strip_prefix(&tmp_target_path.to_string_lossy().to_string()) - .unwrap() - .to_string(); - paths.push(path); - } - - Ok(ResponseResult::PreviewFlist(paths)) -}