Compare commits

..

No commits in common. "ee1eb9f47089b2a40788b669d4e522976dcf86a9" and "065fa9c80e30e0b13132685be4e12399eb42b054" have entirely different histories.

13 changed files with 57 additions and 540 deletions

3
.gitignore vendored
View file

@ -15,6 +15,3 @@ Cargo.lock
# Environment # Environment
.env.local .env.local
# IntelliJ IDEA thing, mostly broken versioning
.idea/misc.xml

7
.idea/misc.xml Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
</project>

View file

@ -11,15 +11,3 @@ Federation database
> [!Caution] > [!Caution]
> >
> This software suite is in active development! > This software suite is in active development!
## Crates
### Libraries
- `acrate-core`: Database stuff
- `acrate-hostmeta`: RFC 6415 serde
- `acrate-nodeinfo`: NodeInfo serde
### Binaries
- `acrate-webfinger`: WebFinger server

View file

@ -1,6 +1,4 @@
DROP TABLE IF EXISTS meta_properties CASCADE; DROP TABLE IF EXISTS meta_properties CASCADE;
DROP TABLE IF EXISTS meta_link_properties CASCADE; DROP TABLE IF EXISTS meta_link_properties CASCADE;
DROP TABLE IF EXISTS meta_link_titles CASCADE;
DROP TABLE IF EXISTS meta_links CASCADE; DROP TABLE IF EXISTS meta_links CASCADE;
DROP TABLE IF EXISTS meta_aliases CASCADE; DROP TABLE IF EXISTS meta_aliases CASCADE;
DROP TABLE IF EXISTS meta_subjects CASCADE;

View file

