use crate::addressing::Address; use crate::database::{ get_latest_files, insert_entry, query, remove_object, retrieve_file, retrieve_object, Addressable, DbPool, Entry, Query, }; use crate::filesystem::{list_directory, UPath}; use crate::hash::{decode, encode}; use crate::jobs::JobContainer; use actix_files::NamedFile; use actix_web::error::{ErrorBadRequest, ErrorInternalServerError, ErrorNotFound}; use actix_web::{delete, error, get, post, put, web, Error, HttpResponse}; use anyhow::Result; use futures_util::StreamExt; use log::debug; use serde::Deserialize; use std::collections::HashMap; use std::convert::TryFrom; use std::path::PathBuf; use std::sync::{Arc, RwLock}; #[derive(Clone)] pub struct State { pub directory: PathBuf, pub db_pool: DbPool, pub job_container: Arc>, } #[get("/api/raw/{hash}")] pub async fn get_raw(state: web::Data, hash: web::Path) -> Result { let address = Address::decode(&decode(hash.into_inner()).map_err(ErrorInternalServerError)?) .map_err(ErrorInternalServerError)?; if let Address::Hash(hash) = address { let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let response = retrieve_file(&connection, hash); debug!("{:?}", response); match response { Ok(result) => match result.get(0) { Some(file) => Ok(NamedFile::open(state.directory.join(&file.path))?), None => Err(error::ErrorNotFound("NOT FOUND")), }, Err(e) => Err(error::ErrorInternalServerError(e)), } } else { Err(ErrorBadRequest("Address does not refer to a file.")) } } #[derive(Deserialize)] pub struct QueryRequest { query: String, } #[get("/api/obj")] pub async fn get_query( state: web::Data, web::Query(info): web::Query, ) -> Result { let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let sexp = lexpr::from_str(info.query.as_str()).map_err(ErrorBadRequest)?; let in_query = Query::try_from(&sexp).map_err(ErrorBadRequest)?; let entries = query(&connection, in_query).map_err(ErrorInternalServerError)?; let mut result: HashMap = HashMap::new(); for entry in entries { result.insert( encode( entry .address() .map_err(ErrorInternalServerError)? .encode() .map_err(ErrorInternalServerError)?, ), entry, ); } Ok(HttpResponse::Ok().json(&result)) } #[get("/api/obj/{address_str}")] pub async fn get_object( state: web::Data, address_str: web::Path, ) -> Result { let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let response: Result> = retrieve_object( &connection, Address::decode(&decode(address_str.into_inner()).map_err(ErrorBadRequest)?) .map_err(ErrorBadRequest)?, ); debug!("{:?}", response); let mut result: HashMap = HashMap::new(); for entry in response.map_err(error::ErrorInternalServerError)? { result.insert( encode( entry .address() .map_err(ErrorInternalServerError)? .encode() .map_err(ErrorInternalServerError)?, ), entry, ); } Ok(HttpResponse::Ok().json(result)) } const MAX_SIZE: usize = 1_000_000; #[put("/api/obj")] pub async fn put_object( state: web::Data, mut payload: web::Payload, ) -> Result { let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let mut body = web::BytesMut::new(); while let Some(chunk) = payload.next().await { let chunk = chunk?; // limit max size of in-memory payload if (body.len() + chunk.len()) > MAX_SIZE { return Err(error::ErrorBadRequest("overflow.")); } body.extend_from_slice(&chunk); } let entry = serde_json::from_slice::(&body).map_err(ErrorBadRequest)?; let result_address = insert_entry(&connection, entry).map_err(ErrorInternalServerError)?; Ok(HttpResponse::Ok().json(result_address)) } #[delete("/api/obj/{address_str}")] pub async fn delete_object( state: web::Data, address_str: web::Path, ) -> Result { let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let _ = remove_object( &connection, Address::decode(&decode(address_str.into_inner()).map_err(ErrorBadRequest)?) .map_err(ErrorInternalServerError)?, ) .map_err(ErrorInternalServerError)?; Ok(HttpResponse::Ok().finish()) } #[get("/api/hier/{path:.*}")] pub async fn list_hier( state: web::Data, path: web::Path, ) -> Result { let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let upath: UPath = path.into_inner().parse().map_err(ErrorBadRequest)?; let entries: Vec = list_directory(&connection, &upath) .await .map_err(ErrorNotFound)?; // todo: 500 if actual error occurs Ok(HttpResponse::Ok().json(entries)) } #[post("/api/refresh")] pub async fn api_refresh(state: web::Data) -> Result { let _pool = state.db_pool.clone(); let _directory = state.directory.clone(); actix::spawn(crate::filesystem::rescan_vault( _pool, _directory, state.job_container.clone(), )); Ok(HttpResponse::Ok().finish()) } #[get("/api/files/{hash}")] pub async fn get_file( state: web::Data, hash: web::Path, ) -> Result { let address = Address::decode(&decode(hash.into_inner()).map_err(ErrorInternalServerError)?) .map_err(ErrorInternalServerError)?; if let Address::Hash(hash) = address { let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let response = retrieve_file(&connection, hash).map_err(ErrorInternalServerError)?; Ok(HttpResponse::Ok().json(response)) } else { Err(ErrorBadRequest("Address does not refer to a file.")) } } #[get("/api/files/latest")] pub async fn latest_files(state: web::Data) -> Result { let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let files = get_latest_files(&connection, 100).map_err(ErrorInternalServerError)?; Ok(HttpResponse::Ok().json(&files)) } #[get("/api/jobs")] pub async fn get_jobs(state: web::Data) -> Result { let jobs = state.job_container.read().unwrap().get_jobs(); Ok(HttpResponse::Ok().json(&jobs)) }