add collections and private posts
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
f5c7570d9d
commit
203df76d6c
18 changed files with 221 additions and 27 deletions
5
migrations/20230713090533_add-collection.down.sql
Normal file
5
migrations/20230713090533_add-collection.down.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
ALTER TABLE pages
|
||||||
|
DROP COLUMN collections,
|
||||||
|
DROP COLUMN published;
|
||||||
|
|
||||||
|
DROP TABLE collections;
|
13
migrations/20230713090533_add-collection.up.sql
Normal file
13
migrations/20230713090533_add-collection.up.sql
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- noqa: RF05
|
||||||
|
|
||||||
|
CREATE TABLE collections (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
site UUID NOT NULL REFERENCES sites (id) ON DELETE CASCADE,
|
||||||
|
slug VARCHAR NOT NULL,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
parent UUID REFERENCES collections (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE pages
|
||||||
|
ADD COLUMN collections UUID [] NOT NULL DEFAULT array[]::UUID[], -- noqa
|
||||||
|
ADD COLUMN published BOOLEAN NOT NULL DEFAULT FALSE;
|
|
@ -0,0 +1,4 @@
|
||||||
|
-- Remove constraint and index (collections_site_slug_unique, collections_site_slug_idx)
|
||||||
|
ALTER TABLE collections DROP CONSTRAINT collections_site_slug_unique;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS collections_site_slug_unique;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Add index/constraint on site and collection slug
|
||||||
|
CREATE UNIQUE INDEX collections_site_slug_idx
|
||||||
|
ON collections (site, slug);
|
||||||
|
|
||||||
|
ALTER TABLE collections
|
||||||
|
ADD CONSTRAINT collections_site_slug_unique UNIQUE
|
||||||
|
USING INDEX collections_site_slug_idx;
|
32
src/content/collection.rs
Normal file
32
src/content/collection.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::FromRow;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::Error;
|
||||||
|
|
||||||
|
pub const DEFAULT_COLLECTIONS: [(&str, &str); 2] = [("blog", "Blog"), ("pages", "Pages")];
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, FromRow)]
|
||||||
|
pub struct Collection {
|
||||||
|
/// Collection ID
|
||||||
|
pub id: Uuid,
|
||||||
|
|
||||||
|
/// Site ID
|
||||||
|
pub site: Uuid,
|
||||||
|
|
||||||
|
/// Collection URL name
|
||||||
|
pub slug: String,
|
||||||
|
|
||||||
|
/// Collection display name
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// Collection parent (eg. blog categories have blog as parent)
|
||||||
|
pub parent: Option<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait CollectionRepository {
|
||||||
|
/// Create default collections for a site
|
||||||
|
async fn create_default_collections(&self, site: &Uuid) -> Result<(), Error>;
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod collection;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,12 @@ pub struct Post {
|
||||||
/// Post blocks (content)
|
/// Post blocks (content)
|
||||||
pub blocks: Json<Vec<PostBlock>>,
|
pub blocks: Json<Vec<PostBlock>>,
|
||||||
|
|
||||||
|
/// Collections the post belongs to
|
||||||
|
pub collections: Vec<Uuid>,
|
||||||
|
|
||||||
|
/// Is the post published?
|
||||||
|
pub published: bool,
|
||||||
|
|
||||||
/// Times
|
/// Times
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub modified_at: Option<NaiveDateTime>,
|
pub modified_at: Option<NaiveDateTime>,
|
||||||
|
@ -81,6 +87,8 @@ pub struct CreatePostData {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub blocks: Vec<PostBlock>,
|
pub blocks: Vec<PostBlock>,
|
||||||
|
pub collections: Vec<Uuid>,
|
||||||
|
pub published: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data to update a new post
|
/// Data to update a new post
|
||||||
|
@ -91,6 +99,8 @@ pub struct UpdatePostData {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
pub blocks: Option<Vec<PostBlock>>,
|
pub blocks: Option<Vec<PostBlock>>,
|
||||||
|
pub collections: Option<Vec<Uuid>>,
|
||||||
|
pub published: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Post with site and author dereferenced for convenience
|
/// Post with site and author dereferenced for convenience
|
||||||
|
@ -106,6 +116,7 @@ pub struct PostWithAuthor {
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub modified_at: Option<NaiveDateTime>,
|
pub modified_at: Option<NaiveDateTime>,
|
||||||
pub deleted_at: Option<NaiveDateTime>,
|
pub deleted_at: Option<NaiveDateTime>,
|
||||||
|
pub published: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
|
@ -31,13 +31,20 @@ pub struct Site {
|
||||||
|
|
||||||
/// More useful version of Site for showing to users
|
/// More useful version of Site for showing to users
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct SiteWithAuthor {
|
pub struct SiteData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub owner: String,
|
pub owner: String,
|
||||||
pub owner_display_name: Option<String>,
|
pub owner_display_name: Option<String>,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
|
pub collections: Vec<CollectionNameAndSlug>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct CollectionNameAndSlug {
|
||||||
|
pub name: String,
|
||||||
|
pub slug: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data required to create a new site
|
/// Data required to create a new site
|
||||||
|
@ -59,10 +66,10 @@ pub struct UpdateSiteData {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait SiteRepository {
|
pub trait SiteRepository {
|
||||||
/// Retrieve site info from site name/slug
|
/// Retrieve site info from site name/slug
|
||||||
async fn get_site_by_name(&self, name: &str) -> Result<SiteWithAuthor, Error>;
|
async fn get_site_by_name(&self, name: &str) -> Result<SiteData, Error>;
|
||||||
|
|
||||||
/// Create a new instance of a site and store it
|
/// Create a new instance of a site and store it
|
||||||
async fn create_site(&self, owner: &Uuid, options: CreateSiteData) -> Result<(), Error>;
|
async fn create_site(&self, owner: &Uuid, options: CreateSiteData) -> Result<Uuid, Error>;
|
||||||
|
|
||||||
/// Update an existing site's info
|
/// Update an existing site's info
|
||||||
async fn update_site(
|
async fn update_site(
|
||||||
|
|
31
src/database/collection.rs
Normal file
31
src/database/collection.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::content::{
|
||||||
|
collection::{CollectionRepository, DEFAULT_COLLECTIONS},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Database;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl CollectionRepository for Database {
|
||||||
|
async fn create_default_collections(&self, site: &Uuid) -> Result<(), Error> {
|
||||||
|
let mut tx = self.pool.begin().await?;
|
||||||
|
|
||||||
|
for (slug, name) in DEFAULT_COLLECTIONS {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"INSERT INTO collections (site, slug, name) VALUES ($1, $2, $3)"#,
|
||||||
|
site,
|
||||||
|
slug,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.execute(&mut tx)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
pub mod collection;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ impl PostRepository for Database {
|
||||||
pages.blocks as "blocks!: Json<Vec<PostBlock>>",
|
pages.blocks as "blocks!: Json<Vec<PostBlock>>",
|
||||||
pages.created_at,
|
pages.created_at,
|
||||||
pages.modified_at,
|
pages.modified_at,
|
||||||
pages.deleted_at
|
pages.deleted_at,
|
||||||
|
pages.published
|
||||||
FROM pages
|
FROM pages
|
||||||
JOIN
|
JOIN
|
||||||
sites ON site = sites.id
|
sites ON site = sites.id
|
||||||
|
@ -61,7 +62,7 @@ impl PostRepository for Database {
|
||||||
data: CreatePostData,
|
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, collections, published) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
owner,
|
owner,
|
||||||
site,
|
site,
|
||||||
data.slug,
|
data.slug,
|
||||||
|
@ -69,6 +70,8 @@ impl PostRepository for Database {
|
||||||
data.description.unwrap_or_else(|| "".to_string()),
|
data.description.unwrap_or_else(|| "".to_string()),
|
||||||
&data.tags,
|
&data.tags,
|
||||||
json!(data.blocks),
|
json!(data.blocks),
|
||||||
|
&data.collections,
|
||||||
|
data.published
|
||||||
).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
|
||||||
|
@ -86,15 +89,18 @@ impl PostRepository for Database {
|
||||||
data: UpdatePostData,
|
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), collections = COALESCE($8, collections), published = COALESCE($9, published) WHERE author = $5 AND site = $6 AND slug = $7 AND deleted_at IS NULL",
|
||||||
data.title,
|
data.title,
|
||||||
data.description,
|
data.description,
|
||||||
data.tags.as_deref(),
|
data.tags.as_deref(),
|
||||||
data.blocks.map(|x| json!(x)),
|
data.blocks.map(|x| json!(x)),
|
||||||
owner,
|
owner,
|
||||||
site,
|
site,
|
||||||
slug
|
slug,
|
||||||
).execute(&self.pool).await.map_err(|err| match err {
|
data.collections.as_deref(),
|
||||||
|
data.published
|
||||||
|
)
|
||||||
|
.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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,53 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use sqlx::Postgres;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::content::{
|
use crate::content::{
|
||||||
site::{CreateSiteData, SiteRepository, SiteWithAuthor, UpdateSiteData},
|
site::{CollectionNameAndSlug, CreateSiteData, SiteData, SiteRepository, UpdateSiteData},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Database;
|
use super::Database;
|
||||||
|
|
||||||
|
impl sqlx::Type<Postgres> for CollectionNameAndSlug {
|
||||||
|
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
||||||
|
sqlx::postgres::PgTypeInfo::with_name("collection_name_slug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> sqlx::Decode<'r, Postgres> for CollectionNameAndSlug {
|
||||||
|
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 slug = decoder.try_decode::<String>()?;
|
||||||
|
let name = decoder.try_decode::<String>()?;
|
||||||
|
Ok(Self { name, slug })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl SiteRepository for Database {
|
impl SiteRepository for Database {
|
||||||
async fn get_site_by_name(&self, name: &str) -> Result<SiteWithAuthor, Error> {
|
async fn get_site_by_name(&self, name: &str) -> Result<SiteData, Error> {
|
||||||
let site = sqlx::query_as!(
|
let site = sqlx::query!(
|
||||||
SiteWithAuthor,
|
|
||||||
r#"SELECT
|
r#"SELECT
|
||||||
sites.name,
|
sites.name,
|
||||||
users.name as owner,
|
users.name as owner,
|
||||||
COALESCE(users.display_name,users.name) as owner_display_name,
|
COALESCE(users.display_name,users.name) as owner_display_name,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
sites.created_at
|
sites.created_at,
|
||||||
|
array_agg(row(collections.slug, collections.name)) as "collections!: Vec<CollectionNameAndSlug>"
|
||||||
FROM sites
|
FROM sites
|
||||||
JOIN
|
JOIN
|
||||||
users ON sites.owner = users.id
|
users ON sites.owner = users.id
|
||||||
|
JOIN
|
||||||
|
collections ON collections.site = sites.id
|
||||||
WHERE
|
WHERE
|
||||||
sites.name = $1
|
sites.name = $1
|
||||||
AND sites.deleted_at IS NULL"#,
|
AND sites.deleted_at IS NULL
|
||||||
|
GROUP BY
|
||||||
|
sites.name, users.name, users.display_name, title, description, sites.created_at"#,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
|
@ -34,18 +56,26 @@ impl SiteRepository for Database {
|
||||||
sqlx::Error::RowNotFound => Error::NotFound,
|
sqlx::Error::RowNotFound => Error::NotFound,
|
||||||
_ => e.into(),
|
_ => e.into(),
|
||||||
})?;
|
})?;
|
||||||
Ok(site)
|
Ok(SiteData {
|
||||||
|
name: site.name,
|
||||||
|
owner: site.owner,
|
||||||
|
owner_display_name: site.owner_display_name,
|
||||||
|
title: site.title,
|
||||||
|
description: site.description,
|
||||||
|
created_at: site.created_at,
|
||||||
|
collections: site.collections,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_site(&self, owner: &Uuid, options: CreateSiteData) -> Result<(), Error> {
|
async fn create_site(&self, owner: &Uuid, options: CreateSiteData) -> Result<Uuid, Error> {
|
||||||
sqlx::query!(
|
let result = sqlx::query!(
|
||||||
"INSERT INTO sites (name, owner, title, description) VALUES ($1, $2, $3, $4)",
|
"INSERT INTO sites (name, owner, title, description) VALUES ($1, $2, $3, $4) RETURNING id",
|
||||||
options.name,
|
options.name,
|
||||||
owner,
|
owner,
|
||||||
options.title,
|
options.title,
|
||||||
options.description,
|
options.description,
|
||||||
)
|
)
|
||||||
.execute(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| match err {
|
.map_err(|err| match err {
|
||||||
sqlx::Error::Database(dberr) if dberr.constraint() == Some("sites_name_key") => {
|
sqlx::Error::Database(dberr) if dberr.constraint() == Some("sites_name_key") => {
|
||||||
|
@ -53,7 +83,7 @@ impl SiteRepository for Database {
|
||||||
}
|
}
|
||||||
_ => err.into(),
|
_ => err.into(),
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(result.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_site(
|
async fn update_site(
|
||||||
|
|
|
@ -49,6 +49,23 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct OptionalUser(pub Option<User>);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<S> FromRequestParts<S> for OptionalUser
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = ApiError<'static>;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
match Extension::<User>::from_request_parts(parts, state).await {
|
||||||
|
Ok(Extension(user)) => Ok(OptionalUser(Some(user))),
|
||||||
|
_ => Ok(OptionalUser(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RequireSession(pub Session);
|
pub struct RequireSession(pub Session);
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod builtins;
|
||||||
mod content;
|
mod content;
|
||||||
mod database;
|
mod database;
|
||||||
mod http;
|
mod http;
|
||||||
mod roles;
|
|
||||||
mod routes;
|
mod routes;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{hash::random, user::User},
|
auth::{hash::random, user::User},
|
||||||
|
builtins::ROLE_SUPERADMIN,
|
||||||
http::error::ApiError,
|
http::error::ApiError,
|
||||||
roles::ROLE_SUPERADMIN,
|
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,17 +11,39 @@ use crate::{
|
||||||
content::{
|
content::{
|
||||||
post::{CreatePostData, PostRepository, UpdatePostData},
|
post::{CreatePostData, PostRepository, UpdatePostData},
|
||||||
site::SiteRepository,
|
site::SiteRepository,
|
||||||
|
Error,
|
||||||
},
|
},
|
||||||
database::Database,
|
database::Database,
|
||||||
http::{error::ApiError, json::JsonBody, session::RequireUser},
|
http::{
|
||||||
|
error::ApiError,
|
||||||
|
json::JsonBody,
|
||||||
|
session::{OptionalUser, RequireUser},
|
||||||
|
},
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn get_post<Repo: PostRepository>(
|
async fn get_post<Repo: PostRepository>(
|
||||||
repository: Repo,
|
repository: Repo,
|
||||||
|
OptionalUser(user): OptionalUser,
|
||||||
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_post_from_url(&site, &slug).await?))
|
let post = repository.get_post_from_url(&site, &slug).await?;
|
||||||
|
|
||||||
|
// If post has been published, show to everyone, otherwise only show to owner/author
|
||||||
|
if post.published {
|
||||||
|
return Ok(Json(post));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
// TODO This will need to be changed if we want more users managing the same site
|
||||||
|
// Also, we should have a better way to match the user!! (mapping func? prolly)
|
||||||
|
if user.name == post.author_username {
|
||||||
|
return Ok(Json(post));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 404'd!!
|
||||||
|
Err(Error::NotFound.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_post<Repo: PostRepository + SiteRepository>(
|
async fn create_post<Repo: PostRepository + SiteRepository>(
|
||||||
|
|
|
@ -8,7 +8,10 @@ use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
content::site::{CreateSiteData, SiteRepository, UpdateSiteData},
|
content::{
|
||||||
|
collection::CollectionRepository,
|
||||||
|
site::{CreateSiteData, SiteRepository, UpdateSiteData},
|
||||||
|
},
|
||||||
database::Database,
|
database::Database,
|
||||||
http::{error::ApiError, json::JsonBody, session::RequireUser},
|
http::{error::ApiError, json::JsonBody, session::RequireUser},
|
||||||
state::AppState,
|
state::AppState,
|
||||||
|
@ -21,12 +24,16 @@ async fn get_site<Repo: SiteRepository>(
|
||||||
Ok(Json(repository.get_site_by_name(&name).await?))
|
Ok(Json(repository.get_site_by_name(&name).await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_site<Repo: SiteRepository>(
|
async fn create_site<Repo: SiteRepository + CollectionRepository>(
|
||||||
repository: Repo,
|
repository: Repo,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(user): RequireUser,
|
||||||
JsonBody(options): JsonBody<CreateSiteData>,
|
JsonBody(options): JsonBody<CreateSiteData>,
|
||||||
) -> Result<impl IntoResponse, ApiError<'static>> {
|
) -> Result<impl IntoResponse, ApiError<'static>> {
|
||||||
repository.create_site(&user.id, options).await?;
|
let id = repository.create_site(&user.id, options).await?;
|
||||||
|
|
||||||
|
// Create default content for the new site
|
||||||
|
repository.create_default_collections(&id).await?;
|
||||||
|
|
||||||
Ok(Json(json!({"ok": true})))
|
Ok(Json(json!({"ok": true})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue