1
Fork 0

database: Heavy refactoring and cleanup

This commit is contained in:
Steffo 2024-11-18 19:55:21 +01:00
parent 1322023e23
commit 7ad60b68f6
Signed by: steffo
GPG key ID: 5ADA3868646C3FC0
3 changed files with 415 additions and 23 deletions

View file

@ -15,6 +15,7 @@ diesel-async = { version = "0.5.1", features = ["postgres"] }
diesel_migrations = { version = "2.2.0", optional = true } diesel_migrations = { version = "2.2.0", optional = true }
log = "0.4.22" log = "0.4.22"
micronfig = { version = "0.3.0", optional = true } micronfig = { version = "0.3.0", optional = true }
mime = "0.3.17"
pretty_env_logger = { version = "0.5.0", optional = true } pretty_env_logger = { version = "0.5.0", optional = true }
uuid = "1.11.0" uuid = "1.11.0"

View file

@ -5,5 +5,7 @@
/// Configured by `diesel.toml`. /// Configured by `diesel.toml`.
mod schema; mod schema;
/// Tables related to web page metadata, XRD and JRD, `host-meta`, `WebFinger`, and so on.
pub mod meta; pub mod meta;
pub use diesel;
pub use diesel_async;

View file

@ -1,88 +1,410 @@
use diesel::{Associations, Identifiable, Insertable, QueryResult, Queryable, QueryableByName, Selectable, pg::Pg}; //! Tables related to web page metadata, XRD and JRD, `host-meta`, `WebFinger`, and so on.
//!
//! # Intended usage
//!
//! 1. An user agent requests an application for the descriptor for a given resource;
//! 2. **The application queries the database using these structures to:**
//! 1. [Determine if a descriptor should be returned, or if it should be handled by another application](MetaSubject);
//! 2. [Determine which aliases the resource has](MetaAlias);
//! 3. [Determine which properties the resource has](MetaProperty);
//! 4. [Determine which links the resource has](MetaLink);
//! 5. [Determine the titles of each link](MetaLinkTitle);
//! 6. [Determine the properties of each link](MetaLinkProperty);
//! 3. The application compiles the data in a single structure that the user agent supports
//! 4. The application returns the data to the user agent
//!
//! # Matching
//!
//! Matching is performed for each single record separately, allowing for example a system administrator to define many separate subjects while globally defining a property for all the resources served.
//!
//! # Reference
//!
//! See [`acrate_rd`] for more information on the specification these structures are based on.
//!
//! # Used by
//!
//! - [`acrate_rdserver`]
//!
use std::str::FromStr;
use diesel::deserialize::FromSql;
use diesel::{AsExpression, Associations, FromSqlRow, Identifiable, Insertable, IntoSql, PgTextExpressionMethods, QueryResult, Queryable, QueryableByName, Selectable, SelectableHelper, ExpressionMethods, BelongingToDsl};
use diesel::pg::{Pg, PgConnection};
use diesel::serialize::{Output, ToSql};
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)] /// Wrapper to use [`mime::Mime`] with [`diesel`].
#[derive(Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression)]
#[diesel(sql_type = diesel::sql_types::Text)]
pub struct Mime(pub mime::Mime);
/// A matchable record denoting the existence of a resource descriptor.
///
/// # See also
///
/// - [`MetaSubjectInsert`]
///
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable)]
#[diesel(table_name = schema::meta_subjects)] #[diesel(table_name = schema::meta_subjects)]
#[diesel(check_for_backend(Pg))] #[diesel(check_for_backend(Pg))]
pub struct MetaSubject { pub struct MetaSubject {
/// The identity column of the record.
pub id: Uuid, pub id: Uuid,
/// The document the record is valid for.
pub document: String, pub document: String,
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
///
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
pub pattern: String, pub pattern: String,
/// The value the `subject` property should take when this record is matched.
///
/// If [`None`], its value is up for specification by the querying application.
pub subject: Option<String>, pub subject: Option<String>,
/// Where the querying application should redirect the user agent to when this record is matched.
///
/// If [`Some`], should always override everything else.
pub redirect: Option<String>, pub redirect: Option<String>,
} }
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)] /// An [`Insertable`] version of [`MetaSubject`].
#[derive(Debug, Insertable)]
#[diesel(table_name = schema::meta_subjects)]
#[diesel(check_for_backend(Pg))]
pub struct MetaSubjectInsert {
/// The document the record is valid for.
pub document: String,
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
///
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
pub pattern: String,
/// The value the `subject` property should take when this record is matched.
///
/// If [`None`], its value is up for specification by the querying application.
pub subject: Option<String>,
/// Where the querying application should redirect the user agent to when this record is matched.
///
/// If [`Some`], should always override everything else.
pub redirect: Option<String>,
}
/// A matchable record denoting an alias belonging to a subject.
///
/// # See also
///
/// - [`MetaAliasInsert`]
///
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable)]
#[diesel(table_name = schema::meta_aliases)] #[diesel(table_name = schema::meta_aliases)]
#[diesel(check_for_backend(Pg))] #[diesel(check_for_backend(Pg))]
pub struct MetaAlias { pub struct MetaAlias {
/// The identity column of the record.
pub id: Uuid, pub id: Uuid,
/// The document the record is valid for.
pub document: String, pub document: String,
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
///
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
pub pattern: String, pub pattern: String,
/// The alias to **add** to the list of aliases of the resource.
pub alias: String, pub alias: String,
} }
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)] /// An [`Insertable`] version of [`MetaAlias`].
#[derive(Debug, Insertable)]
#[diesel(table_name = schema::meta_aliases)]
#[diesel(check_for_backend(Pg))]
pub struct MetaAliasInsert {
/// The document the record is valid for.
pub document: String,
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
///
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
pub pattern: String,
/// The alias to **add** to the list of aliases of the resource.
pub alias: String,
}
/// A matchable record denoting a link towards another resource someway related with the subject.
///
/// # See also
///
/// - [`MetaLinkInsert`]
///
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable)]
#[diesel(table_name = schema::meta_links)] #[diesel(table_name = schema::meta_links)]
#[diesel(check_for_backend(Pg))] #[diesel(check_for_backend(Pg))]
pub struct MetaLink { pub struct MetaLink {
/// The identity column of the record.
pub id: Uuid, pub id: Uuid,
/// The document the record is valid for.
pub document: String, pub document: String,
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
///
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
pub pattern: String, pub pattern: String,
/// The relationship the link establishes between itself and the value of the link.
pub rel: String, pub rel: String,
pub type_: Option<String>,
/// The media type of the value of the link.
///
/// Can be [`None`] if it shouldn't be specified.
pub type_: Option<Mime>,
/// The URI to the document this property is linking the subject to.
///
/// Can be [`None`] if it shouldn't be specified, for example if [`Self::template`] is [`Some`].
pub href: Option<String>, pub href: Option<String>,
/// The template to the document this property is linking the subject to.
///
/// Can be [`None`] if it shouldn't be specified, for example if [`Self::href`] is [`Some`].
pub template: Option<String>, pub template: Option<String>,
} }
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable, Associations)] /// An [`Insertable`] version of [`MetaLink`].
#[derive(Debug, Insertable)]
#[diesel(table_name = schema::meta_links)]
#[diesel(check_for_backend(Pg))]
pub struct MetaLinkInsert {
/// The document the record is valid for.
pub document: String,
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
///
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
pub pattern: String,
/// The relationship the link establishes between itself and the value of the link.
pub rel: String,
/// The media type of the value of the link.
///
/// Can be [`None`] if it shouldn't be specified.
pub type_: Option<Mime>,
/// The URI to the document this property is linking the subject to.
///
/// Can be [`None`] if it shouldn't be specified, for example if [`Self::template`] is [`Some`].
pub href: Option<String>,
/// The template to the document this property is linking the subject to.
///
/// Can be [`None`] if it shouldn't be specified, for example if [`Self::href`] is [`Some`].
pub template: Option<String>,
}
/// A property that a [`MetaLink`] has.
///
/// # See also
///
/// - [`MetaLinkPropertyInsert`]
///
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, 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))] #[diesel(check_for_backend(Pg))]
pub struct MetaLinkProperty { pub struct MetaLinkProperty {
/// The identity column of the record.
pub id: Uuid, pub id: Uuid,
/// The [`MetaLink::id`] this record refers to.
pub meta_link_id: Uuid, pub meta_link_id: Uuid,
/// The relationship the property establishes between the [`MetaLink`] and the [`MetaLinkProperty::value`].
pub rel: String, pub rel: String,
/// The value that the property has.
pub value: Option<String>, pub value: Option<String>,
} }
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable, Associations)] /// An [`Insertable`] version of [`MetaLinkProperty`].
#[derive(Debug, Insertable)]
#[diesel(belongs_to(MetaLink))]
#[diesel(table_name = schema::meta_link_properties)]
#[diesel(check_for_backend(Pg))]
pub struct MetaLinkPropertyInsert {
/// The [`MetaLink::id`] this record refers to.
pub meta_link_id: Uuid,
/// The relationship the property establishes between the [`MetaLink`] and the [`MetaLinkProperty::value`].
pub rel: String,
/// The value that the property has.
pub value: Option<String>,
}
/// A title that a [`MetaLink`] has in a certain language.
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Associations)]
#[diesel(belongs_to(MetaLink))] #[diesel(belongs_to(MetaLink))]
#[diesel(table_name = schema::meta_link_titles)] #[diesel(table_name = schema::meta_link_titles)]
#[diesel(check_for_backend(Pg))] #[diesel(check_for_backend(Pg))]
pub struct MetaLinkTitle { pub struct MetaLinkTitle {
/// The identity column of the record.
pub id: Uuid, pub id: Uuid,
/// The [`MetaLink::id`] this record refers to.
pub meta_link_id: Uuid, pub meta_link_id: Uuid,
/// The language of the title.
pub language: String, pub language: String,
/// The actual contents of the title.
pub value: String, pub value: String,
} }
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Insertable)] /// An [`Insertable`] version of [`MetaLinkTitle`].
#[derive(Debug, Insertable)]
#[diesel(belongs_to(MetaLink))]
#[diesel(table_name = schema::meta_link_titles)]
#[diesel(check_for_backend(Pg))]
pub struct MetaLinkTitleInsert {
/// The [`MetaLink::id`] this record refers to.
pub meta_link_id: Uuid,
/// The language of the title.
pub language: String,
/// The actual contents of the title.
pub value: String,
}
/// A matchable record denoting a property that a subject has.
///
/// # See also
///
/// - [`MetaPropertyInsert`]
///
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable)]
#[diesel(table_name = schema::meta_properties)] #[diesel(table_name = schema::meta_properties)]
#[diesel(check_for_backend(Pg))] #[diesel(check_for_backend(Pg))]
pub struct MetaProperty { pub struct MetaProperty {
/// The identity column of the record.
pub id: Uuid, pub id: Uuid,
/// The document the record is valid for.
pub document: String, pub document: String,
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
///
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
pub pattern: String, pub pattern: String,
/// The relationship the property establishes between the [`MetaSubject`] and the [`MetaProperty::value`].
pub rel: String, pub rel: String,
/// The value that the property has.
pub value: Option<String>, pub value: Option<String>,
} }
/// An [`Insertable`] version of [`MetaLinkTitle`].
#[derive(Debug, Insertable)]
#[diesel(table_name = schema::meta_properties)]
#[diesel(check_for_backend(Pg))]
pub struct MetaPropertyInsert {
/// The document the record is valid for.
pub document: String,
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
///
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
pub pattern: String,
/// The relationship the property establishes between the [`MetaSubject`] and the [`MetaProperty::value`].
pub rel: String,
/// The value that the property has.
pub value: Option<String>,
}
/// Allow [`diesel::sql_types::Text`] values to be parsed as [`Mime`].
impl<DB> FromSql<diesel::sql_types::Text, DB> for Mime
where
DB: diesel::backend::Backend,
String: FromSql<diesel::sql_types::Text, DB>,
{
fn from_sql(bytes: <DB as diesel::backend::Backend>::RawValue<'_>) -> diesel::deserialize::Result<Self> {
let s = <String as FromSql<diesel::sql_types::Text, DB>>::from_sql(bytes)?;
let mime = mime::Mime::from_str(&s)?;
Ok(Self(mime))
}
}
/// Allow [`diesel::sql_types::Text`] values to be written to with [`Mime`].
impl<DB> ToSql<diesel::sql_types::Text, DB> for Mime
where
DB: diesel::backend::Backend,
str: ToSql<diesel::sql_types::Text, DB>,
{
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
let mime = self.0.essence_str();
<str as ToSql<diesel::sql_types::Text, DB>>::to_sql(mime, out)
}
}
impl MetaSubject { impl MetaSubject {
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject_: &str) -> QueryResult<Vec<Self>> { /// Synchronously query the records matching the given document and resource.
use diesel::prelude::*; pub fn query_matching(conn: &mut PgConnection, doc: &str, resource: &str) -> QueryResult<Vec<Self>> {
use diesel::QueryDsl;
use diesel::RunQueryDsl;
use schema::meta_subjects::dsl::*;
let document_is_equal = document.eq(doc);
let resource_matches_pattern = resource.into_sql::<diesel::sql_types::Text>().ilike(pattern);
meta_subjects
.filter(document_is_equal)
.filter(resource_matches_pattern)
.select(Self::as_select())
.load(conn)
}
/// Asynchronously query the records matching the given document and resource.
pub async fn aquery_matching(conn: &mut AsyncPgConnection, doc: &str, resource: &str) -> QueryResult<Vec<Self>> {
use diesel::QueryDsl;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use schema::meta_subjects::dsl::*; use schema::meta_subjects::dsl::*;
let document_is_equal = document.eq(doc); let document_is_equal = document.eq(doc);
let subject_matches_pattern = subject_.into_sql::<diesel::sql_types::Text>().ilike(pattern); let resource_matches_pattern = resource.into_sql::<diesel::sql_types::Text>().ilike(pattern);
meta_subjects meta_subjects
.filter(document_is_equal) .filter(document_is_equal)
.filter(subject_matches_pattern) .filter(resource_matches_pattern)
.select(Self::as_select()) .select(Self::as_select())
.load(conn) .load(conn)
.await .await
@ -90,8 +412,25 @@ impl MetaSubject {
} }
impl MetaAlias { impl MetaAlias {
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> { /// Synchronously query the records matching the given document and resource.
use diesel::prelude::*; pub fn query_matching(conn: &mut PgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
use diesel::QueryDsl;
use diesel::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)
}
/// Asynchronously query the records matching the given document and resource.
pub async fn aquery_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
use diesel::QueryDsl;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use schema::meta_aliases::dsl::*; use schema::meta_aliases::dsl::*;
@ -108,8 +447,25 @@ impl MetaAlias {
} }
impl MetaLink { impl MetaLink {
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> { /// Synchronously query the records matching the given document and resource.
use diesel::prelude::*; pub async fn query_matching(conn: &mut PgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
use diesel::QueryDsl;
use diesel::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)
}
/// Asynchronously query the records matching the given document and resource.
pub async fn aquery_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
use diesel::QueryDsl;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use schema::meta_links::dsl::*; use schema::meta_links::dsl::*;
@ -126,8 +482,16 @@ impl MetaLink {
} }
impl MetaLinkProperty { impl MetaLinkProperty {
pub async fn query_by_link(conn: &mut AsyncPgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> { /// Synchronously query the records belonging to the given [`MetaLink`]s.
use diesel::prelude::*; pub fn query_by_link(conn: &mut PgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
use diesel::RunQueryDsl;
Self::belonging_to(links)
.load(conn)
}
/// Asynchronously query the records belonging to the given [`MetaLink`]s.
pub async fn aquery_by_link(conn: &mut AsyncPgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
Self::belonging_to(links) Self::belonging_to(links)
@ -137,8 +501,16 @@ impl MetaLinkProperty {
} }
impl MetaLinkTitle { impl MetaLinkTitle {
pub async fn query_by_link(conn: &mut AsyncPgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> { /// Synchronously query the records belonging to the given [`MetaLink`]s.
use diesel::prelude::*; pub fn query_by_link(conn: &mut PgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
use diesel::RunQueryDsl;
Self::belonging_to(links)
.load(conn)
}
/// Asynchronously query the records belonging to the given [`MetaLink`]s.
pub async fn aquery_by_link(conn: &mut AsyncPgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
Self::belonging_to(links) Self::belonging_to(links)
@ -148,8 +520,25 @@ impl MetaLinkTitle {
} }
impl MetaProperty { impl MetaProperty {
pub async fn query_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> { /// Synchronously query the records matching the given document and resource.
use diesel::prelude::*; pub fn query_matching(conn: &mut PgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
use diesel::QueryDsl;
use diesel::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)
}
/// Asynchronously query the records matching the given document and resource.
pub async fn aquery_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
use diesel::QueryDsl;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use schema::meta_properties::dsl::*; use schema::meta_properties::dsl::*;