195 lines
5.8 KiB
Rust
195 lines
5.8 KiB
Rust
use async_trait::async_trait;
|
|
use sqlx::Postgres;
|
|
use uuid::Uuid;
|
|
|
|
use crate::content::{
|
|
collection::CollectionData,
|
|
site::{CreateSiteData, SiteData, SiteRepository, SiteShortInfo, UpdateSiteData},
|
|
Error,
|
|
};
|
|
|
|
use super::Database;
|
|
|
|
impl sqlx::Type<Postgres> for CollectionData {
|
|
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
sqlx::postgres::PgTypeInfo::with_name("collection_data")
|
|
}
|
|
}
|
|
|
|
impl<'r> sqlx::Decode<'r, Postgres> for CollectionData {
|
|
fn decode(
|
|
value: sqlx::postgres::PgValueRef<'r>,
|
|
) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
|
|
let mut decoder = sqlx::postgres::types::PgRecordDecoder::new(value)?;
|
|
let id = decoder.try_decode::<Uuid>()?;
|
|
let name = decoder.try_decode::<String>()?;
|
|
let slug = decoder.try_decode::<String>()?;
|
|
let parent = decoder.try_decode::<Option<Uuid>>()?;
|
|
Ok(Self {
|
|
id,
|
|
name,
|
|
slug,
|
|
parent,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl SiteRepository for Database {
|
|
async fn get_site_by_name(&self, name: &str) -> Result<SiteData, Error> {
|
|
let site = sqlx::query_as!(
|
|
SiteData,
|
|
r#"SELECT
|
|
sites.name,
|
|
users.name as owner,
|
|
COALESCE(users.display_name,users.name) as owner_display_name,
|
|
title,
|
|
description,
|
|
sites.created_at,
|
|
default_collection,
|
|
array_agg(row(collections.id, collections.slug, collections.name, parent)) as "collections!: Vec<CollectionData>"
|
|
FROM sites
|
|
JOIN
|
|
users ON sites.owner = users.id
|
|
JOIN
|
|
collections ON collections.site = sites.id
|
|
WHERE
|
|
sites.name = $1
|
|
AND sites.deleted_at IS NULL
|
|
GROUP BY
|
|
sites.name, users.name, users.display_name, title, description, sites.created_at, default_collection"#,
|
|
name
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await
|
|
.map_err(|e| match e {
|
|
sqlx::Error::RowNotFound => Error::NotFound,
|
|
_ => e.into(),
|
|
})?;
|
|
Ok(site)
|
|
}
|
|
|
|
async fn create_site(&self, owner: &Uuid, options: CreateSiteData) -> Result<Uuid, Error> {
|
|
let result = sqlx::query!(
|
|
"INSERT INTO sites (id, name, owner, title, description)
|
|
VALUES ($1, $2, $3, $4, $5) RETURNING id",
|
|
Uuid::now_v7(),
|
|
options.name,
|
|
owner,
|
|
options.title,
|
|
options.description,
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await
|
|
.map_err(|err| match err {
|
|
sqlx::Error::Database(dberr) if dberr.constraint() == Some("sites_name_key") => {
|
|
Error::IdentifierNotAvailable
|
|
}
|
|
_ => err.into(),
|
|
})?;
|
|
Ok(result.id)
|
|
}
|
|
|
|
async fn update_site(
|
|
&self,
|
|
owner: &Uuid,
|
|
name: &str,
|
|
options: UpdateSiteData,
|
|
) -> Result<(), Error> {
|
|
let result = sqlx::query!(
|
|
r#"UPDATE sites SET
|
|
name = COALESCE($1, name),
|
|
title = COALESCE($2, title),
|
|
description = COALESCE($3, description),
|
|
default_collection = COALESCE($4, default_collection),
|
|
modified_at = NOW()
|
|
WHERE
|
|
name = $5
|
|
AND owner = $6
|
|
AND deleted_at IS NULL"#,
|
|
options.name,
|
|
options.title,
|
|
options.description,
|
|
options.default_collection,
|
|
name,
|
|
owner,
|
|
)
|
|
.execute(&self.pool)
|
|
.await
|
|
.map_err(|err| match err {
|
|
sqlx::Error::Database(dberr) if dberr.constraint() == Some("sites_name_key") => {
|
|
Error::IdentifierNotAvailable
|
|
}
|
|
_ => err.into(),
|
|
})?;
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Err(Error::NotFound);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn delete_site(&self, owner: &Uuid, name: &str, soft_delete: bool) -> Result<(), Error> {
|
|
let result = if soft_delete {
|
|
sqlx::query!(
|
|
// soft delete
|
|
"UPDATE sites SET deleted_at = NOW() WHERE name = $1 AND owner = $2 AND deleted_at IS NULL",
|
|
name, owner
|
|
)
|
|
.execute(&self.pool)
|
|
.await?
|
|
} else {
|
|
sqlx::query!(
|
|
// hard delete
|
|
"DELETE FROM sites WHERE name = $1 AND owner = $2",
|
|
name,
|
|
owner,
|
|
)
|
|
.execute(&self.pool)
|
|
.await?
|
|
};
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Err(Error::NotFound);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_site_id_and_owner(&self, name: &str) -> Result<(Uuid, Uuid), Error> {
|
|
let record = sqlx::query!(
|
|
"SELECT id, owner FROM sites WHERE name = $1 AND deleted_at IS NULL",
|
|
name,
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await
|
|
.map_err(|e| match e {
|
|
sqlx::Error::RowNotFound => Error::NotFound,
|
|
_ => e.into(),
|
|
})?;
|
|
Ok((record.id, record.owner))
|
|
}
|
|
|
|
async fn get_sites_by_owner(&self, owner: &Uuid) -> Result<Vec<SiteShortInfo>, Error> {
|
|
let sites = sqlx::query_as!(
|
|
SiteShortInfo,
|
|
r#"SELECT
|
|
name, title, description, created_at
|
|
FROM
|
|
sites
|
|
WHERE
|
|
owner = $1
|
|
"#,
|
|
owner,
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await
|
|
.map_err(|e| match e {
|
|
sqlx::Error::RowNotFound => Error::NotFound,
|
|
_ => e.into(),
|
|
})?;
|
|
Ok(sites)
|
|
}
|
|
}
|