Compare commits
27 commits
2d3421e2c3
...
065fa9c80e
Author | SHA1 | Date | |
---|---|---|---|
065fa9c80e | |||
f5449cd397 | |||
6315c22ad6 | |||
affb063508 | |||
94203cdc7b | |||
d0aa551ef4 | |||
b7924c8776 | |||
3906d03653 | |||
69392bf7e5 | |||
153dc7d50f | |||
3e2a7fd110 | |||
c235467b06 | |||
18b3619785 | |||
6ebf2ddd6d | |||
b5d907bd07 | |||
5a3d913933 | |||
21842d9f80 | |||
2ca4bb3662 | |||
84f7002338 | |||
c27af2cf53 | |||
e65c85616d | |||
956bd17ad0 | |||
c9afa95fd6 | |||
34e1eff855 | |||
c09edc9b08 | |||
ad2383c56b | |||
03aab44ba4 |
12 changed files with 310 additions and 84 deletions
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/acrate-core/migrations/2024-11-14-031744_Add webfinger table/up.sql" dialect="GenericSQL" />
|
||||
<file url="file://$PROJECT_DIR$/acrate-core/migrations/2024-11-14-031744_meta/up.sql" dialect="GenericSQL" />
|
||||
</component>
|
||||
</project>
|
|
@ -4,10 +4,10 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
diesel = "2.2.4"
|
||||
diesel = { version = "2.2.4", features = ["postgres", "uuid"] }
|
||||
diesel-async = { version = "0.5.1", features = ["postgres"] }
|
||||
diesel_migrations = "2.2.0"
|
||||
acrate-hostmeta = { path = "../acrate-hostmeta" }
|
||||
acrate-nodeinfo = { path = "../acrate-nodeinfo" }
|
||||
uuid = "1.11.0"
|
||||
|
||||
[lints.clippy]
|
||||
tabs-in-doc-comments = "allow"
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
DROP FUNCTION get_meta_property;
|
||||
DROP FUNCTION get_meta_link;
|
||||
DROP FUNCTION get_meta_aliases;
|
||||
|
||||
DROP TABLE meta_property;
|
||||
DROP TABLE meta_link_property;
|
||||
DROP TABLE meta_link;
|
||||
DROP TABLE meta_alias;
|
|
@ -1,47 +0,0 @@
|
|||
CREATE TABLE meta_alias (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
pattern BPCHAR NOT NULL,
|
||||
alias BPCHAR NOT NULL,
|
||||
|
||||
CONSTRAINT unique_aliases UNIQUE (alias),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE meta_link (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
pattern BPCHAR NOT NULL,
|
||||
rel BPCHAR NOT NULL,
|
||||
type BPCHAR,
|
||||
href BPCHAR,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE meta_link_property (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
link UUID REFERENCES meta_link (id),
|
||||
rel BPCHAR NOT NULL,
|
||||
value BPCHAR,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE meta_property (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
pattern BPCHAR NOT NULL,
|
||||
value BPCHAR,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE FUNCTION get_meta_aliases(BPCHAR) RETURNS SETOF meta_alias AS $$
|
||||
SELECT * FROM meta_alias WHERE meta_alias.pattern ILIKE $1;
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
CREATE FUNCTION get_meta_link(BPCHAR) RETURNS SETOF meta_link AS $$
|
||||
SELECT * FROM meta_link WHERE meta_link.pattern ILIKE $1;
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
CREATE FUNCTION get_meta_property(BPCHAR) RETURNS SETOF meta_property AS $$
|
||||
SELECT * FROM meta_property WHERE meta_property.pattern ILIKE $1;
|
||||
$$ LANGUAGE SQL;
|
4
acrate-core/migrations/2024-11-14-031744_meta/down.sql
Normal file
4
acrate-core/migrations/2024-11-14-031744_meta/down.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
DROP TABLE IF EXISTS meta_properties CASCADE;
|
||||
DROP TABLE IF EXISTS meta_link_properties CASCADE;
|
||||
DROP TABLE IF EXISTS meta_links CASCADE;
|
||||
DROP TABLE IF EXISTS meta_aliases CASCADE;
|
39
acrate-core/migrations/2024-11-14-031744_meta/up.sql
Normal file
39
acrate-core/migrations/2024-11-14-031744_meta/up.sql
Normal file
|
@ -0,0 +1,39 @@
|
|||
CREATE TABLE meta_aliases (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
document BPCHAR NOT NULL,
|
||||
pattern BPCHAR NOT NULL,
|
||||
alias BPCHAR NOT NULL,
|
||||
|
||||
CONSTRAINT unique_aliases UNIQUE (alias),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE meta_links (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
document BPCHAR NOT NULL,
|
||||
pattern BPCHAR NOT NULL,
|
||||
rel BPCHAR NOT NULL,
|
||||
type BPCHAR,
|
||||
href BPCHAR,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE meta_link_properties (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
meta_link_id UUID REFERENCES meta_links (id) NOT NULL,
|
||||
rel BPCHAR NOT NULL,
|
||||
value BPCHAR,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE meta_properties (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
document BPCHAR NOT NULL,
|
||||
pattern BPCHAR NOT NULL,
|
||||
rel BPCHAR NOT NULL,
|
||||
value BPCHAR,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
|
@ -1,4 +1,8 @@
|
|||
//! Core crate of the `acrate` project.
|
||||
|
||||
pub use acrate_nodeinfo as nodeinfo;
|
||||
pub use acrate_hostmeta as hostmeta;
|
||||
mod schema;
|
||||
|
||||
pub mod meta;
|
||||
|
||||
pub use diesel;
|
||||
pub use diesel_async;
|
||||
|
|
109
acrate-core/src/meta.rs
Normal file
109
acrate-core/src/meta.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use diesel::{Associations, Identifiable, Insertable, QueryResult, Queryable, QueryableByName, Selectable};
|
||||
use diesel_async::AsyncPgConnection;
|
||||
use uuid::Uuid;
|
||||
use super::schema;
|
||||
|
||||
|
||||
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)]
|
||||
#[diesel(table_name = schema::meta_aliases)]
|
||||
pub struct MetaAlias {
|
||||
pub id: Uuid,
|
||||
pub document: String,
|
||||
pub pattern: String,
|
||||
pub alias: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)]
|
||||
#[diesel(table_name = schema::meta_links)]
|
||||
pub struct MetaLink {
|
||||
pub id: Uuid,
|
||||
pub document: String,
|
||||
pub pattern: String,
|
||||
pub rel: String,
|
||||
pub r#type: Option<String>,
|
||||
pub href: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(MetaLink))]
|
||||
#[diesel(table_name = schema::meta_link_properties)]
|
||||
pub struct MetaLinkProperty {
|
||||
pub id: Uuid,
|
||||
pub meta_link_id: Uuid,
|
||||
pub rel: String,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)]
|
||||
#[diesel(table_name = schema::meta_properties)]
|
||||
pub struct MetaProperty {
|
||||
pub id: Uuid,
|
||||
pub document: String,
|
||||
pub pattern: String,
|
||||
pub rel: String,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl MetaAlias {
|
||||
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<MetaAlias>> {
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use schema::meta_aliases::dsl::*;
|
||||
|
||||
let document_is_equal = document.eq(doc);
|
||||
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
||||
|
||||
meta_aliases
|
||||
.filter(document_is_equal)
|
||||
.filter(subject_matches_pattern)
|
||||
.select(Self::as_select())
|
||||
.load(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl MetaLink {
|
||||
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<MetaLink>> {
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use schema::meta_links::dsl::*;
|
||||
|
||||
let document_is_equal = document.eq(doc);
|
||||
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
||||
|
||||
meta_links
|
||||
.filter(document_is_equal)
|
||||
.filter(subject_matches_pattern)
|
||||
.select(Self::as_select())
|
||||
.load(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn query_properties(&self, conn: &mut AsyncPgConnection) -> QueryResult<Vec<MetaLinkProperty>> {
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
MetaLinkProperty::belonging_to(self)
|
||||
.load(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl MetaProperty {
|
||||
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<MetaProperty>> {
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use schema::meta_properties::dsl::*;
|
||||
|
||||
let document_is_equal = document.eq(doc);
|
||||
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
||||
|
||||
meta_properties
|
||||
.filter(document_is_equal)
|
||||
.filter(subject_matches_pattern)
|
||||
.select(Self::as_select())
|
||||
.load(conn)
|
||||
.await
|
||||
}
|
||||
}
|
|
@ -1,46 +1,49 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
meta_alias (id) {
|
||||
meta_aliases (id) {
|
||||
id -> Uuid,
|
||||
document -> Bpchar,
|
||||
pattern -> Bpchar,
|
||||
alias -> Bpchar,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
meta_link (id) {
|
||||
meta_link_properties (id) {
|
||||
id -> Uuid,
|
||||
meta_link_id -> Uuid,
|
||||
rel -> Bpchar,
|
||||
value -> Nullable<Bpchar>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
meta_links (id) {
|
||||
id -> Uuid,
|
||||
document -> Bpchar,
|
||||
pattern -> Bpchar,
|
||||
rel -> Bpchar,
|
||||
#[sql_name = "type"]
|
||||
type_ -> Nullable<Bpchar>,
|
||||
r#type -> Nullable<Bpchar>,
|
||||
href -> Nullable<Bpchar>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
meta_link_property (id) {
|
||||
meta_properties (id) {
|
||||
id -> Uuid,
|
||||
link -> Nullable<Uuid>,
|
||||
document -> Bpchar,
|
||||
pattern -> Bpchar,
|
||||
rel -> Bpchar,
|
||||
value -> Nullable<Bpchar>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
meta_property (id) {
|
||||
id -> Uuid,
|
||||
pattern -> Bpchar,
|
||||
value -> Nullable<Bpchar>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(meta_link_property -> meta_link (link));
|
||||
diesel::joinable!(meta_link_properties -> meta_links (meta_link_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
meta_alias,
|
||||
meta_link,
|
||||
meta_link_property,
|
||||
meta_property,
|
||||
meta_aliases,
|
||||
meta_link_properties,
|
||||
meta_links,
|
||||
meta_properties,
|
||||
);
|
||||
|
|
|
@ -4,6 +4,8 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
acrate-core = { path = "../acrate-core" }
|
||||
acrate-hostmeta = { path = "../acrate-hostmeta" }
|
||||
anyhow = "1.0.93"
|
||||
axum = { version = "0.7.7", features = ["macros"] }
|
||||
axum-extra = { version = "0.9.4", features = ["query"] }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
micronfig::config!(
|
||||
ACRATE_WEBFINGER_DATABASE_URL: String,
|
||||
ACRATE_WEBFINGER_BIND_ADDRESS: String,
|
||||
);
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use axum::http::StatusCode;
|
||||
use axum::body::Body;
|
||||
use axum::http::{HeaderMap, HeaderValue, StatusCode};
|
||||
use axum_extra::extract::Query;
|
||||
use serde::Deserialize;
|
||||
use acrate_core::diesel_async::{AsyncConnection, AsyncPgConnection};
|
||||
use acrate_hostmeta::jrd::ResourceDescriptorLinkJRD;
|
||||
use acrate_hostmeta::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD};
|
||||
use crate::config;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct WebfingerQuery {
|
||||
|
@ -10,13 +15,127 @@ pub struct WebfingerQuery {
|
|||
pub rel: Vec<String>,
|
||||
}
|
||||
|
||||
const WEBFINGER_DOC: &str = "/.well-known/webfinger";
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn webfinger_handler(
|
||||
Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>
|
||||
) -> StatusCode {
|
||||
Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<(Body, HeaderMap), StatusCode> {
|
||||
log::info!("Handling a WebFinger request!");
|
||||
|
||||
log::debug!("Resource is: {resource:#?}");
|
||||
|
||||
log::debug!("Rel is: {rel:#?}");
|
||||
|
||||
StatusCode::NO_CONTENT
|
||||
let accept = headers.get("Accept")
|
||||
.map(|v| v.to_str())
|
||||
.filter(Result::is_ok)
|
||||
.map(|v| v.unwrap())
|
||||
.unwrap_or("application/json")
|
||||
.to_string();
|
||||
log::debug!("Accept is: {accept:#?}");
|
||||
|
||||
let mut response_headers = HeaderMap::new();
|
||||
|
||||
let mut conn = AsyncPgConnection::establish(config::ACRATE_WEBFINGER_DATABASE_URL())
|
||||
.await
|
||||
.map_err(|_| StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
let aliases = acrate_core::meta::MetaAlias::query_matching(&mut conn, WEBFINGER_DOC, &resource)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let properties = acrate_core::meta::MetaProperty::query_matching(&mut conn, WEBFINGER_DOC, &resource)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let links = acrate_core::meta::MetaLink::query_matching(&mut conn, WEBFINGER_DOC, &resource)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
for mime in accept.split(", ") {
|
||||
response_headers.insert("Content-Type", mime.parse().unwrap());
|
||||
|
||||
match mime {
|
||||
"application/json" | "application/jrd+json" => {
|
||||
let subject = Some(resource);
|
||||
|
||||
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
|
||||
.into_iter()
|
||||
.map(|link| ResourceDescriptorLinkJRD {
|
||||
rel: link.rel,
|
||||
r#type: link.r#type,
|
||||
href: link.href,
|
||||
titles: Default::default(), // TODO: Titles
|
||||
properties: Default::default(), // TODO: Link properties
|
||||
template: None, // TODO: Template
|
||||
})
|
||||
.collect::<Vec<ResourceDescriptorLinkJRD>>();
|
||||
|
||||
let rd = acrate_hostmeta::jrd::ResourceDescriptorJRD {
|
||||
subject,
|
||||
aliases,
|
||||
properties,
|
||||
links,
|
||||
};
|
||||
|
||||
let body = rd.
|
||||
|
||||
return Ok(rd, response_headers)
|
||||
},
|
||||
"application/xml" | "application/xrd+xml" => {
|
||||
let subject = Some(resource);
|
||||
|
||||
let aliases = aliases
|
||||
.into_iter()
|
||||
.map(|alias| alias.alias)
|
||||
.collect();
|
||||
|
||||
let properties = properties
|
||||
.into_iter()
|
||||
.map(|prop| ResourceDescriptorPropertyXRD {
|
||||
r#type: prop.rel, // TODO: Ah si chiama type?
|
||||
value: prop.value,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let links = links
|
||||
.into_iter()
|
||||
.map(|link| ResourceDescriptorLinkXRD {
|
||||
rel: link.rel,
|
||||
r#type: link.r#type,
|
||||
href: link.href,
|
||||
titles: Default::default(), // TODO: Titles
|
||||
properties: Default::default(), // TODO: Link properties
|
||||
template: None, // TODO: Template
|
||||
})
|
||||
.collect::<Vec<ResourceDescriptorLinkXRD>>();
|
||||
|
||||
let rd = acrate_hostmeta::xrd::ResourceDescriptorXRD {
|
||||
subject,
|
||||
aliases,
|
||||
properties,
|
||||
links,
|
||||
};
|
||||
|
||||
return Ok(StatusCode::OK)
|
||||
},
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(StatusCode::NOT_ACCEPTABLE)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue