This commit is contained in:
parent
3d50d2b4b0
commit
e626ef87dc
5 changed files with 59 additions and 59 deletions
|
@ -1,4 +1,4 @@
|
||||||
pub mod page;
|
pub mod post;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -7,8 +7,8 @@ use uuid::Uuid;
|
||||||
use super::Error;
|
use super::Error;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, FromRow)]
|
#[derive(Deserialize, Serialize, FromRow)]
|
||||||
pub struct Page {
|
pub struct Post {
|
||||||
/// Page ID
|
/// Post ID
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
|
|
||||||
/// Site ID
|
/// Site ID
|
||||||
|
@ -17,20 +17,20 @@ pub struct Page {
|
||||||
/// Author ID
|
/// Author ID
|
||||||
pub author: Uuid,
|
pub author: Uuid,
|
||||||
|
|
||||||
/// URL-friendly short code for the page
|
/// URL-friendly short code for the post
|
||||||
pub slug: String,
|
pub slug: String,
|
||||||
|
|
||||||
/// Page title
|
/// Post title
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
|
||||||
/// Page description (for SEO/content)
|
/// Post description (for SEO/content)
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
|
||||||
/// Page tags (for internal search)
|
/// Post tags (for internal search)
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
|
||||||
/// Page blocks (content)
|
/// Post blocks (content)
|
||||||
pub blocks: Json<Vec<PageBlock>>,
|
pub blocks: Json<Vec<PostBlock>>,
|
||||||
|
|
||||||
/// Times
|
/// Times
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
|
@ -40,7 +40,7 @@ pub struct Page {
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
pub enum PageBlock {
|
pub enum PostBlock {
|
||||||
MarkupV1 {
|
MarkupV1 {
|
||||||
/// Markup format (markdown, html, plain)
|
/// Markup format (markdown, html, plain)
|
||||||
format: String,
|
format: String,
|
||||||
|
@ -73,50 +73,50 @@ pub struct ImageElement {
|
||||||
pub caption: Option<String>,
|
pub caption: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data to create a new page
|
/// Data to create a new post
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CreatePageData {
|
pub struct CreatePostData {
|
||||||
pub slug: String,
|
pub slug: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub blocks: Vec<PageBlock>,
|
pub blocks: Vec<PostBlock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data to update a new page
|
/// Data to update a new post
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpdatePageData {
|
pub struct UpdatePostData {
|
||||||
pub slug: Option<String>,
|
pub slug: Option<String>,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
pub blocks: Option<Vec<PageBlock>>,
|
pub blocks: Option<Vec<PostBlock>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait PageRepository {
|
pub trait PostRepository {
|
||||||
/// Get a page from its slug
|
/// Get a post from its slug
|
||||||
async fn get_page_from_url(&self, site: &str, slug: &str) -> Result<Page, Error>;
|
async fn get_post_from_url(&self, site: &str, slug: &str) -> Result<Post, Error>;
|
||||||
|
|
||||||
/// Create a new page
|
/// Create a new post
|
||||||
async fn create_page(
|
async fn create_post(
|
||||||
&self,
|
&self,
|
||||||
owner: &Uuid,
|
owner: &Uuid,
|
||||||
site: &Uuid,
|
site: &Uuid,
|
||||||
data: CreatePageData,
|
data: CreatePostData,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Update a page
|
/// Update a post
|
||||||
async fn update_page(
|
async fn update_post(
|
||||||
&self,
|
&self,
|
||||||
owner: &Uuid,
|
owner: &Uuid,
|
||||||
site: &Uuid,
|
site: &Uuid,
|
||||||
slug: &str,
|
slug: &str,
|
||||||
data: UpdatePageData,
|
data: UpdatePostData,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Delete a page
|
/// Delete a post
|
||||||
async fn delete_page(
|
async fn delete_post(
|
||||||
&self,
|
&self,
|
||||||
owner: &Uuid,
|
owner: &Uuid,
|
||||||
site: &Uuid,
|
site: &Uuid,
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
pub mod page;
|
pub mod post;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
|
|
|
@ -4,34 +4,34 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::content::{
|
use crate::content::{
|
||||||
self,
|
self,
|
||||||
page::{CreatePageData, Page, PageRepository, UpdatePageData},
|
post::{CreatePostData, Post, PostRepository, UpdatePostData},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Database;
|
use super::Database;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl PageRepository for Database {
|
impl PostRepository for Database {
|
||||||
async fn get_page_from_url(&self, site: &str, slug: &str) -> Result<Page, content::Error> {
|
async fn get_post_from_url(&self, site: &str, slug: &str) -> Result<Post, content::Error> {
|
||||||
let page_query = sqlx::query_as(
|
let post_query = sqlx::query_as(
|
||||||
"SELECT pages.* FROM pages JOIN sites ON site = sites.id WHERE slug = $1 AND name = $2 AND pages.deleted_at IS NULL AND sites.deleted_at IS NULL")
|
"SELECT pages.* FROM pages JOIN sites ON site = sites.id WHERE slug = $1 AND name = $2 AND pages.deleted_at IS NULL AND sites.deleted_at IS NULL")
|
||||||
.bind(slug)
|
.bind(slug)
|
||||||
.bind(site);
|
.bind(site);
|
||||||
let page: Page = page_query
|
let post: Post = post_query
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
sqlx::Error::RowNotFound => Error::NotFound,
|
sqlx::Error::RowNotFound => Error::NotFound,
|
||||||
_ => e.into(),
|
_ => e.into(),
|
||||||
})?;
|
})?;
|
||||||
Ok(page)
|
Ok(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_page(
|
async fn create_post(
|
||||||
&self,
|
&self,
|
||||||
owner: &Uuid,
|
owner: &Uuid,
|
||||||
site: &Uuid,
|
site: &Uuid,
|
||||||
data: CreatePageData,
|
data: CreatePostData,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO pages (author, site, slug, title, description, tags, blocks) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
"INSERT INTO pages (author, site, slug, title, description, tags, blocks) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||||
|
@ -51,12 +51,12 @@ impl PageRepository for Database {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_page(
|
async fn update_post(
|
||||||
&self,
|
&self,
|
||||||
owner: &Uuid,
|
owner: &Uuid,
|
||||||
site: &Uuid,
|
site: &Uuid,
|
||||||
page: &str,
|
slug: &str,
|
||||||
data: UpdatePageData,
|
data: UpdatePostData,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
"UPDATE pages SET title = COALESCE($1, title), description = COALESCE($2, description), tags = COALESCE($3, tags), blocks = COALESCE($4, blocks) WHERE author = $5 AND site = $6 AND slug = $7 AND deleted_at IS NULL",
|
"UPDATE pages SET title = COALESCE($1, title), description = COALESCE($2, description), tags = COALESCE($3, tags), blocks = COALESCE($4, blocks) WHERE author = $5 AND site = $6 AND slug = $7 AND deleted_at IS NULL",
|
||||||
|
@ -66,7 +66,7 @@ impl PageRepository for Database {
|
||||||
data.blocks.map(|x| json!(x)),
|
data.blocks.map(|x| json!(x)),
|
||||||
owner,
|
owner,
|
||||||
site,
|
site,
|
||||||
page
|
slug
|
||||||
).execute(&self.pool).await.map_err(|err| match err {
|
).execute(&self.pool).await.map_err(|err| match err {
|
||||||
sqlx::Error::Database(dberr) if dberr.constraint() == Some("pages_site_slug_unique") => {
|
sqlx::Error::Database(dberr) if dberr.constraint() == Some("pages_site_slug_unique") => {
|
||||||
Error::IdentifierNotAvailable
|
Error::IdentifierNotAvailable
|
||||||
|
@ -81,11 +81,11 @@ impl PageRepository for Database {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_page(
|
async fn delete_post(
|
||||||
&self,
|
&self,
|
||||||
owner: &Uuid,
|
owner: &Uuid,
|
||||||
site: &Uuid,
|
site: &Uuid,
|
||||||
page: &str,
|
slug: &str,
|
||||||
soft_delete: bool,
|
soft_delete: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let result = if soft_delete {
|
let result = if soft_delete {
|
||||||
|
@ -93,14 +93,14 @@ impl PageRepository for Database {
|
||||||
"UPDATE pages SET deleted_at = NOW() WHERE author = $1 AND site = $2 AND slug = $3 AND deleted_at IS NULL",
|
"UPDATE pages SET deleted_at = NOW() WHERE author = $1 AND site = $2 AND slug = $3 AND deleted_at IS NULL",
|
||||||
owner,
|
owner,
|
||||||
site,
|
site,
|
||||||
page
|
slug
|
||||||
).execute(&self.pool).await?
|
).execute(&self.pool).await?
|
||||||
} else {
|
} else {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"DELETE FROM pages WHERE author = $1 AND site = $2 AND slug = $3",
|
"DELETE FROM pages WHERE author = $1 AND site = $2 AND slug = $3",
|
||||||
owner,
|
owner,
|
||||||
site,
|
site,
|
||||||
page
|
slug
|
||||||
)
|
)
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
.await?
|
.await?
|
|
@ -9,7 +9,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
content::{
|
content::{
|
||||||
page::{CreatePageData, PageRepository, UpdatePageData},
|
post::{CreatePostData, PostRepository, UpdatePostData},
|
||||||
site::SiteRepository,
|
site::SiteRepository,
|
||||||
},
|
},
|
||||||
database::Database,
|
database::Database,
|
||||||
|
@ -17,42 +17,42 @@ use crate::{
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn get_page<Repo: PageRepository>(
|
async fn get_post<Repo: PostRepository>(
|
||||||
repository: Repo,
|
repository: Repo,
|
||||||
Path((site, slug)): Path<(String, String)>,
|
Path((site, slug)): Path<(String, String)>,
|
||||||
) -> Result<impl IntoResponse, ApiError<'static>> {
|
) -> Result<impl IntoResponse, ApiError<'static>> {
|
||||||
Ok(Json(repository.get_page_from_url(&site, &slug).await?))
|
Ok(Json(repository.get_post_from_url(&site, &slug).await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_page<Repo: PageRepository + SiteRepository>(
|
async fn create_post<Repo: PostRepository + SiteRepository>(
|
||||||
repository: Repo,
|
repository: Repo,
|
||||||
Path(site): Path<String>,
|
Path(site): Path<String>,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(user): RequireUser,
|
||||||
JsonBody(page): JsonBody<CreatePageData>,
|
JsonBody(data): JsonBody<CreatePostData>,
|
||||||
) -> Result<impl IntoResponse, ApiError<'static>> {
|
) -> Result<impl IntoResponse, ApiError<'static>> {
|
||||||
// Resolve site
|
// Resolve site
|
||||||
let site_id = repository.get_site_id(&site, &user.id).await?;
|
let site_id = repository.get_site_id(&site, &user.id).await?;
|
||||||
|
|
||||||
repository.create_page(&user.id, &site_id, page).await?;
|
repository.create_post(&user.id, &site_id, data).await?;
|
||||||
Ok(Json(json!({ "ok": true })))
|
Ok(Json(json!({ "ok": true })))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_page<Repo: PageRepository + SiteRepository>(
|
async fn update_post<Repo: PostRepository + SiteRepository>(
|
||||||
repository: Repo,
|
repository: Repo,
|
||||||
Path((site, slug)): Path<(String, String)>,
|
Path((site, slug)): Path<(String, String)>,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(user): RequireUser,
|
||||||
JsonBody(page): JsonBody<UpdatePageData>,
|
JsonBody(data): JsonBody<UpdatePostData>,
|
||||||
) -> Result<impl IntoResponse, ApiError<'static>> {
|
) -> Result<impl IntoResponse, ApiError<'static>> {
|
||||||
// Resolve site
|
// Resolve site
|
||||||
let site_id = repository.get_site_id(&site, &user.id).await?;
|
let site_id = repository.get_site_id(&site, &user.id).await?;
|
||||||
|
|
||||||
repository
|
repository
|
||||||
.update_page(&user.id, &site_id, &slug, page)
|
.update_post(&user.id, &site_id, &slug, data)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Json(json!({ "ok": true })))
|
Ok(Json(json!({ "ok": true })))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_page<Repo: PageRepository + SiteRepository>(
|
async fn delete_post<Repo: PostRepository + SiteRepository>(
|
||||||
repository: Repo,
|
repository: Repo,
|
||||||
Path((site, slug)): Path<(String, String)>,
|
Path((site, slug)): Path<(String, String)>,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(user): RequireUser,
|
||||||
|
@ -61,18 +61,18 @@ async fn delete_page<Repo: PageRepository + SiteRepository>(
|
||||||
let site_id = repository.get_site_id(&site, &user.id).await?;
|
let site_id = repository.get_site_id(&site, &user.id).await?;
|
||||||
|
|
||||||
repository
|
repository
|
||||||
.delete_page(&user.id, &site_id, &slug, true)
|
.delete_post(&user.id, &site_id, &slug, true)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Json(json!({ "ok": true })))
|
Ok(Json(json!({ "ok": true })))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn router() -> Router<Arc<AppState>> {
|
pub fn router() -> Router<Arc<AppState>> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/:site", post(create_page::<Database>))
|
.route("/:site", post(create_post::<Database>))
|
||||||
.route(
|
.route(
|
||||||
"/:site/:slug",
|
"/:site/:slug",
|
||||||
get(get_page::<Database>)
|
get(get_post::<Database>)
|
||||||
.put(update_page::<Database>)
|
.put(update_post::<Database>)
|
||||||
.delete(delete_page::<Database>),
|
.delete(delete_post::<Database>),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue