use axum::extract::{Path, Query, State};
use axum::response::{IntoResponse, Response};
use axum::Json;
use sbol_db_sparql::GraphScope;
use serde::Deserialize;
use serde_json::json;
use super::download::{download_scoped, DownloadParams, Format};
use super::routes::{run_scoped_value, UserObject};
use super::{queries, render};
use crate::{ApiError, AppState};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShareObject {
pub user_id: String,
pub collection_id: String,
pub display_id: String,
pub version: String,
pub hash: String,
}
impl ShareObject {
fn object_uri(&self, state: &AppState) -> String {
format!(
"{}user/{}/{}/{}/{}",
state.app.registry_namespace.database_prefix(),
self.user_id,
self.collection_id,
self.display_id,
self.version
)
}
}
fn verify(state: &AppState, object: &ShareObject) -> Result<String, ApiError> {
let uri = object.object_uri(state);
let expected = sbol_db_app::share_hash(&uri, &state.config.share_link_salt);
if expected == object.hash {
Ok(uri)
} else {
Err(ApiError::NotFound(format!("share link for {uri}")))
}
}
pub async fn share_link(State(state): State<AppState>, Path(object): Path<UserObject>) -> Response {
let uri = super::routes::user_uri(&state, &object);
let hash = sbol_db_app::share_hash(&uri, &state.config.share_link_salt);
let url = format!(
"/user/{}/{}/{}/{}/{}/share",
object.user_id, object.collection_id, object.display_id, object.version, hash
);
Json(json!({ "hash": hash, "url": url })).into_response()
}
macro_rules! share_download {
($name:ident, $format:expr) => {
pub async fn $name(
State(state): State<AppState>,
Path(object): Path<ShareObject>,
Query(params): Query<DownloadParams>,
) -> Result<Response, ApiError> {
let uri = verify(&state, &object)?;
download_scoped(
state,
GraphScope::Union,
uri,
object.display_id,
$format,
params.version,
)
.await
}
};
}
share_download!(share_bare, Format::Sbol);
share_download!(share_full, Format::Sbol);
share_download!(share_sbol, Format::Sbol);
share_download!(share_sbolnr, Format::SbolNonRecursive);
share_download!(share_genbank, Format::GenBank);
share_download!(share_fasta, Format::Fasta);
share_download!(share_gff, Format::Gff3);
share_download!(share_omex, Format::Omex);
share_download!(share_summary, Format::Summary);
pub async fn share_metadata(
State(state): State<AppState>,
Path(object): Path<ShareObject>,
) -> Result<Response, ApiError> {
let uri = verify(&state, &object)?;
let results = run_scoped_value(&state, &queries::metadata(&uri), GraphScope::Union).await?;
Ok(render::metadata_response(&results))
}
pub async fn share_sub_collections(
State(state): State<AppState>,
Path(object): Path<ShareObject>,
) -> Result<Response, ApiError> {
let uri = verify(&state, &object)?;
let results =
run_scoped_value(&state, &queries::sub_collections(&uri), GraphScope::Union).await?;
Ok(render::collections_response(&results))
}
macro_rules! share_relation {
($name:ident, $query:path, $count:expr) => {
pub async fn $name(
State(state): State<AppState>,
Path(object): Path<ShareObject>,
) -> Result<Response, ApiError> {
let uri = verify(&state, &object)?;
let results =
run_scoped_value(&state, &$query(&uri, $count), GraphScope::Union).await?;
Ok(if $count {
render::count_response(&results)
} else {
render::search_response(&results)
})
}
};
}
share_relation!(share_uses, queries::uses, false);
share_relation!(share_uses_count, queries::uses, true);
share_relation!(share_twins, queries::twins, false);
share_relation!(share_twins_count, queries::twins, true);