@ -1,39 +1,25 @@
CREATE TABLE IF NOT EXISTS meta_subjects ( CREATE TABLE meta_aliases (
id UUID DEFAULT gen_random_uuid(),
document BPCHAR NOT NULL,
pattern BPCHAR NOT NULL,
redirect BPCHAR,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS meta_aliases (
id UUID DEFAULT gen_random_uuid(), id UUID DEFAULT gen_random_uuid(),
document BPCHAR NOT NULL, document BPCHAR NOT NULL,
pattern BPCHAR NOT NULL, pattern BPCHAR NOT NULL,
alias BPCHAR NOT NULL, alias BPCHAR NOT NULL,
CONSTRAINT unique_aliases UNIQUE (alias),
PRIMARY KEY (id) PRIMARY KEY (id)
); );
CREATE TABLE IF NOT EXISTS meta_links ( CREATE TABLE meta_links (
id UUID DEFAULT gen_random_uuid(), id UUID DEFAULT gen_random_uuid(),
document BPCHAR NOT NULL, document BPCHAR NOT NULL,
pattern BPCHAR NOT NULL, pattern BPCHAR NOT NULL,
rel BPCHAR NOT NULL, rel BPCHAR NOT NULL,
type BPCHAR, type BPCHAR,
href BPCHAR, href BPCHAR,
template BPCHAR,
CONSTRAINT either_href_or_template_not_null CHECK (
(href IS NOT NULL AND template IS NULL)
OR
(href IS NULL AND template IS NOT NULL)
),
PRIMARY KEY (id) PRIMARY KEY (id)
); );
CREATE TABLE IF NOT EXISTS meta_link_properties ( CREATE TABLE meta_link_properties (
id UUID DEFAULT gen_random_uuid(), id UUID DEFAULT gen_random_uuid(),
meta_link_id UUID REFERENCES meta_links (id) NOT NULL, meta_link_id UUID REFERENCES meta_links (id) NOT NULL,
rel BPCHAR NOT NULL, rel BPCHAR NOT NULL,
@ -42,7 +28,7 @@ CREATE TABLE IF NOT EXISTS meta_link_properties (
PRIMARY KEY (id) PRIMARY KEY (id)
); );
CREATE TABLE IF NOT EXISTS meta_properties ( CREATE TABLE meta_properties (
id UUID DEFAULT gen_random_uuid(), id UUID DEFAULT gen_random_uuid(),
document BPCHAR NOT NULL, document BPCHAR NOT NULL,
pattern BPCHAR NOT NULL, pattern BPCHAR NOT NULL,
@ -51,13 +37,3 @@ CREATE TABLE IF NOT EXISTS meta_properties (
PRIMARY KEY (id) PRIMARY KEY (id)
); );
CREATE TABLE IF NOT EXISTS meta_link_titles (
id UUID DEFAULT gen_random_uuid(),
meta_link_id UUID REFERENCES meta_links (id) NOT NULL,
language BPCHAR NOT NULL DEFAULT 'und',
value BPCHAR NOT NULL,
CONSTRAINT unique_languages UNIQUE (meta_link_id, language),
PRIMARY KEY(id)
);

View file

@ -1,22 +1,11 @@
use diesel::{Associations, Identifiable, Insertable, QueryResult, Queryable, QueryableByName, Selectable, pg::Pg}; use diesel::{Associations, Identifiable, Insertable, QueryResult, Queryable, QueryableByName, Selectable};
use diesel_async::AsyncPgConnection; use diesel_async::AsyncPgConnection;
use uuid::Uuid; use uuid::Uuid;
use super::schema; use super::schema;
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)]
#[diesel(table_name = schema::meta_subjects)]
#[diesel(check_for_backend(Pg))]
pub struct MetaSubject {
pub id: Uuid,
pub document: String,
pub pattern: String,
pub redirect: Option<String>,
}
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)] #[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)]
#[diesel(table_name = schema::meta_aliases)] #[diesel(table_name = schema::meta_aliases)]
#[diesel(check_for_backend(Pg))]
pub struct MetaAlias { pub struct MetaAlias {
pub id: Uuid, pub id: Uuid,
pub document: String, pub document: String,
@ -26,21 +15,18 @@ pub struct MetaAlias {
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)] #[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)]
#[diesel(table_name = schema::meta_links)] #[diesel(table_name = schema::meta_links)]
#[diesel(check_for_backend(Pg))]
pub struct MetaLink { pub struct MetaLink {
pub id: Uuid, pub id: Uuid,
pub document: String, pub document: String,
pub pattern: String, pub pattern: String,
pub rel: String, pub rel: String,
pub type_: Option<String>, pub r#type: Option<String>,
pub href: Option<String>, pub href: Option<String>,
pub template: Option<String>,
} }
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable, Associations)] #[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable, Associations)]
#[diesel(belongs_to(MetaLink))] #[diesel(belongs_to(MetaLink))]
#[diesel(table_name = schema::meta_link_properties)] #[diesel(table_name = schema::meta_link_properties)]
#[diesel(check_for_backend(Pg))]
pub struct MetaLinkProperty { pub struct MetaLinkProperty {
pub id: Uuid, pub id: Uuid,
pub meta_link_id: Uuid, pub meta_link_id: Uuid,
@ -48,20 +34,8 @@ pub struct MetaLinkProperty {
pub value: Option<String>, pub value: Option<String>,
} }
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable, Associations)]
#[diesel(belongs_to(MetaLink))]
#[diesel(table_name = schema::meta_link_titles)]
#[diesel(check_for_backend(Pg))]
pub struct MetaLinkTitle {
pub id: Uuid,
pub meta_link_id: Uuid,
pub language: String,
pub value: String,
}
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)] #[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)]
#[diesel(table_name = schema::meta_properties)] #[diesel(table_name = schema::meta_properties)]
#[diesel(check_for_backend(Pg))]
pub struct MetaProperty { pub struct MetaProperty {
pub id: Uuid, pub id: Uuid,
pub document: String, pub document: String,
@ -70,26 +44,9 @@ pub struct MetaProperty {
pub value: Option<String>, pub value: Option<String>,
} }
impl MetaSubject {
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use schema::meta_subjects::dsl::*;
let document_is_equal = document.eq(doc);
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
meta_subjects
.filter(document_is_equal)
.filter(subject_matches_pattern)
.select(Self::as_select())
.load(conn)
.await
}
}
impl MetaAlias { impl MetaAlias {
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> { pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<MetaAlias>> {
use diesel::prelude::*; use diesel::prelude::*;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use schema::meta_aliases::dsl::*; use schema::meta_aliases::dsl::*;
@ -107,7 +64,7 @@ impl MetaAlias {
} }
impl MetaLink { impl MetaLink {
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> { pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<MetaLink>> {
use diesel::prelude::*; use diesel::prelude::*;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use schema::meta_links::dsl::*; use schema::meta_links::dsl::*;
@ -122,32 +79,19 @@ impl MetaLink {
.load(conn) .load(conn)
.await .await
} }
}
impl MetaLinkProperty { pub async fn query_properties(&self, conn: &mut AsyncPgConnection) -> QueryResult<Vec<MetaLinkProperty>> {
pub async fn query_by_link(conn: &mut AsyncPgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
use diesel::prelude::*; use diesel::prelude::*;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
Self::belonging_to(links) MetaLinkProperty::belonging_to(self)
.load(conn)
.await
}
}
impl MetaLinkTitle {
pub async fn query_by_link(conn: &mut AsyncPgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
Self::belonging_to(links)
.load(conn) .load(conn)
.await .await
} }
} }
impl MetaProperty { impl MetaProperty {
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> { pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<MetaProperty>> {
use diesel::prelude::*; use diesel::prelude::*;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use schema::meta_properties::dsl::*; use schema::meta_properties::dsl::*;

View file

@ -18,25 +18,14 @@ diesel::table! {
} }
} }
diesel::table! {
meta_link_titles (id) {
id -> Uuid,
meta_link_id -> Uuid,
language -> Bpchar,
value -> Bpchar,
}
}
diesel::table! { diesel::table! {
meta_links (id) { meta_links (id) {
id -> Uuid, id -> Uuid,
document -> Bpchar, document -> Bpchar,
pattern -> Bpchar, pattern -> Bpchar,
rel -> Bpchar, rel -> Bpchar,
#[sql_name = "type"] r#type -> Nullable<Bpchar>,
type_ -> Nullable<Bpchar>,
href -> Nullable<Bpchar>, href -> Nullable<Bpchar>,
template -> Nullable<Bpchar>,
} }
} }
@ -50,23 +39,11 @@ diesel::table! {
} }
} }
diesel::table! {
meta_subjects (id) {
id -> Uuid,
document -> Bpchar,
pattern -> Bpchar,
redirect -> Nullable<Bpchar>,
}
}
diesel::joinable!(meta_link_properties -> meta_links (meta_link_id)); diesel::joinable!(meta_link_properties -> meta_links (meta_link_id));
diesel::joinable!(meta_link_titles -> meta_links (meta_link_id));
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
meta_aliases, meta_aliases,
meta_link_properties, meta_link_properties,
meta_link_titles,
meta_links, meta_links,
meta_properties, meta_properties,
meta_subjects,
); );

