tirocinio-canali-steffo-acrate/acrate_rdserver/src/route.rs

309 lines
7.9 KiB
Rust

use std::iter::IntoIterator;
use std::sync::Arc;
use axum::Extension;
use axum::extract::Path;
use axum::http::{HeaderMap, Response, StatusCode};
use axum_extra::extract::Query;
use mediatype::{MediaTypeBuf, MediaTypeList};
use serde::Deserialize;
use acrate_database::connect::connect_async;
use acrate_database::diesel::GroupedBy;
use acrate_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject};
use acrate_rd::jrd::ResourceDescriptorLinkJRD;
use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD};
pub async fn healthcheck_handler() -> Result<StatusCode, StatusCode> {
log::debug!("Handling an healthcheck request!");
log::trace!("Making sure the database is up...");
let _conn = connect_async()
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
log::trace!("Healthcheck successful! Everything's fine!");
Ok(StatusCode::NO_CONTENT)
}
#[derive(Debug, Clone, Deserialize)]
pub struct WebfingerQuery {
pub resource: Option<String>,
#[serde(default)]
pub rel: Vec<String>,
}
pub async fn webfinger_handler(
Path(path): Path<String>,
Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>,
headers: HeaderMap,
Extension(mj): Extension<Arc<minijinja::Environment<'static>>>,
) -> Result<Response<String>, StatusCode> {
log::debug!("Handling a WebFinger request!");
let resource = resource.unwrap_or_else(|| "".to_string());
log::debug!("Resource is: {resource:#?}");
let path = format!("/{path}");
log::debug!("Path is: {path:#?}");
log::debug!("Rel is: {rel:#?}");
let accept = headers.get("Accept");
log::debug!("Accept is: {accept:#?}");
let accept = accept
.map(|h| h.to_str())
.unwrap_or(Ok("*/*"))
.map(MediaTypeList::new)
.map_err(|_| StatusCode::BAD_REQUEST)?;
let mut response = Response::new("".to_string());
let mut conn = connect_async()
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
let subjects = MetaSubject::aquery_matching(&mut conn, &path, &resource)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let subject = subjects.first()
.ok_or(StatusCode::NOT_FOUND)?;
if subject.redirect.is_some() {
{
let headers = response.headers_mut();
headers.insert(
"Location",
subject.redirect
.as_ref()
.expect("redirect not to have become suddenly None")
.parse()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
);
}
{
let status = response.status_mut();
*status = StatusCode::FOUND;
}
return Ok(response);
}
let subject = subject.subject.clone();
let aliases = MetaAlias::aquery_matching(&mut conn, &path, &resource)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let properties = MetaProperty::aquery_matching(&mut conn, &path, &resource)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let links = MetaLink::aquery_matching(&mut conn, &path, &resource)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let link_properties = MetaLinkProperty::aquery_by_link(&mut conn, &links)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.grouped_by(&links);
let link_titles = MetaLinkTitle::aquery_by_link(&mut conn, &links)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.grouped_by(&links);
let links_full: Vec<(MetaLink, Vec<MetaLinkProperty>, Vec<MetaLinkTitle>)> = links
.into_iter()
.zip(link_properties)
.zip(link_titles)
.map(|((link, properties), titles)| (link, properties, titles))
.collect();
{
let headers = response.headers_mut();
headers.insert(
"Access-Control-Allow-Origin",
"*".parse().unwrap()
);
}
for media_type in accept.into_iter() {
let media_type = match media_type {
Err(e) => {
log::debug!("Skipping error while parsing media type: {e:?}");
continue;
},
Ok(media_type) => media_type
};
{
let headers = response.headers_mut();
headers.insert(
"Content-Type",
media_type.to_string()
.parse()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
);
}
match media_type.essence().to_string().to_ascii_lowercase().as_str() {
"*/*" | "application/json" | "application/jrd+json" => {
let aliases = aliases
.into_iter()
.map(|alias| alias.alias)
.collect();
let properties = properties
.into_iter()
.map(|prop| (prop.rel, prop.value))
.collect();
let links = links_full
.into_iter()
.map(|(link, properties, titles)| ResourceDescriptorLinkJRD {
rel: link.rel,
r#type: link.type_.map(|m| m.0),
href: link.href,
template: link.template,
properties: properties
.into_iter()
.map(|property| (property.rel, property.value))
.collect(),
titles: titles
.into_iter()
.map(|title| (title.language, title.value))
.collect(),
})
.collect::<Vec<ResourceDescriptorLinkJRD>>();
let rd = acrate_rd::jrd::ResourceDescriptorJRD {
subject,
aliases,
properties,
links,
};
let json = serde_json::to_string_pretty(&rd)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
{
let body = response.body_mut();
body.push_str(&json);
}
return Ok(response);
},
"application/xml" | "application/xrd+xml" => {
let aliases = aliases
.into_iter()
.map(|alias| alias.alias)
.collect();
let properties: Vec<ResourceDescriptorPropertyXRD> = properties
.into_iter()
.map(|prop| ResourceDescriptorPropertyXRD {
rel: prop.rel,
value: prop.value,
})
.collect();
let links = links_full
.into_iter()
.map(|(link, properties, titles)| ResourceDescriptorLinkXRD {
rel: link.rel,
r#type: link.type_.map(|m| m.0),
href: link.href,
template: link.template,
properties: properties
.into_iter()
.map(|property| ResourceDescriptorPropertyXRD {
rel: property.rel,
value: property.value,
})
.collect(),
titles: titles
.into_iter()
.map(|title| ResourceDescriptorTitleXRD {
language: title.language,
value: title.value,
})
.collect(),
})
.collect::<Vec<ResourceDescriptorLinkXRD>>();
let rd = acrate_rd::xrd::ResourceDescriptorXRD {
subject,
aliases,
properties,
links,
};
let xml = quick_xml::se::to_string(&rd)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
{
let body = response.body_mut();
body.push_str(r#"<?xml version="1.0" encoding="UTF-8"?>"#);
body.push_str(&xml);
}
return Ok(response);
},
"text/html" => {
let aliases: Vec<String> = aliases.into_iter()
.map(|alias| alias.alias)
.collect();
let properties: Vec<(String, Option<String>)> = properties.into_iter()
.map(|prop| {
(prop.rel, prop.value)
})
.collect();
let links: Vec<(String, Option<MediaTypeBuf>, Option<String>, Option<String>, Vec<(String, Option<String>)>, Vec<(String, String)>)> = links_full
.into_iter()
.map(|(link, properties, titles)| {
(
link.rel,
link.type_.map(|m| m.0),
link.href,
link.template,
properties.into_iter()
.map(|prop| (prop.rel, prop.value))
.collect::<Vec<(String, Option<String>)>>(),
titles.into_iter()
.map(|title| (title.language, title.value))
.collect::<Vec<(String, String)>>()
)
})
.collect();
let html = mj.get_template("rd.html.j2")
.expect("rd.html.j2 to exist")
.render(
minijinja::context!(
path => path,
subject => subject,
aliases => aliases,
properties => properties,
links => links,
)
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
{
let body = response.body_mut();
body.push_str(&html);
}
return Ok(response);
},
_ => {
continue;
},
}
}
Err(StatusCode::NOT_ACCEPTABLE)
}