Skip to content

Commit

Permalink
edit preview endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
rawdaGastan committed Aug 22, 2024
1 parent 74679fa commit 02f6380
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 73 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions fl-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion fl-server/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub async fn sign_in_handler(
}))
}

fn get_user_by_username(users: Vec<User>, username: &str) -> Option<User> {
pub fn get_user_by_username(users: Vec<User>, username: &str) -> Option<User> {
let user = users.iter().find(|u| u.username == username)?;
Some(user.clone())
}
Expand Down
145 changes: 141 additions & 4 deletions fl-server/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
tags(
(name = "fl-server", description = "Flist conversion API")
)
Expand All @@ -48,6 +55,13 @@ pub struct FlistBody {
pub registry_token: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
pub struct PreviewResponse {
pub content: Vec<String>,
pub metadata: String,
pub checksum: String,
}

#[derive(Debug, Clone, Serialize, PartialEq, ToSchema)]
pub enum FlistState {
Accepted(String),
Expand Down Expand Up @@ -356,6 +370,129 @@ pub async fn list_flists_handler(
Ok(ResponseResult::Flists(flists))
}

#[utoipa::path(
get,
path = "/v1/api/fl/preview/{flist_path}",
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"),
),
params(
("flist_path" = String, Path, description = "flist file path")
)
)]
#[debug_handler]
pub async fn preview_flist_handler(
Extension(cfg): Extension<config::Config>,
Path(flist_path): Path<String>,
) -> impl IntoResponse {
let fl_path = 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<bool> {
let mut dir = tokio::fs::read_dir(dir_path).await?;

Expand Down
4 changes: 4 additions & 0 deletions fl-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ async fn app() -> Result<()> {
auth::authorize,
)),
)
.route(
"/v1/api/fl/preview/:flist_path",
get(handlers::preview_flist_handler),
)
.route("/v1/api/fl", get(handlers::list_flists_handler))
.route("/*path", get(serve_flists::serve_flists));

Expand Down
8 changes: 6 additions & 2 deletions fl-server/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -75,7 +79,7 @@ pub enum ResponseResult {
FlistCreated(Job),
FlistState(FlistState),
Flists(HashMap<String, Vec<FileInfo>>),
PreviewFlist(Vec<String>),
PreviewFlist(PreviewResponse),
SignedIn(SignInResponse),
DirTemplate(DirListTemplate),
Res(hyper::Response<tower_http::services::fs::ServeFileSystemResponseBody>),
Expand Down
66 changes: 0 additions & 66 deletions fl-server/src/serve_flists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -12,7 +11,6 @@ use axum::{
};
use axum_macros::debug_handler;
use percent_encoding::percent_decode;
use rfs::{cache, fungi::Reader};

use crate::{
config,
Expand All @@ -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();
Expand Down Expand Up @@ -145,60 +136,3 @@ pub async fn visit_dir_one_level(

Ok(files)
}

async fn preview_flist(path: &String) -> Result<ResponseResult, ResponseError> {
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))
}

0 comments on commit 02f6380

Please sign in to comment.