View file

@ -17,7 +17,6 @@ pub struct ResourceDescriptorJRD {
/// ///
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.1> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.1>
/// ///
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>, pub subject: Option<String>,
/// Other names the resource described by this document can be referred to. /// Other names the resource described by this document can be referred to.
@ -71,7 +70,6 @@ pub struct ResourceDescriptorLinkJRD {
/// ///
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.2> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.2>
/// ///
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>, pub r#type: Option<String>,
/// URI to the resource put in relation. /// URI to the resource put in relation.
@ -80,7 +78,6 @@ pub struct ResourceDescriptorLinkJRD {
/// ///
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.3> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.3>
/// ///
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<String>, pub href: Option<String>,
/// Titles of the resource put in relation in various languages. /// Titles of the resource put in relation in various languages.
@ -107,7 +104,6 @@ pub struct ResourceDescriptorLinkJRD {
/// ///
/// - <https://datatracker.ietf.org/doc/html/rfc6415#section-4.2> /// - <https://datatracker.ietf.org/doc/html/rfc6415#section-4.2>
/// ///
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<String>, pub template: Option<String>,
} }
@ -223,7 +219,7 @@ impl From<ResourceDescriptorLinkXRD> for ResourceDescriptorLinkJRD {
impl From<ResourceDescriptorPropertyXRD> for (String, Option<String>) { impl From<ResourceDescriptorPropertyXRD> for (String, Option<String>) {
fn from(value: ResourceDescriptorPropertyXRD) -> Self { fn from(value: ResourceDescriptorPropertyXRD) -> Self {
(value.rel, value.value) (value.r#type, value.value)
} }
} }

View file

@ -9,7 +9,6 @@ use crate::jrd::{ResourceDescriptorJRD, ResourceDescriptorLinkJRD};
/// - <https://datatracker.ietf.org/doc/html/rfc6415#section-3> /// - <https://datatracker.ietf.org/doc/html/rfc6415#section-3>
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4>
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename = "XRD")]
pub struct ResourceDescriptorXRD { pub struct ResourceDescriptorXRD {
/// The resource this document refers to. /// The resource this document refers to.
/// ///
@ -18,7 +17,6 @@ pub struct ResourceDescriptorXRD {
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.1> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.1>
/// ///
#[serde(rename = "Subject")] #[serde(rename = "Subject")]
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>, pub subject: Option<String>,
/// Other names the resource described by this document can be referred to. /// Other names the resource described by this document can be referred to.
@ -60,7 +58,6 @@ pub struct ResourceDescriptorXRD {
/// - <https://datatracker.ietf.org/doc/html/rfc6415#section-3.1.1> /// - <https://datatracker.ietf.org/doc/html/rfc6415#section-3.1.1>
/// ///
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename = "Link")]
pub struct ResourceDescriptorLinkXRD { pub struct ResourceDescriptorLinkXRD {
/// The kind of relation established by the subject with the attached resource. /// The kind of relation established by the subject with the attached resource.
/// ///
@ -78,7 +75,6 @@ pub struct ResourceDescriptorLinkXRD {
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.2> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.2>
/// ///
#[serde(rename = "@type")] #[serde(rename = "@type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>, pub r#type: Option<String>,
/// URI to the resource put in relation. /// URI to the resource put in relation.
@ -88,7 +84,6 @@ pub struct ResourceDescriptorLinkXRD {
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.3> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.3>
/// ///
#[serde(rename = "@href")] #[serde(rename = "@href")]
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<String>, pub href: Option<String>,
/// Titles of the resource put in relation in various languages. /// Titles of the resource put in relation in various languages.
@ -98,7 +93,6 @@ pub struct ResourceDescriptorLinkXRD {
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.4> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.4>
/// ///
#[serde(default)] #[serde(default)]
#[serde(rename = "Title")]
pub titles: Vec<ResourceDescriptorTitleXRD>, pub titles: Vec<ResourceDescriptorTitleXRD>,
/// Additional information about the resource put in relation. /// Additional information about the resource put in relation.
@ -108,7 +102,6 @@ pub struct ResourceDescriptorLinkXRD {
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.5> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.5>
/// ///
#[serde(default)] #[serde(default)]
#[serde(rename = "Property")]
pub properties: Vec<ResourceDescriptorPropertyXRD>, pub properties: Vec<ResourceDescriptorPropertyXRD>,
/// Template to fill to get the URL to resource-specific information. /// Template to fill to get the URL to resource-specific information.
@ -118,7 +111,6 @@ pub struct ResourceDescriptorLinkXRD {
/// - <https://datatracker.ietf.org/doc/html/rfc6415#section-4.2> /// - <https://datatracker.ietf.org/doc/html/rfc6415#section-4.2>
/// ///
#[serde(rename = "@template")] #[serde(rename = "@template")]
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<String>, pub template: Option<String>,
} }
@ -128,26 +120,22 @@ pub struct ResourceDescriptorLinkXRD {
/// ///
/// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.4> /// - <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.4>
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename = "Title")]
pub struct ResourceDescriptorTitleXRD { pub struct ResourceDescriptorTitleXRD {
/// The language of the title. /// The language of the title.
#[serde(rename = "@lang")] #[serde(rename = "@lang")]
pub language: String, pub language: String,
/// The title itself. /// The title itself.
#[serde(rename = "$text")]
pub value: String, pub value: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename = "Property")]
pub struct ResourceDescriptorPropertyXRD { pub struct ResourceDescriptorPropertyXRD {
/// The property identifier, or type. /// The property identifier, or type.
#[serde(rename = "@rel")] #[serde(alias = "@type")]
pub rel: String, pub r#type: String,
/// The property value. /// The property value.
#[serde(rename = "$text")]
pub value: Option<String>, pub value: Option<String>,
} }
@ -246,7 +234,7 @@ impl From<ResourceDescriptorJRD> for ResourceDescriptorXRD {
impl From<(String, Option<String>)> for ResourceDescriptorPropertyXRD { impl From<(String, Option<String>)> for ResourceDescriptorPropertyXRD {
fn from(value: (String, Option<String>)) -> Self { fn from(value: (String, Option<String>)) -> Self {
Self { Self {
rel: value.0, r#type: value.0,
value: value.1, value: value.1,
} }
} }

View file

@ -11,9 +11,6 @@ axum = { version = "0.7.7", features = ["macros"] }
axum-extra = { version = "0.9.4", features = ["query"] } axum-extra = { version = "0.9.4", features = ["query"] }
log = "0.4.22" log = "0.4.22"
micronfig = "0.3.0" micronfig = "0.3.0"
minijinja = "2.5.0"
pretty_env_logger = "0.5.0" pretty_env_logger = "0.5.0"
quick-xml = { version = "0.37.0", features = ["serialize"] }
serde = { version = "1.0.215", features = ["derive"] } serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.132"
tokio = { version = "1.41.1", features = ["macros", "net", "rt-multi-thread"] } tokio = { version = "1.41.1", features = ["macros", "net", "rt-multi-thread"] }

View file

@ -1,6 +1,4 @@
use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
use axum::Extension;
mod config; mod config;
mod route; mod route;
@ -11,17 +9,9 @@ async fn main() -> anyhow::Result<std::convert::Infallible> {
pretty_env_logger::init(); pretty_env_logger::init();
log::debug!("Logging initialized!"); log::debug!("Logging initialized!");
log::trace!("Creating Minijinja environment...");
let mut mj = minijinja::Environment::<'static>::new();
log::trace!("Adding webfinger page to the Minijinja environment...");
mj.add_template("webfinger.html.j2", include_str!("webfinger.html.j2"))
.expect("webfinger.html.j2 to be a valid Minijinja template");
log::trace!("Creating Axum router..."); log::trace!("Creating Axum router...");
let app = axum::Router::new() let app = axum::Router::new()
.route("/.well-known/webfinger", axum::routing::get(route::webfinger_handler)) .route("/.well-known/webfinger", axum::routing::get(route::webfinger_handler));
.layer(Extension(Arc::new(mj)));
log::trace!("Axum router created successfully!"); log::trace!("Axum router created successfully!");
log::trace!("Creating Tokio listener..."); log::trace!("Creating Tokio listener...");

View file

@ -1,18 +1,15 @@
use std::sync::Arc; use axum::body::Body;
use axum::Extension; use axum::http::{HeaderMap, HeaderValue, StatusCode};
use axum::http::{HeaderMap, Response, StatusCode};
use axum_extra::extract::Query; use axum_extra::extract::Query;
use serde::Deserialize; use serde::Deserialize;
use acrate_core::diesel::GroupedBy;
use acrate_core::diesel_async::{AsyncConnection, AsyncPgConnection}; use acrate_core::diesel_async::{AsyncConnection, AsyncPgConnection};
use acrate_core::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject};
use acrate_hostmeta::jrd::ResourceDescriptorLinkJRD; use acrate_hostmeta::jrd::ResourceDescriptorLinkJRD;
use acrate_hostmeta::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD}; use acrate_hostmeta::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD};
use crate::config; use crate::config;
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct WebfingerQuery { pub struct WebfingerQuery {
pub resource: Option<String>, pub resource: String,
#[serde(default)] #[serde(default)]
pub rel: Vec<String>, pub rel: Vec<String>,
@ -24,11 +21,9 @@ const WEBFINGER_DOC: &str = "/.well-known/webfinger";
pub async fn webfinger_handler( pub async fn webfinger_handler(
Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>, Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>,
headers: HeaderMap, headers: HeaderMap,
Extension(mj): Extension<Arc<minijinja::Environment<'static>>>, ) -> Result<(Body, HeaderMap), StatusCode> {
) -> Result<Response<String>, StatusCode> {
log::info!("Handling a WebFinger request!"); log::info!("Handling a WebFinger request!");
let resource = resource.unwrap_or_else(|| "".to_string());
log::debug!("Resource is: {resource:#?}"); log::debug!("Resource is: {resource:#?}");
log::debug!("Rel is: {rel:#?}"); log::debug!("Rel is: {rel:#?}");
@ -41,92 +36,29 @@ pub async fn webfinger_handler(
.to_string(); .to_string();
log::debug!("Accept is: {accept:#?}"); log::debug!("Accept is: {accept:#?}");
let mut response = Response::new("".to_string()); let mut response_headers = HeaderMap::new();
let mut conn = AsyncPgConnection::establish(config::ACRATE_WEBFINGER_DATABASE_URL()) let mut conn = AsyncPgConnection::establish(config::ACRATE_WEBFINGER_DATABASE_URL())
.await .await
.map_err(|_| StatusCode::BAD_GATEWAY)?; .map_err(|_| StatusCode::BAD_GATEWAY)?;
let subjects = MetaSubject::query_matching(&mut conn, WEBFINGER_DOC, &resource) let aliases = acrate_core::meta::MetaAlias::query_matching(&mut conn, WEBFINGER_DOC, &resource)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let subject = subjects.first() let properties = acrate_core::meta::MetaProperty::query_matching(&mut conn, WEBFINGER_DOC, &resource)
.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 aliases = MetaAlias::query_matching(&mut conn, WEBFINGER_DOC, &resource)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let properties = MetaProperty::query_matching(&mut conn, WEBFINGER_DOC, &resource) let links = acrate_core::meta::MetaLink::query_matching(&mut conn, WEBFINGER_DOC, &resource)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let links = MetaLink::query_matching(&mut conn, WEBFINGER_DOC, &resource) for mime in accept.split(", ") {
.await response_headers.insert("Content-Type", mime.parse().unwrap());
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let link_properties = MetaLinkProperty::query_by_link(&mut conn, &links)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.grouped_by(&links);
let link_titles = MetaLinkTitle::query_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 mime in accept.split(",") {
{
let headers = response.headers_mut();
headers.insert(
"Content-Type",
mime.parse().map_err(|_| StatusCode::BAD_REQUEST)?
);
}
let (mime, _params) = match mime.trim().split_once(";") {
Some((mime, params)) => (mime, Some(params)),
None => (mime, None),
};
match mime { match mime {
"*/*" | "application/json" | "application/jrd+json" => { "application/json" | "application/jrd+json" => {
let subject = Some(resource); let subject = Some(resource);
let aliases = aliases let aliases = aliases
@ -139,21 +71,15 @@ pub async fn webfinger_handler(
.map(|prop| (prop.rel, prop.value)) .map(|prop| (prop.rel, prop.value))
.collect(); .collect();
let links = links_full let links = links
.into_iter() .into_iter()
.map(|(link, properties, titles)| ResourceDescriptorLinkJRD { .map(|link| ResourceDescriptorLinkJRD {
rel: link.rel, rel: link.rel,
r#type: link.type_, r#type: link.r#type,
href: link.href, href: link.href,
template: link.template, titles: Default::default(), // TODO: Titles
properties: properties properties: Default::default(), // TODO: Link properties
.into_iter() template: None, // TODO: Template
.map(|property| (property.rel, property.value))
.collect(),
titles: titles
.into_iter()
.map(|title| (title.language, title.value))
.collect(),
}) })
.collect::<Vec<ResourceDescriptorLinkJRD>>(); .collect::<Vec<ResourceDescriptorLinkJRD>>();
@ -164,15 +90,9 @@ pub async fn webfinger_handler(
links, links,
}; };
let json = serde_json::to_string_pretty(&rd) let body = rd.
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
{ return Ok(rd, response_headers)
let body = response.body_mut();
body.push_str(&json);
}
return Ok(response);
}, },
"application/xml" | "application/xrd+xml" => { "application/xml" | "application/xrd+xml" => {
let subject = Some(resource); let subject = Some(resource);
@ -182,35 +102,23 @@ pub async fn webfinger_handler(
.map(|alias| alias.alias) .map(|alias| alias.alias)
.collect(); .collect();
let properties: Vec<ResourceDescriptorPropertyXRD> = properties let properties = properties
.into_iter() .into_iter()
.map(|prop| ResourceDescriptorPropertyXRD { .map(|prop| ResourceDescriptorPropertyXRD {
rel: prop.rel, r#type: prop.rel, // TODO: Ah si chiama type?
value: prop.value, value: prop.value,
}) })
.collect(); .collect();
let links = links_full let links = links
.into_iter() .into_iter()
.map(|(link, properties, titles)| ResourceDescriptorLinkXRD { .map(|link| ResourceDescriptorLinkXRD {
rel: link.rel, rel: link.rel,
r#type: link.type_, r#type: link.r#type,
href: link.href, href: link.href,
template: link.template, titles: Default::default(), // TODO: Titles
properties: properties properties: Default::default(), // TODO: Link properties
.into_iter() template: None, // TODO: Template
.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>>(); .collect::<Vec<ResourceDescriptorLinkXRD>>();
@ -221,68 +129,11 @@ pub async fn webfinger_handler(
links, links,
}; };
let xml = quick_xml::se::to_string(&rd) return Ok(StatusCode::OK)
.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<String>, Option<String>, Option<String>, Vec<(String, Option<String>)>, Vec<(String, String)>)> = links_full
.into_iter()
.map(|(link, properties, titles)| {
(
link.rel,
link.type_,
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("webfinger.html.j2")
.expect("webfinger.html.j2 to exist")
.render(
minijinja::context!(
subject => resource,
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; continue;
}, }
} }
} }

View file

@ -1,192 +0,0 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>{{ subject }} · Acrate Webfinger</title>
<style>
:root {
--wf-yellow: #e2bb03;
--wf-brown: #3b351d;
--link-color: #0094b9;
}
body {
font-family: sans-serif;
}
h1, h2 {
text-align: center;
}
main {
max-width: 800px;
padding: 8px;
margin: 0 auto;
border: 2px solid currentColor;
border-radius: 8px;
}
a {
color: var(--link-color);
}
hr {
margin: 8px -8px;
border: 1px solid currentColor;
opacity: 1;
}
small span {
font-family: monospace;
}
@media screen {
main {
box-shadow: 4px 4px 10px currentColor;
}
}
@media screen and (prefers-color-scheme: dark) {
body {
background-color: black;
color: var(--wf-yellow);
}
main {
background-color: var(--wf-brown);
}
main, hr {
border-color: var(--wf-yellow);
}
}
@media screen and (prefers-color-scheme: light) {
body {
background-color: var(--wf-yellow);
color: var(--wf-brown);
}
main {
background-color: white;
}
main, hr {
border-color: var(--wf-brown);
}
}
</style>
</head>
<body>
<header>
<h1>
Acrate Webfinger
</h1>
</header>
<main>
<h2 id="section-subject">
<span id="subject">{{ subject }}</span>
</h2>
{% if aliases %}
<hr>
<section id="section-aliases">
<h3>
Aliases
</h3>
<ul>
{% for alias in aliases %}
<li><a href="{{ alias }}">{{ alias }}</a></li>
{% endfor %}
</ul>
</section>
{% endif %}
{% if properties %}
<hr>
<section id="section-properties">
<h3>
Properties
</h3>
<dl>
{% for property in properties %}
<dt>
<a href="{{ property[0] }}">{{ property[0] }}</a>
</dt>
<dd>
<span>{{ property[1] }}</span>
</dd>
{% endfor %}
</dl>
</section>
{% endif %}
{% if links %}
<hr>
<section id="section-links">
<h3>
Links
</h3>
<dl>
{% for link in links %}
<dt>
<h4>
{% if link[0] != "self" %}
<a href="{{ link[0] }}">{{ link[0] }}</a>
{% else %}
<span>{{ link[0] }}</span>
{% endif %}
{% if link[1] is not none %}
<small>
(<span>{{ link[1] }}</span>)
</small>
{% endif %}
</h4>
</dt>
<dd>
{% if link[2] is not none %}
<h5>
Link destination
</h5>
<a href="{{ link[2] }}">{{ link[2] }}</a>
{% endif %}
{% if link[3] is not none %}
<h5>
Link template
</h5>
<span>{{ link[3] }}</span>
{% endif %}
{% if link[4] %}
<h5>
Link properties
</h5>
<dl>
{% for property in link[4] %}
<dt>
<a href="{{ property[0] }}">{{ property[0] }}</a>
</dt>
<dd>
<span>{{ property[1] }}</span>
</dd>
{% endfor %}
</dl>
{% endif %}
{% if link[5] %}
<h5>
Link titles
</h5>
<dl>
{% for title in link[5] %}
<dt>
<span>{{ title[0] }}</span>
</dt>
<dd>
<span>{{ title[1] }}</span>
</dd>
{% endfor %}
</dl>
{% endif %}
</dd>
{% endfor %}
</dl>
</section>
{% endif %}
</main>
</body>