add collection fetching
This commit is contained in:
parent
81134dc0dd
commit
3c015177a0
13 changed files with 345 additions and 162 deletions
345
sqlx-data.json
345
sqlx-data.json
|
@ -12,80 +12,43 @@
|
||||||
},
|
},
|
||||||
"query": "DELETE FROM sessions WHERE id = $1"
|
"query": "DELETE FROM sessions WHERE id = $1"
|
||||||
},
|
},
|
||||||
"23f1a1f2aa96a5a23880f330d04f5dc695a148f4aa50b8c76378b68944569e44": {
|
"1d21e65cb21ec57986f154395627de9aefbf77f88c892c5e093787ec53d623d3": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "author_display_name",
|
"name": "id",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Varchar"
|
"type_info": "Uuid"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "author_username",
|
"name": "slug",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Varchar"
|
"type_info": "Varchar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "slug",
|
"name": "name",
|
||||||
"ordinal": 2,
|
"ordinal": 2,
|
||||||
"type_info": "Varchar"
|
"type_info": "Varchar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "parent",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Varchar"
|
"type_info": "Uuid"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "description",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tags",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "VarcharArray"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "blocks!: Json<Vec<PostBlock>>",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Jsonb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Timestamp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "modified_at",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Timestamp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "published",
|
|
||||||
"ordinal": 9,
|
|
||||||
"type_info": "Bool"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nullable": [
|
"nullable": [
|
||||||
null,
|
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Left": [
|
"Left": [
|
||||||
"Text",
|
"Uuid"
|
||||||
"Text"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "SELECT\n COALESCE(users.display_name,users.name) as author_display_name,\n users.name as author_username,\n pages.slug,\n pages.title,\n pages.description,\n pages.tags,\n pages.blocks as \"blocks!: Json<Vec<PostBlock>>\",\n pages.created_at,\n pages.modified_at,\n pages.published\n FROM pages \n JOIN\n sites ON site = sites.id\n JOIN\n users ON author = users.id\n WHERE\n slug = $1\n AND sites.name = $2 \n AND pages.deleted_at IS NULL\n AND sites.deleted_at IS NULL"
|
"query": "SELECT id, slug, name, parent FROM collections WHERE site = $1"
|
||||||
},
|
},
|
||||||
"28ec2833283df836e457161f44f0fbc75ed37dcc0ba63eb09bf285aae2f7a380": {
|
"28ec2833283df836e457161f44f0fbc75ed37dcc0ba63eb09bf285aae2f7a380": {
|
||||||
"describe": {
|
"describe": {
|
||||||
|
@ -158,83 +121,6 @@
|
||||||
},
|
},
|
||||||
"query": "SELECT\n sites.name,\n users.name as owner,\n COALESCE(users.display_name,users.name) as owner_display_name,\n title,\n description,\n sites.created_at,\n array_agg(row(collections.slug, collections.name)) as \"collections!: Vec<CollectionNameAndSlug>\"\n FROM sites\n JOIN\n users ON sites.owner = users.id\n JOIN\n collections ON collections.site = sites.id\n WHERE\n sites.name = $1\n AND sites.deleted_at IS NULL\n GROUP BY\n sites.name, users.name, users.display_name, title, description, sites.created_at"
|
"query": "SELECT\n sites.name,\n users.name as owner,\n COALESCE(users.display_name,users.name) as owner_display_name,\n title,\n description,\n sites.created_at,\n array_agg(row(collections.slug, collections.name)) as \"collections!: Vec<CollectionNameAndSlug>\"\n FROM sites\n JOIN\n users ON sites.owner = users.id\n JOIN\n collections ON collections.site = sites.id\n WHERE\n sites.name = $1\n AND sites.deleted_at IS NULL\n GROUP BY\n sites.name, users.name, users.display_name, title, description, sites.created_at"
|
||||||
},
|
},
|
||||||
"320625aa3fac73cb43d6a68592767174558ab1a6923d3574ad629d1a1bd0d4a6": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "author_display_name",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "author_username",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "slug",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "title",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "description",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tags",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "VarcharArray"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "blocks!: Json<Vec<PostBlock>>",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Jsonb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Timestamp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "modified_at",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Timestamp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "published",
|
|
||||||
"ordinal": 9,
|
|
||||||
"type_info": "Bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid",
|
|
||||||
"Uuid",
|
|
||||||
"Timestamp",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "SELECT\n COALESCE(users.display_name,users.name) as author_display_name,\n users.name as author_username,\n pages.slug,\n pages.title,\n pages.description,\n pages.tags,\n pages.blocks as \"blocks!: Json<Vec<PostBlock>>\",\n pages.created_at,\n pages.modified_at,\n pages.published\n FROM pages\n JOIN\n users ON author = users.id\n WHERE\n site = $1\n AND $2 = ANY(collections)\n AND pages.deleted_at IS NULL\n AND published = true\n AND pages.created_at <= $3\n ORDER BY created_at DESC\n LIMIT $4\n "
|
|
||||||
},
|
|
||||||
"35deaad55b84124dcba02c555718fe815003e3f50b1c8ee2fc7e540009fa5aef": {
|
"35deaad55b84124dcba02c555718fe815003e3f50b1c8ee2fc7e540009fa5aef": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
@ -323,6 +209,89 @@
|
||||||
},
|
},
|
||||||
"query": "DELETE FROM sessions WHERE expires_at < $1"
|
"query": "DELETE FROM sessions WHERE expires_at < $1"
|
||||||
},
|
},
|
||||||
|
"7aff3e9becb9da6bfba6d50a364fd56f46b739d6e7a5742e5efe1d19858ee427": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "author_display_name",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "author_username",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slug",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tags",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blocks!: Json<Vec<PostBlock>>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Jsonb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "modified_at",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "published",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collections",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "UuidArray"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text",
|
||||||
|
"Timestamp",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n COALESCE(users.display_name,users.name) as author_display_name,\n users.name as author_username,\n pages.slug,\n pages.title,\n pages.description,\n pages.tags,\n pages.blocks as \"blocks!: Json<Vec<PostBlock>>\",\n pages.created_at,\n pages.modified_at,\n pages.published,\n pages.collections\n FROM pages\n JOIN\n users ON author = users.id\n JOIN\n sites ON site = sites.id\n JOIN\n collections ON collections.slug = $2 AND collections.site = sites.id\n WHERE\n sites.name = $1\n AND collections.id = ANY(collections)\n AND pages.deleted_at IS NULL\n AND published = true\n AND pages.created_at <= $3\n ORDER BY created_at DESC\n LIMIT $4\n "
|
||||||
|
},
|
||||||
"7bdb55b060b5e81b50c1bf86cb117215cba7010c91f8384a397efac06a88507d": {
|
"7bdb55b060b5e81b50c1bf86cb117215cba7010c91f8384a397efac06a88507d": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
|
@ -366,6 +335,32 @@
|
||||||
},
|
},
|
||||||
"query": "INSERT INTO users ( id, name, password, roles )\n VALUES ( $1,$2,$3,$4 ) RETURNING id, created_at"
|
"query": "INSERT INTO users ( id, name, password, roles )\n VALUES ( $1,$2,$3,$4 ) RETURNING id, created_at"
|
||||||
},
|
},
|
||||||
|
"8c2ad67b10d926e72efdcf63140c6cc34790aca9a90b52f555e745bbb30faffa": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Uuid"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT id, owner FROM sites WHERE name = $1 AND deleted_at IS NULL"
|
||||||
|
},
|
||||||
"9f37c6f3929ca1bf2a6ec55291d652b216419fd13e27b3d61d23c9cf900c501e": {
|
"9f37c6f3929ca1bf2a6ec55291d652b216419fd13e27b3d61d23c9cf900c501e": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
@ -485,6 +480,87 @@
|
||||||
},
|
},
|
||||||
"query": "SELECT\n sessions.id AS session_id,\n sessions.actor AS session_actor,\n sessions.secret,\n sessions.created_at AS session_created_at,\n sessions.expires_at,\n users.id AS user_id,\n users.name,\n users.email,\n users.display_name,\n users.bio,\n users.roles,\n users.created_at AS user_created_at,\n users.modified_at,\n users.deleted_at\n FROM\n sessions\n JOIN\n users ON sessions.actor = users.id\n WHERE\n sessions.id = $1"
|
"query": "SELECT\n sessions.id AS session_id,\n sessions.actor AS session_actor,\n sessions.secret,\n sessions.created_at AS session_created_at,\n sessions.expires_at,\n users.id AS user_id,\n users.name,\n users.email,\n users.display_name,\n users.bio,\n users.roles,\n users.created_at AS user_created_at,\n users.modified_at,\n users.deleted_at\n FROM\n sessions\n JOIN\n users ON sessions.actor = users.id\n WHERE\n sessions.id = $1"
|
||||||
},
|
},
|
||||||
|
"cb17620d355a66a591cfae86e63dc293664a85777b39d7d19f2493a7388ecaf6": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "author_display_name",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "author_username",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slug",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tags",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blocks!: Json<Vec<PostBlock>>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Jsonb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "modified_at",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "published",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collections",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "UuidArray"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n COALESCE(users.display_name,users.name) as author_display_name,\n users.name as author_username,\n pages.slug,\n pages.title,\n pages.description,\n pages.tags,\n pages.blocks as \"blocks!: Json<Vec<PostBlock>>\",\n pages.created_at,\n pages.modified_at,\n pages.published,\n pages.collections\n FROM pages \n JOIN\n sites ON site = sites.id\n JOIN\n users ON author = users.id\n WHERE\n slug = $1\n AND sites.name = $2 \n AND pages.deleted_at IS NULL\n AND sites.deleted_at IS NULL"
|
||||||
|
},
|
||||||
"cde4892e52ef14049256499eb4b0529a3b1cefd8f83ef3dc7cb201ee4a8253a3": {
|
"cde4892e52ef14049256499eb4b0529a3b1cefd8f83ef3dc7cb201ee4a8253a3": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
|
@ -571,26 +647,5 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "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"
|
"query": "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"
|
||||||
},
|
|
||||||
"f3d0f52ab3c9d7ed46086d21cd88207a75d9f2e2990f3fb721f0e14e1ec52e10": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Uuid"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text",
|
|
||||||
"Uuid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "SELECT id FROM sites WHERE name = $1 AND owner = $2 AND deleted_at IS NULL"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,8 +25,18 @@ pub struct Collection {
|
||||||
pub parent: Option<Uuid>,
|
pub parent: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct CollectionData {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub slug: String,
|
||||||
|
pub name: String,
|
||||||
|
pub parent: Option<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait CollectionRepository {
|
pub trait CollectionRepository {
|
||||||
|
async fn list_collections(&self, site: &Uuid) -> Result<Vec<CollectionData>, Error>;
|
||||||
|
|
||||||
/// Create default collections for a site
|
/// Create default collections for a site
|
||||||
async fn create_default_collections(&self, site: &Uuid) -> Result<(), Error>;
|
async fn create_default_collections(&self, site: &Uuid) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@ pub enum Error {
|
||||||
#[error("Resource not found")]
|
#[error("Resource not found")]
|
||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
|
#[error("No access to resource")]
|
||||||
|
AccessDenied,
|
||||||
|
|
||||||
#[error("Resource identifier not available")]
|
#[error("Resource identifier not available")]
|
||||||
IdentifierNotAvailable,
|
IdentifierNotAvailable,
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,7 @@ pub struct PostWithAuthor {
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub modified_at: Option<NaiveDateTime>,
|
pub modified_at: Option<NaiveDateTime>,
|
||||||
pub published: bool,
|
pub published: bool,
|
||||||
|
pub collections: Vec<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsCursor<NaiveDateTime> for PostWithAuthor {
|
impl AsCursor<NaiveDateTime> for PostWithAuthor {
|
||||||
|
@ -134,8 +135,8 @@ pub trait PostRepository {
|
||||||
/// List posts in a collection
|
/// List posts in a collection
|
||||||
async fn list_posts_in_collection(
|
async fn list_posts_in_collection(
|
||||||
&self,
|
&self,
|
||||||
site: &Uuid,
|
site: &str,
|
||||||
collection: &Uuid,
|
collection: &str,
|
||||||
cursor: Option<NaiveDateTime>,
|
cursor: Option<NaiveDateTime>,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<CursorList<PostWithAuthor, NaiveDateTime>, Error>;
|
) -> Result<CursorList<PostWithAuthor, NaiveDateTime>, Error>;
|
||||||
|
|
|
@ -83,5 +83,5 @@ pub trait SiteRepository {
|
||||||
async fn delete_site(&self, owner: &Uuid, name: &str, soft_delete: bool) -> Result<(), Error>;
|
async fn delete_site(&self, owner: &Uuid, name: &str, soft_delete: bool) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Resolve a site's name to its ID
|
/// Resolve a site's name to its ID
|
||||||
async fn get_site_id(&self, name: &str, owner: &Uuid) -> Result<Uuid, Error>;
|
async fn get_site_id_and_owner(&self, name: &str) -> Result<(Uuid, Uuid), Error>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub struct CursorList<T, C> {
|
pub struct CursorList<T, C> {
|
||||||
pub items: Vec<T>,
|
pub items: Vec<T>,
|
||||||
pub next_cursor: Option<C>,
|
pub next_cursor: Option<C>,
|
||||||
|
|
|
@ -2,7 +2,7 @@ use async_trait::async_trait;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::content::{
|
use crate::content::{
|
||||||
collection::{CollectionRepository, DEFAULT_COLLECTIONS},
|
collection::{CollectionData, CollectionRepository, DEFAULT_COLLECTIONS},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,6 +10,16 @@ use super::Database;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl CollectionRepository for Database {
|
impl CollectionRepository for Database {
|
||||||
|
async fn list_collections(&self, site: &Uuid) -> Result<Vec<CollectionData>, Error> {
|
||||||
|
Ok(sqlx::query_as!(
|
||||||
|
CollectionData,
|
||||||
|
r#"SELECT id, slug, name, parent FROM collections WHERE site = $1"#,
|
||||||
|
site
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
async fn create_default_collections(&self, site: &Uuid) -> Result<(), Error> {
|
async fn create_default_collections(&self, site: &Uuid) -> Result<(), Error> {
|
||||||
let mut tx = self.pool.begin().await?;
|
let mut tx = self.pool.begin().await?;
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ use super::Database;
|
||||||
impl PostRepository for Database {
|
impl PostRepository for Database {
|
||||||
async fn list_posts_in_collection(
|
async fn list_posts_in_collection(
|
||||||
&self,
|
&self,
|
||||||
site: &Uuid,
|
site: &str,
|
||||||
collection: &Uuid,
|
collection: &str,
|
||||||
from: Option<NaiveDateTime>,
|
from: Option<NaiveDateTime>,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<CursorList<PostWithAuthor, NaiveDateTime>, Error> {
|
) -> Result<CursorList<PostWithAuthor, NaiveDateTime>, Error> {
|
||||||
|
@ -37,13 +37,18 @@ 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.published
|
pages.published,
|
||||||
|
pages.collections
|
||||||
FROM pages
|
FROM pages
|
||||||
JOIN
|
JOIN
|
||||||
users ON author = users.id
|
users ON author = users.id
|
||||||
|
JOIN
|
||||||
|
sites ON site = sites.id
|
||||||
|
JOIN
|
||||||
|
collections ON collections.slug = $2 AND collections.site = sites.id
|
||||||
WHERE
|
WHERE
|
||||||
site = $1
|
sites.name = $1
|
||||||
AND $2 = ANY(collections)
|
AND collections.id = ANY(collections)
|
||||||
AND pages.deleted_at IS NULL
|
AND pages.deleted_at IS NULL
|
||||||
AND published = true
|
AND published = true
|
||||||
AND pages.created_at <= $3
|
AND pages.created_at <= $3
|
||||||
|
@ -78,7 +83,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.published
|
pages.published,
|
||||||
|
pages.collections
|
||||||
FROM pages
|
FROM pages
|
||||||
JOIN
|
JOIN
|
||||||
sites ON site = sites.id
|
sites ON site = sites.id
|
||||||
|
|
|
@ -153,11 +153,10 @@ impl SiteRepository for Database {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_site_id(&self, name: &str, owner: &Uuid) -> Result<Uuid, Error> {
|
async fn get_site_id_and_owner(&self, name: &str) -> Result<(Uuid, Uuid), Error> {
|
||||||
let record = sqlx::query!(
|
let record = sqlx::query!(
|
||||||
"SELECT id FROM sites WHERE name = $1 AND owner = $2 AND deleted_at IS NULL",
|
"SELECT id, owner FROM sites WHERE name = $1 AND deleted_at IS NULL",
|
||||||
name,
|
name,
|
||||||
owner
|
|
||||||
)
|
)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
|
@ -165,6 +164,6 @@ impl SiteRepository for Database {
|
||||||
sqlx::Error::RowNotFound => Error::NotFound,
|
sqlx::Error::RowNotFound => Error::NotFound,
|
||||||
_ => e.into(),
|
_ => e.into(),
|
||||||
})?;
|
})?;
|
||||||
Ok(record.id)
|
Ok((record.id, record.owner))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,11 @@ const ERR_NOT_AVAILABLE: ApiError<'static> = ApiError::Client {
|
||||||
code: "id-not-available",
|
code: "id-not-available",
|
||||||
message: "the chosen identifier is not available",
|
message: "the chosen identifier is not available",
|
||||||
};
|
};
|
||||||
|
const ERR_UNAUTHORIZED: ApiError<'static> = ApiError::Client {
|
||||||
|
status: StatusCode::FORBIDDEN,
|
||||||
|
code: "access-denied",
|
||||||
|
message: "you are not authorized to perform this action",
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ApiError<'a> {
|
pub enum ApiError<'a> {
|
||||||
|
@ -80,6 +85,7 @@ impl From<content::Error> for ApiError<'_> {
|
||||||
match err {
|
match err {
|
||||||
content::Error::NotFound => ERR_NOT_FOUND,
|
content::Error::NotFound => ERR_NOT_FOUND,
|
||||||
content::Error::IdentifierNotAvailable => ERR_NOT_AVAILABLE,
|
content::Error::IdentifierNotAvailable => ERR_NOT_AVAILABLE,
|
||||||
|
content::Error::AccessDenied => ERR_UNAUTHORIZED,
|
||||||
content::Error::QueryFailed(err) => err.into(),
|
content::Error::QueryFailed(err) => err.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
76
src/routes/collections.rs
Normal file
76
src/routes/collections.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use axum::{
|
||||||
|
extract::{Path, Query},
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{get, post},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
content::{collection::CollectionRepository, post::PostRepository, site::SiteRepository},
|
||||||
|
database::Database,
|
||||||
|
http::error::ApiError,
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn create_collection<Repo: CollectionRepository>(repository: Repo) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_collection<Repo: CollectionRepository>(repository: Repo) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_collections_for_site<Repo: SiteRepository + CollectionRepository>(
|
||||||
|
repository: Repo,
|
||||||
|
Path(site): Path<String>,
|
||||||
|
) -> Result<impl IntoResponse, ApiError<'static>> {
|
||||||
|
let (site_id, _) = repository.get_site_id_and_owner(&site).await?;
|
||||||
|
|
||||||
|
Ok(Json(repository.list_collections(&site_id).await?))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_collection<Repo: CollectionRepository>(repository: Repo) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_collection<Repo: CollectionRepository>(repository: Repo) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PaginationQuery {
|
||||||
|
before: Option<NaiveDateTime>,
|
||||||
|
limit: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_posts_in_collection<Repo: PostRepository>(
|
||||||
|
repository: Repo,
|
||||||
|
Path((site, slug)): Path<(String, String)>,
|
||||||
|
query: Query<PaginationQuery>,
|
||||||
|
) -> Result<impl IntoResponse, ApiError<'static>> {
|
||||||
|
let results = repository
|
||||||
|
.list_posts_in_collection(&site, &slug, query.before, query.limit)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn router() -> Router<Arc<AppState>> {
|
||||||
|
Router::new()
|
||||||
|
.route(
|
||||||
|
"/:site",
|
||||||
|
get(list_collections_for_site::<Database>).post(create_collection::<Database>),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:site/:slug",
|
||||||
|
get(get_collection::<Database>)
|
||||||
|
.put(update_collection::<Database>)
|
||||||
|
.delete(delete_collection::<Database>),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:site/:slug/posts",
|
||||||
|
get(list_posts_in_collection::<Database>),
|
||||||
|
)
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ use crate::state::AppState;
|
||||||
|
|
||||||
mod admin;
|
mod admin;
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod collections;
|
||||||
mod posts;
|
mod posts;
|
||||||
mod sites;
|
mod sites;
|
||||||
|
|
||||||
|
@ -14,4 +15,5 @@ pub fn create_router() -> Router<Arc<AppState>> {
|
||||||
.nest("/admin", admin::router())
|
.nest("/admin", admin::router())
|
||||||
.nest("/posts", posts::router())
|
.nest("/posts", posts::router())
|
||||||
.nest("/sites", sites::router())
|
.nest("/sites", sites::router())
|
||||||
|
.nest("/collections", collections::router())
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,11 @@ async fn create_post<Repo: PostRepository + SiteRepository>(
|
||||||
JsonBody(data): JsonBody<CreatePostData>,
|
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, owner) = repository.get_site_id_and_owner(&site).await?;
|
||||||
|
|
||||||
|
if user.id != owner {
|
||||||
|
return Err(Error::AccessDenied.into());
|
||||||
|
}
|
||||||
|
|
||||||
repository.create_post(&user.id, &site_id, data).await?;
|
repository.create_post(&user.id, &site_id, data).await?;
|
||||||
Ok(Json(json!({ "ok": true })))
|
Ok(Json(json!({ "ok": true })))
|
||||||
|
@ -66,7 +70,11 @@ async fn update_post<Repo: PostRepository + SiteRepository>(
|
||||||
JsonBody(data): JsonBody<UpdatePostData>,
|
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, owner) = repository.get_site_id_and_owner(&site).await?;
|
||||||
|
|
||||||
|
if user.id != owner {
|
||||||
|
return Err(Error::AccessDenied.into());
|
||||||
|
}
|
||||||
|
|
||||||
repository
|
repository
|
||||||
.update_post(&user.id, &site_id, &slug, data)
|
.update_post(&user.id, &site_id, &slug, data)
|
||||||
|
@ -80,7 +88,11 @@ async fn delete_post<Repo: PostRepository + SiteRepository>(
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(user): RequireUser,
|
||||||
) -> 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, owner) = repository.get_site_id_and_owner(&site).await?;
|
||||||
|
|
||||||
|
if user.id != owner {
|
||||||
|
return Err(Error::AccessDenied.into());
|
||||||
|
}
|
||||||
|
|
||||||
repository
|
repository
|
||||||
.delete_post(&user.id, &site_id, &slug, true)
|
.delete_post(&user.id, &site_id, &slug, true)
|
||||||
|
|
Loading…
Reference in a new issue