hierarchy listing endpoint, fix trailing slash upath parsing
This commit is contained in:
parent
2b92a1ad67
commit
ff457faca3
4 changed files with 133 additions and 33 deletions
|
@ -31,7 +31,7 @@ pub struct InnerEntry {
|
||||||
pub value: EntryValue,
|
pub value: EntryValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum EntryValue {
|
pub enum EntryValue {
|
||||||
Value(serde_json::Value),
|
Value(serde_json::Value),
|
||||||
Address(Address),
|
Address(Address),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::addressing::Address;
|
use crate::addressing::Address;
|
||||||
use crate::database::{
|
use crate::database::{
|
||||||
DbExecutor, Entry, EntryValue, InnerEntry, InsertEntry, QueryEntries, RetrieveByHash,
|
DbExecutor, Entry, EntryValue, InnerEntry, InsertEntry, QueryEntries, RetrieveByHash,
|
||||||
|
RetrieveObject,
|
||||||
};
|
};
|
||||||
use crate::hash::{ComputeHash, HasherWorker};
|
use crate::hash::{ComputeHash, HasherWorker};
|
||||||
use crate::models;
|
use crate::models;
|
||||||
|
@ -48,6 +49,7 @@ impl std::str::FromStr for UPath {
|
||||||
});
|
});
|
||||||
result.append(
|
result.append(
|
||||||
rest[TOP_SEPARATOR.len()..rest.len()]
|
rest[TOP_SEPARATOR.len()..rest.len()]
|
||||||
|
.trim_end_matches('/')
|
||||||
.split("/")
|
.split("/")
|
||||||
.map(|part| UDirectory {
|
.map(|part| UDirectory {
|
||||||
name: String::from(part),
|
name: String::from(part),
|
||||||
|
@ -59,6 +61,7 @@ impl std::str::FromStr for UPath {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
None => string
|
None => string
|
||||||
|
.trim_end_matches('/')
|
||||||
.split("/")
|
.split("/")
|
||||||
.map(|part| UDirectory {
|
.map(|part| UDirectory {
|
||||||
name: String::from(part),
|
name: String::from(part),
|
||||||
|
@ -104,10 +107,89 @@ impl std::fmt::Display for UPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait EntryList {
|
||||||
|
fn extract_addresses(&self) -> Vec<Address>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntryList for Vec<Entry> {
|
||||||
|
fn extract_addresses(&self) -> Vec<Address> {
|
||||||
|
self.into_iter()
|
||||||
|
.filter_map(|e| {
|
||||||
|
if let EntryValue::Address(address) = &e.value {
|
||||||
|
Some(address.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_roots(db_executor: &Addr<crate::database::DbExecutor>) -> Result<Vec<Entry>> {
|
||||||
|
let all_directories: Vec<Entry> = db_executor
|
||||||
|
.send(QueryEntries {
|
||||||
|
target: None,
|
||||||
|
key: Some(DIR_KEY.to_string()),
|
||||||
|
value: None,
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let directories_with_parents: Vec<Address> = db_executor
|
||||||
|
.send(QueryEntries {
|
||||||
|
target: None,
|
||||||
|
key: Some(DIR_HAS_KEY.to_string()),
|
||||||
|
value: None,
|
||||||
|
})
|
||||||
|
.await??
|
||||||
|
.extract_addresses();
|
||||||
|
|
||||||
|
Ok(all_directories
|
||||||
|
.into_iter()
|
||||||
|
.filter(|entry| !directories_with_parents.contains(&entry.target))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_directory(db_executor: &Addr<DbExecutor>, path: &UPath) -> Result<Vec<Entry>> {
|
||||||
|
let entry_addresses = match path.0.len() {
|
||||||
|
0 => list_roots(db_executor)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| e.target)
|
||||||
|
.collect(),
|
||||||
|
_ => {
|
||||||
|
let resolved_path: Vec<Address> = resolve_path(db_executor, path, false).await?;
|
||||||
|
let last = resolved_path.last().unwrap();
|
||||||
|
|
||||||
|
db_executor
|
||||||
|
.send(QueryEntries {
|
||||||
|
target: Some(last.clone()),
|
||||||
|
key: Some(DIR_HAS_KEY.to_string()),
|
||||||
|
value: None,
|
||||||
|
})
|
||||||
|
.await??
|
||||||
|
.extract_addresses()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result: Vec<Entry> = vec![];
|
||||||
|
for address in entry_addresses {
|
||||||
|
result.extend(
|
||||||
|
db_executor
|
||||||
|
.send(RetrieveObject { target: address })
|
||||||
|
.await??
|
||||||
|
.into_iter()
|
||||||
|
.filter(|e| [DIR_KEY, FILENAME_KEY, FILE_IDENTITY_KEY].contains(&e.key.as_str()))
|
||||||
|
.collect::<Vec<Entry>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_or_create_dir(
|
pub async fn fetch_or_create_dir(
|
||||||
db_executor: &Addr<crate::database::DbExecutor>,
|
db_executor: &Addr<crate::database::DbExecutor>,
|
||||||
parent: Option<Address>,
|
parent: Option<Address>,
|
||||||
directory: UDirectory,
|
directory: UDirectory,
|
||||||
|
create: bool,
|
||||||
) -> Result<Address> {
|
) -> Result<Address> {
|
||||||
match parent.clone() {
|
match parent.clone() {
|
||||||
Some(address) => trace!("FETCHING/CREATING {}/{:#}", address, directory),
|
Some(address) => trace!("FETCHING/CREATING {}/{:#}", address, directory),
|
||||||
|
@ -135,15 +217,7 @@ pub async fn fetch_or_create_dir(
|
||||||
value: None,
|
value: None,
|
||||||
})
|
})
|
||||||
.await??
|
.await??
|
||||||
.into_iter()
|
.extract_addresses();
|
||||||
.filter_map(|e: Entry| {
|
|
||||||
if let EntryValue::Address(address) = e.value {
|
|
||||||
Some(address)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let valid = directories
|
let valid = directories
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -157,28 +231,32 @@ pub async fn fetch_or_create_dir(
|
||||||
|
|
||||||
match valid_directories.len() {
|
match valid_directories.len() {
|
||||||
0 => {
|
0 => {
|
||||||
let new_directory_address = Address::UUID(Uuid::new_v4());
|
if create {
|
||||||
let directory_entry = InnerEntry {
|
let new_directory_address = Address::UUID(Uuid::new_v4());
|
||||||
target: new_directory_address.clone(),
|
let directory_entry = InnerEntry {
|
||||||
key: String::from(DIR_KEY),
|
target: new_directory_address.clone(),
|
||||||
value: dir_value,
|
key: String::from(DIR_KEY),
|
||||||
};
|
value: dir_value,
|
||||||
let _ = db_executor
|
|
||||||
.send(InsertEntry {
|
|
||||||
entry: directory_entry,
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
if parent.is_some() {
|
|
||||||
let has_entry = InnerEntry {
|
|
||||||
target: parent.unwrap(),
|
|
||||||
key: String::from(DIR_HAS_KEY),
|
|
||||||
value: EntryValue::Address(new_directory_address.clone()),
|
|
||||||
};
|
};
|
||||||
let _ = db_executor.send(InsertEntry { entry: has_entry }).await??;
|
let _ = db_executor
|
||||||
}
|
.send(InsertEntry {
|
||||||
|
entry: directory_entry,
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
Ok(new_directory_address)
|
if parent.is_some() {
|
||||||
|
let has_entry = InnerEntry {
|
||||||
|
target: parent.unwrap(),
|
||||||
|
key: String::from(DIR_HAS_KEY),
|
||||||
|
value: EntryValue::Address(new_directory_address.clone()),
|
||||||
|
};
|
||||||
|
let _ = db_executor.send(InsertEntry { entry: has_entry }).await??;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(new_directory_address)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Directory does not exist."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
1 => Ok(valid_directories[0].clone()),
|
1 => Ok(valid_directories[0].clone()),
|
||||||
_ => Err(anyhow!(
|
_ => Err(anyhow!(
|
||||||
|
@ -187,9 +265,10 @@ pub async fn fetch_or_create_dir(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resolve_path_with_parents(
|
pub async fn resolve_path(
|
||||||
db_executor: &Addr<DbExecutor>,
|
db_executor: &Addr<DbExecutor>,
|
||||||
path: &UPath,
|
path: &UPath,
|
||||||
|
create: bool,
|
||||||
) -> Result<Vec<Address>> {
|
) -> Result<Vec<Address>> {
|
||||||
let mut result: Vec<Address> = vec![];
|
let mut result: Vec<Address> = vec![];
|
||||||
let mut path_stack = path.0.to_vec();
|
let mut path_stack = path.0.to_vec();
|
||||||
|
@ -200,6 +279,7 @@ pub async fn resolve_path_with_parents(
|
||||||
db_executor,
|
db_executor,
|
||||||
result.last().cloned(),
|
result.last().cloned(),
|
||||||
path_stack.pop().unwrap(),
|
path_stack.pop().unwrap(),
|
||||||
|
create,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
result.push(dir_address);
|
result.push(dir_address);
|
||||||
|
@ -288,7 +368,7 @@ async fn _reimport_directory<T: AsRef<Path>>(
|
||||||
}))
|
}))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
let resolved_path = resolve_path_with_parents(db_executor, &upath).await?;
|
let resolved_path = resolve_path(db_executor, &upath, true).await?;
|
||||||
let parent_dir = resolved_path.last().unwrap();
|
let parent_dir = resolved_path.last().unwrap();
|
||||||
let dir_has_entry = InnerEntry {
|
let dir_has_entry = InnerEntry {
|
||||||
target: parent_dir.clone(),
|
target: parent_dir.clone(),
|
||||||
|
|
|
@ -82,6 +82,7 @@ fn main() -> std::io::Result<()> {
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.service(routes::get_raw)
|
.service(routes::get_raw)
|
||||||
.service(routes::get_object)
|
.service(routes::get_object)
|
||||||
|
.service(routes::list_hier)
|
||||||
.service(routes::get_lookup)
|
.service(routes::get_lookup)
|
||||||
.service(routes::api_refresh)
|
.service(routes::api_refresh)
|
||||||
.service(
|
.service(
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::addressing::Address;
|
use crate::addressing::Address;
|
||||||
use crate::database::Entry;
|
use crate::database::Entry;
|
||||||
|
use crate::filesystem::{list_directory, UPath};
|
||||||
use crate::hash::{decode, encode};
|
use crate::hash::{decode, encode};
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
use actix_web::error::{ErrorBadRequest, ErrorInternalServerError};
|
use actix_web::error::{ErrorBadRequest, ErrorInternalServerError, ErrorNotFound};
|
||||||
use actix_web::{error, get, post, web, Error, HttpResponse};
|
use actix_web::{error, get, post, web, Error, HttpResponse};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
@ -66,6 +67,24 @@ pub async fn get_object(
|
||||||
Ok(HttpResponse::Ok().json(result))
|
Ok(HttpResponse::Ok().json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/hier/{path:.*}")]
|
||||||
|
pub async fn list_hier(
|
||||||
|
state: web::Data<State>,
|
||||||
|
path: web::Path<String>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let upath: UPath = path.into_inner().parse().map_err(ErrorBadRequest)?;
|
||||||
|
let entries: Vec<Entry> = list_directory(&state.db, &upath)
|
||||||
|
.await
|
||||||
|
.map_err(ErrorNotFound)?; // todo: 500 if actual error occurs
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(
|
||||||
|
entries
|
||||||
|
.iter()
|
||||||
|
.map(Entry::as_json)
|
||||||
|
.collect::<serde_json::Value>(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LookupRequest {
|
pub struct LookupRequest {
|
||||||
query: String,
|
query: String,
|
||||||
|
|
Loading…
Reference in a new issue