mirror of
https://github.com/AppFlowy-IO/AppFlowy-Cloud.git
synced 2025-04-19 03:24:42 -04:00
feat: Template CRUD Endpoint (#731)
* feat: template crud endpoint * fix: clippy error * fix: categories for related view * fix: add created at and last updated at to template response * feat: template api delete endpoint * feat: include number of template count for template creator * fix: use params instead of individual fields for template api * fix: seach template creator by name query * chore: simplify query * feat: support template count limit for template homepage
This commit is contained in:
parent
256e5ca22d
commit
9c8e718246
23 changed files with 2082 additions and 151 deletions
14
.sqlx/query-09cf032adce81ba99362b3df50ba104f4e1eb2d538350c65cf615ea13f1c37f0.json
generated
Normal file
14
.sqlx/query-09cf032adce81ba99362b3df50ba104f4e1eb2d538350c65cf615ea13f1c37f0.json
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM af_template_view\n WHERE view_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "09cf032adce81ba99362b3df50ba104f4e1eb2d538350c65cf615ea13f1c37f0"
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n tc.creator_id AS \"id!\",\n name AS \"name!\",\n avatar_url AS \"avatar_url!\",\n ARRAY_AGG((al.link_type, al.url)) FILTER (WHERE link_type IS NOT NULL) AS \"account_links: Vec<AccountLinkColumn>\"\n FROM af_template_creator tc\n LEFT OUTER JOIN af_template_creator_account_link al\n ON tc.creator_id = al.creator_id\n WHERE tc.creator_id = $1\n GROUP BY (tc.creator_id, name, avatar_url)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id!",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "avatar_url!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "account_links: Vec<AccountLinkColumn>",
|
||||
"type_info": "RecordArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "13e511cfa6be2924fe6b141651a49ed3c36d12aa844322b2a30fa4ce01e0b361"
|
||||
}
|
21
.sqlx/query-2394226650959b34ae80b1948b7a111720b3ea5da48934d8d7e395ecc84e6985.json
generated
Normal file
21
.sqlx/query-2394226650959b34ae80b1948b7a111720b3ea5da48934d8d7e395ecc84e6985.json
generated
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE af_template_view SET\n updated_at = NOW(),\n name = $2,\n description = $3,\n about = $4,\n view_url = $5,\n creator_id = $6,\n is_new_template = $7,\n is_featured = $8\n WHERE view_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Bool",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2394226650959b34ae80b1948b7a111720b3ea5da48934d8d7e395ecc84e6985"
|
||||
}
|
46
.sqlx/query-340b8cef5a7676541b86505cdf103fcb5b54c40a9d6e599dc1d9dc0a95e1e862.json
generated
Normal file
46
.sqlx/query-340b8cef5a7676541b86505cdf103fcb5b54c40a9d6e599dc1d9dc0a95e1e862.json
generated
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH creator_number_of_templates AS (\n SELECT\n creator_id,\n COUNT(1)::int AS number_of_templates\n FROM af_template_view\n WHERE name ILIKE $1\n GROUP BY creator_id\n )\n\n SELECT\n creator.creator_id AS \"id!\",\n name AS \"name!\",\n avatar_url AS \"avatar_url!\",\n ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS \"account_links: Vec<AccountLinkColumn>\",\n COALESCE(number_of_templates, 0) AS \"number_of_templates!\"\n FROM af_template_creator creator\n LEFT OUTER JOIN af_template_creator_account_link account_link\n USING (creator_id)\n LEFT OUTER JOIN creator_number_of_templates\n USING (creator_id)\n WHERE name ILIKE $1\n GROUP BY (creator.creator_id, name, avatar_url, number_of_templates)\n ORDER BY created_at ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id!",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "avatar_url!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "account_links: Vec<AccountLinkColumn>",
|
||||
"type_info": "RecordArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "number_of_templates!",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "340b8cef5a7676541b86505cdf103fcb5b54c40a9d6e599dc1d9dc0a95e1e862"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH\n updated_creator AS (\n UPDATE af_template_creator\n SET name = $2, avatar_url = $3, updated_at = NOW()\n WHERE creator_id = $1\n RETURNING creator_id, name, avatar_url\n ),\n account_links AS (\n INSERT INTO af_template_creator_account_link (creator_id, link_type, url)\n SELECT updated_creator.creator_id as creator_id, link_type, url FROM\n UNNEST($4::text[], $5::text[]) AS t(link_type, url)\n CROSS JOIN updated_creator\n RETURNING\n creator_id,\n link_type,\n url\n )\n SELECT\n updated_creator.creator_id AS id,\n name,\n avatar_url,\n ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS \"account_links: Vec<AccountLinkColumn>\"\n FROM updated_creator\n LEFT OUTER JOIN account_links\n ON updated_creator.creator_id = account_links.creator_id\n GROUP BY (id, name, avatar_url)\n ",
|
||||
"query": "\n WITH\n updated_creator AS (\n UPDATE af_template_creator\n SET name = $2, avatar_url = $3, updated_at = NOW()\n WHERE creator_id = $1\n RETURNING creator_id, name, avatar_url\n ),\n account_links AS (\n INSERT INTO af_template_creator_account_link (creator_id, link_type, url)\n SELECT updated_creator.creator_id as creator_id, link_type, url FROM\n UNNEST($4::text[], $5::text[]) AS t(link_type, url)\n CROSS JOIN updated_creator\n RETURNING\n creator_id,\n link_type,\n url\n ),\n creator_number_of_templates AS (\n SELECT\n creator_id,\n COUNT(1)::int AS number_of_templates\n FROM af_template_view\n WHERE creator_id = $1\n GROUP BY creator_id\n )\n SELECT\n updated_creator.creator_id AS id,\n name,\n avatar_url,\n ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS \"account_links: Vec<AccountLinkColumn>\",\n COALESCE(number_of_templates, 0) AS \"number_of_templates!\"\n FROM updated_creator\n LEFT OUTER JOIN account_links\n USING (creator_id)\n LEFT OUTER JOIN creator_number_of_templates\n USING (creator_id)\n GROUP BY (id, name, avatar_url, number_of_templates)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -22,6 +22,11 @@
|
|||
"ordinal": 3,
|
||||
"name": "account_links: Vec<AccountLinkColumn>",
|
||||
"type_info": "RecordArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "number_of_templates!",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
@ -37,8 +42,9 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "fcf9b5bd2d2184fc2041bb0ddfe4ee89c24c5bf7613f4e8641acf739c00ffc17"
|
||||
"hash": "3ca587826f0598e7786c765dcb2fcd6ae08d8aa404f02920307547c769a3f91b"
|
||||
}
|
14
.sqlx/query-3d3309a4ae7a88b3f7c9608dd78a1c1dc9b237a37e29722bcd2910bd23f9d873.json
generated
Normal file
14
.sqlx/query-3d3309a4ae7a88b3f7c9608dd78a1c1dc9b237a37e29722bcd2910bd23f9d873.json
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM af_related_template_view\n WHERE view_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3d3309a4ae7a88b3f7c9608dd78a1c1dc9b237a37e29722bcd2910bd23f9d873"
|
||||
}
|
158
.sqlx/query-50052ba4fa38e18bcf7d6ef76f8ffdb0263dfc0eb6aa001a8c30ab881ce3300e.json
generated
Normal file
158
.sqlx/query-50052ba4fa38e18bcf7d6ef76f8ffdb0263dfc0eb6aa001a8c30ab881ce3300e.json
generated
Normal file
|
@ -0,0 +1,158 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH recent_template AS (\n SELECT\n template_template_category.category_id,\n template_template_category.view_id,\n category.name,\n category.icon,\n category.bg_color,\n ROW_NUMBER() OVER (PARTITION BY template_template_category.category_id ORDER BY template.created_at DESC) AS recency\n FROM af_template_view_template_category template_template_category\n JOIN af_template_category category\n USING (category_id)\n JOIN af_template_view template\n USING (view_id)\n ),\n template_group_by_category_and_view AS (\n SELECT\n category_id,\n view_id,\n ARRAY_AGG((\n category_id,\n name,\n icon,\n bg_color\n )::template_category_minimal_type) AS categories\n FROM recent_template\n WHERE recency <= $1\n GROUP BY category_id, view_id\n ),\n template_group_by_category_and_view_with_creator_and_template_details AS (\n SELECT\n template_group_by_category_and_view.category_id,\n (\n template.view_id,\n template.created_at,\n template.updated_at,\n template.name,\n template.description,\n template.view_url,\n (\n creator.creator_id,\n creator.name,\n creator.avatar_url\n )::template_creator_minimal_type,\n template_group_by_category_and_view.categories,\n template.is_new_template,\n template.is_featured\n )::template_minimal_type AS template\n FROM template_group_by_category_and_view\n JOIN af_template_view template\n USING (view_id)\n JOIN af_template_creator creator\n USING (creator_id)\n ),\n template_group_by_category AS (\n SELECT\n category_id,\n ARRAY_AGG(template) AS templates\n FROM template_group_by_category_and_view_with_creator_and_template_details\n GROUP BY category_id\n )\n SELECT\n (\n template_group_by_category.category_id,\n category.name,\n category.icon,\n category.bg_color\n )::template_category_minimal_type AS \"category!: AFTemplateCategoryMinimalRow\",\n templates AS \"templates!: Vec<AFTemplateMinimalRow>\"\n FROM template_group_by_category\n JOIN af_template_category category\n USING (category_id)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "category!: AFTemplateCategoryMinimalRow",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "template_category_minimal_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"category_id",
|
||||
"Uuid"
|
||||
],
|
||||
[
|
||||
"name",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"icon",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"bg_color",
|
||||
"Text"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "templates!: Vec<AFTemplateMinimalRow>",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "template_minimal_type[]",
|
||||
"kind": {
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "template_minimal_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"view_id",
|
||||
"Uuid"
|
||||
],
|
||||
[
|
||||
"created_at",
|
||||
"Timestamptz"
|
||||
],
|
||||
[
|
||||
"updated_at",
|
||||
"Timestamptz"
|
||||
],
|
||||
[
|
||||
"name",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"description",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"view_url",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"creator",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "template_creator_minimal_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"creator_id",
|
||||
"Uuid"
|
||||
],
|
||||
[
|
||||
"name",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"avatar_url",
|
||||
"Text"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"categories",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "template_category_minimal_type[]",
|
||||
"kind": {
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "template_category_minimal_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"category_id",
|
||||
"Uuid"
|
||||
],
|
||||
[
|
||||
"name",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"icon",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"bg_color",
|
||||
"Text"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"is_new_template",
|
||||
"Bool"
|
||||
],
|
||||
[
|
||||
"is_featured",
|
||||
"Bool"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "50052ba4fa38e18bcf7d6ef76f8ffdb0263dfc0eb6aa001a8c30ab881ce3300e"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH\n new_creator AS (\n INSERT INTO af_template_creator (name, avatar_url)\n VALUES ($1, $2)\n RETURNING creator_id, name, avatar_url\n ),\n account_links AS (\n INSERT INTO af_template_creator_account_link (creator_id, link_type, url)\n SELECT new_creator.creator_id as creator_id, link_type, url FROM\n UNNEST($3::text[], $4::text[]) AS t(link_type, url)\n CROSS JOIN new_creator\n RETURNING\n creator_id,\n link_type,\n url\n )\n SELECT\n new_creator.creator_id AS id,\n name,\n avatar_url,\n ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS \"account_links: Vec<AccountLinkColumn>\"\n FROM new_creator\n LEFT OUTER JOIN account_links\n ON new_creator.creator_id = account_links.creator_id\n GROUP BY (id, name, avatar_url)\n ",
|
||||
"query": "\n WITH\n new_creator AS (\n INSERT INTO af_template_creator (name, avatar_url)\n VALUES ($1, $2)\n RETURNING creator_id, name, avatar_url\n ),\n account_links AS (\n INSERT INTO af_template_creator_account_link (creator_id, link_type, url)\n SELECT new_creator.creator_id as creator_id, link_type, url FROM\n UNNEST($3::text[], $4::text[]) AS t(link_type, url)\n CROSS JOIN new_creator\n RETURNING\n creator_id,\n link_type,\n url\n )\n SELECT\n new_creator.creator_id AS id,\n name,\n avatar_url,\n ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS \"account_links: Vec<AccountLinkColumn>\",\n 0 AS \"number_of_templates!\"\n FROM new_creator\n LEFT OUTER JOIN account_links\n USING (creator_id)\n GROUP BY (id, name, avatar_url)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -22,6 +22,11 @@
|
|||
"ordinal": 3,
|
||||
"name": "account_links: Vec<AccountLinkColumn>",
|
||||
"type_info": "RecordArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "number_of_templates!",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
@ -36,8 +41,9 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "23ce30adcad72a7beba4db6a1a9a5947b433aa28d1ac8a1e9fa328aa751fe4a2"
|
||||
"hash": "523087b0101a35abfc70a561272acec7a357491a86901f7927b8242173b5c8c8"
|
||||
}
|
14
.sqlx/query-84e600f13d61c56a45133e7458d5152e68dec72030e5789bf4149a333b6ebdf5.json
generated
Normal file
14
.sqlx/query-84e600f13d61c56a45133e7458d5152e68dec72030e5789bf4149a333b6ebdf5.json
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM af_template_view_template_category\n WHERE view_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "84e600f13d61c56a45133e7458d5152e68dec72030e5789bf4149a333b6ebdf5"
|
||||
}
|
245
.sqlx/query-9b4d78e8e2c2a8d77d99f400ad1ad3b9eb936988f6cba82146f0fb91e06899d2.json
generated
Normal file
245
.sqlx/query-9b4d78e8e2c2a8d77d99f400ad1ad3b9eb936988f6cba82146f0fb91e06899d2.json
generated
Normal file
|
@ -0,0 +1,245 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH template_with_creator_account_link AS (\n SELECT\n template.view_id,\n template.creator_id,\n COALESCE(\n ARRAY_AGG((link_type, url)::account_link_type) FILTER (WHERE link_type IS NOT NULL),\n '{}'\n ) AS account_links\n FROM af_template_view template\n JOIN af_template_creator creator\n USING (creator_id)\n LEFT OUTER JOIN af_template_creator_account_link account_link\n USING (creator_id)\n WHERE view_id = $1\n GROUP BY (view_id, template.creator_id)\n ),\n related_template_with_category AS (\n SELECT\n template.related_view_id,\n ARRAY_AGG(\n (\n template_category.category_id,\n template_category.name,\n template_category.icon,\n template_category.bg_color\n )::template_category_minimal_type\n ) AS categories\n FROM af_related_template_view template\n JOIN af_template_view_template_category template_template_category\n ON template.related_view_id = template_template_category.view_id\n JOIN af_template_category template_category\n USING (category_id)\n WHERE template.view_id = $1\n GROUP BY template.related_view_id\n ),\n template_with_related_template AS (\n SELECT\n template.view_id,\n ARRAY_AGG(\n (\n template.related_view_id,\n related_template.created_at,\n related_template.updated_at,\n related_template.name,\n related_template.description,\n related_template.view_url,\n (\n creator.creator_id,\n creator.name,\n creator.avatar_url\n )::template_creator_minimal_type,\n related_template_with_category.categories,\n related_template.is_new_template,\n related_template.is_featured\n )::template_minimal_type\n ) AS related_templates\n FROM af_related_template_view template\n JOIN af_template_view related_template\n ON template.related_view_id = related_template.view_id\n JOIN af_template_creator creator\n ON related_template.creator_id = creator.creator_id\n JOIN related_template_with_category\n ON template.related_view_id = related_template_with_category.related_view_id\n WHERE template.view_id = $1\n GROUP BY template.view_id\n ),\n template_with_category AS (\n SELECT\n view_id,\n COALESCE(\n ARRAY_AGG((\n vtc.category_id,\n name,\n icon,\n bg_color,\n description,\n category_type,\n priority\n )) FILTER (WHERE vtc.category_id IS NOT NULL),\n '{}'\n ) AS categories\n FROM af_template_view_template_category vtc\n JOIN af_template_category tc\n ON vtc.category_id = tc.category_id\n WHERE view_id = $1\n GROUP BY view_id\n ),\n creator_number_of_templates AS (\n SELECT\n creator_id,\n COUNT(*) AS number_of_templates\n FROM af_template_view\n GROUP BY creator_id\n )\n\n SELECT\n template.view_id,\n template.created_at,\n template.updated_at,\n template.name,\n template.description,\n template.about,\n template.view_url,\n (\n creator.creator_id,\n creator.name,\n creator.avatar_url,\n template_with_creator_account_link.account_links,\n creator_number_of_templates.number_of_templates\n )::template_creator_type AS \"creator!: AFTemplateCreatorRow\",\n template_with_category.categories AS \"categories!: Vec<AFTemplateCategoryRow>\",\n COALESCE(template_with_related_template.related_templates, '{}') AS \"related_templates!: Vec<AFTemplateMinimalRow>\",\n template.is_new_template,\n template.is_featured\n FROM af_template_view template\n JOIN af_template_creator creator\n USING (creator_id)\n JOIN template_with_creator_account_link\n ON template.view_id = template_with_creator_account_link.view_id\n LEFT OUTER JOIN template_with_related_template\n ON template.view_id = template_with_related_template.view_id\n JOIN template_with_category\n ON template.view_id = template_with_category.view_id\n LEFT OUTER JOIN creator_number_of_templates\n ON template.creator_id = creator_number_of_templates.creator_id\n WHERE template.view_id = $1\n\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "view_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "about",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "view_url",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "creator!: AFTemplateCreatorRow",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "template_creator_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"creator_id",
|
||||
"Uuid"
|
||||
],
|
||||
[
|
||||
"name",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"avatar_url",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"account_links",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "account_link_type[]",
|
||||
"kind": {
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "account_link_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"link_type",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"url",
|
||||
"Text"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"number_of_templates",
|
||||
"Int4"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "categories!: Vec<AFTemplateCategoryRow>",
|
||||
"type_info": "RecordArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "related_templates!: Vec<AFTemplateMinimalRow>",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "template_minimal_type[]",
|
||||
"kind": {
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "template_minimal_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"view_id",
|
||||
"Uuid"
|
||||
],
|
||||
[
|
||||
"created_at",
|
||||
"Timestamptz"
|
||||
],
|
||||
[
|
||||
"updated_at",
|
||||
"Timestamptz"
|
||||
],
|
||||
[
|
||||
"name",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"description",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"view_url",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"creator",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "template_creator_minimal_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"creator_id",
|
||||
"Uuid"
|
||||
],
|
||||
[
|
||||
"name",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"avatar_url",
|
||||
"Text"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"categories",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "template_category_minimal_type[]",
|
||||
"kind": {
|
||||
"Array": {
|
||||
"Custom": {
|
||||
"name": "template_category_minimal_type",
|
||||
"kind": {
|
||||
"Composite": [
|
||||
[
|
||||
"category_id",
|
||||
"Uuid"
|
||||
],
|
||||
[
|
||||
"name",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"icon",
|
||||
"Text"
|
||||
],
|
||||
[
|
||||
"bg_color",
|
||||
"Text"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"is_new_template",
|
||||
"Bool"
|
||||
],
|
||||
[
|
||||
"is_featured",
|
||||
"Bool"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "is_new_template",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "is_featured",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9b4d78e8e2c2a8d77d99f400ad1ad3b9eb936988f6cba82146f0fb91e06899d2"
|
||||
}
|
15
.sqlx/query-b509712055858af398fd12ddd1a8c3da54280cf55f0c53f340bddbf4bf09b3e0.json
generated
Normal file
15
.sqlx/query-b509712055858af398fd12ddd1a8c3da54280cf55f0c53f340bddbf4bf09b3e0.json
generated
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO af_related_template_view (view_id, related_view_id)\n SELECT $1 AS view_id, related_view_id\n FROM UNNEST($2::uuid[]) AS t(related_view_id)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b509712055858af398fd12ddd1a8c3da54280cf55f0c53f340bddbf4bf09b3e0"
|
||||
}
|
46
.sqlx/query-bd34e351ea1adc0d12d4f1cce5a855089b7f39a431dea2903c3e0b9a220640b8.json
generated
Normal file
46
.sqlx/query-bd34e351ea1adc0d12d4f1cce5a855089b7f39a431dea2903c3e0b9a220640b8.json
generated
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH creator_number_of_templates AS (\n SELECT\n creator_id,\n COUNT(1)::int AS number_of_templates\n FROM af_template_view\n WHERE creator_id = $1\n GROUP BY creator_id\n )\n SELECT\n creator.creator_id AS \"id!\",\n name AS \"name!\",\n avatar_url AS \"avatar_url!\",\n ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS \"account_links: Vec<AccountLinkColumn>\",\n COALESCE(number_of_templates, 0) AS \"number_of_templates!\"\n FROM af_template_creator creator\n LEFT OUTER JOIN af_template_creator_account_link account_link\n USING (creator_id)\n LEFT OUTER JOIN creator_number_of_templates\n USING (creator_id)\n WHERE creator.creator_id = $1\n GROUP BY (creator.creator_id, name, avatar_url, number_of_templates)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id!",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "avatar_url!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "account_links: Vec<AccountLinkColumn>",
|
||||
"type_info": "RecordArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "number_of_templates!",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bd34e351ea1adc0d12d4f1cce5a855089b7f39a431dea2903c3e0b9a220640b8"
|
||||
}
|
21
.sqlx/query-ca2a21db67716e3f12b9f9240c1dba1b7cbe0bec1f59ef132fed53942ebad317.json
generated
Normal file
21
.sqlx/query-ca2a21db67716e3f12b9f9240c1dba1b7cbe0bec1f59ef132fed53942ebad317.json
generated
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO af_template_view (\n view_id,\n name,\n description,\n about,\n view_url,\n creator_id,\n is_new_template,\n is_featured\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Bool",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ca2a21db67716e3f12b9f9240c1dba1b7cbe0bec1f59ef132fed53942ebad317"
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n tc.creator_id AS \"id!\",\n name AS \"name!\",\n avatar_url AS \"avatar_url!\",\n ARRAY_AGG((al.link_type, al.url)) FILTER (WHERE link_type IS NOT NULL) AS \"account_links: Vec<AccountLinkColumn>\"\n FROM af_template_creator tc\n LEFT OUTER JOIN af_template_creator_account_link al\n ON tc.creator_id = al.creator_id\n WHERE name LIKE $1\n GROUP BY (tc.creator_id, name, avatar_url)\n ORDER BY created_at ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id!",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "avatar_url!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "account_links: Vec<AccountLinkColumn>",
|
||||
"type_info": "RecordArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "e2e0800097f488070f216ec48be4ee9feb649dcb3ff27a0ec337e3f623c09ad0"
|
||||
}
|
15
.sqlx/query-f54ced785b4fdd22c9236b566996d5d9d4a8c91902e4029fe8f8f30f3af39b39.json
generated
Normal file
15
.sqlx/query-f54ced785b4fdd22c9236b566996d5d9d4a8c91902e4029fe8f8f30f3af39b39.json
generated
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO af_template_view_template_category (view_id, category_id)\n SELECT $1 as view_id, category_id FROM\n UNNEST($2::uuid[]) AS category_id\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f54ced785b4fdd22c9236b566996d5d9d4a8c91902e4029fe8f8f30f3af39b39"
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
use client_api_entity::{
|
||||
AccountLink, CreateTemplateCategoryParams, CreateTemplateCreatorParams,
|
||||
GetTemplateCategoriesQueryParams, GetTemplateCreatorsQueryParams, TemplateCategories,
|
||||
TemplateCategory, TemplateCategoryType, TemplateCreator, TemplateCreators,
|
||||
UpdateTemplateCategoryParams, UpdateTemplateCreatorParams,
|
||||
AccountLink, CreateTemplateCategoryParams, CreateTemplateCreatorParams, CreateTemplateParams,
|
||||
GetTemplateCategoriesQueryParams, GetTemplateCreatorsQueryParams, GetTemplatesQueryParams,
|
||||
Template, TemplateCategories, TemplateCategory, TemplateCategoryType, TemplateCreator,
|
||||
TemplateCreators, Templates, UpdateTemplateCategoryParams, UpdateTemplateCreatorParams,
|
||||
UpdateTemplateParams,
|
||||
};
|
||||
use reqwest::Method;
|
||||
use shared_entity::response::{AppResponse, AppResponseError};
|
||||
|
@ -34,6 +35,14 @@ fn template_creator_resource_url(base_url: &str, creator_id: Uuid) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
fn template_resources_url(base_url: &str) -> String {
|
||||
format!("{}/template", template_api_prefix(base_url))
|
||||
}
|
||||
|
||||
fn template_resource_url(base_url: &str, view_id: Uuid) -> String {
|
||||
format!("{}/{}", template_resources_url(base_url), view_id)
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub async fn create_template_category(
|
||||
&self,
|
||||
|
@ -204,4 +213,88 @@ impl Client {
|
|||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn create_template(
|
||||
&self,
|
||||
params: &CreateTemplateParams,
|
||||
) -> Result<Template, AppResponseError> {
|
||||
let url = template_resources_url(&self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::POST, &url)
|
||||
.await?
|
||||
.json(params)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<Template>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn get_template(&self, view_id: Uuid) -> Result<Template, AppResponseError> {
|
||||
let url = template_resource_url(&self.base_url, view_id);
|
||||
let resp = self
|
||||
.http_client_without_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<Template>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn get_templates(
|
||||
&self,
|
||||
category_id: Option<Uuid>,
|
||||
is_featured: Option<bool>,
|
||||
is_new_template: Option<bool>,
|
||||
name_contains: Option<String>,
|
||||
) -> Result<Templates, AppResponseError> {
|
||||
let url = template_resources_url(&self.base_url);
|
||||
let resp = self
|
||||
.http_client_without_auth(Method::GET, &url)
|
||||
.await?
|
||||
.query(&GetTemplatesQueryParams {
|
||||
category_id,
|
||||
is_featured,
|
||||
is_new_template,
|
||||
name_contains,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<Templates>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn update_template(
|
||||
&self,
|
||||
view_id: Uuid,
|
||||
params: &UpdateTemplateParams,
|
||||
) -> Result<Template, AppResponseError> {
|
||||
let url = template_resource_url(&self.base_url, view_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::PUT, &url)
|
||||
.await?
|
||||
.json(params)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<Template>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn delete_template(&self, view_id: Uuid) -> Result<(), AppResponseError> {
|
||||
let url = template_resource_url(&self.base_url, view_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::DELETE, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1055,6 +1055,14 @@ pub struct TemplateCategory {
|
|||
pub priority: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateCategoryMinimal {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub icon: String,
|
||||
pub bg_color: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateTemplateCategoryParams {
|
||||
pub name: String,
|
||||
|
@ -1098,6 +1106,14 @@ pub struct TemplateCreator {
|
|||
pub name: String,
|
||||
pub avatar_url: String,
|
||||
pub account_links: Vec<AccountLink>,
|
||||
pub number_of_templates: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateCreatorMinimal {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub avatar_url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
@ -1119,6 +1135,94 @@ pub struct GetTemplateCreatorsQueryParams {
|
|||
pub name_contains: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Template {
|
||||
pub view_id: Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub last_updated_at: DateTime<Utc>,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub about: String,
|
||||
pub view_url: String,
|
||||
pub categories: Vec<TemplateCategory>,
|
||||
pub creator: TemplateCreator,
|
||||
pub is_new_template: bool,
|
||||
pub is_featured: bool,
|
||||
pub related_templates: Vec<TemplateMinimal>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateMinimal {
|
||||
pub view_id: Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub last_updated_at: DateTime<Utc>,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub view_url: String,
|
||||
pub creator: TemplateCreatorMinimal,
|
||||
pub categories: Vec<TemplateCategoryMinimal>,
|
||||
pub is_new_template: bool,
|
||||
pub is_featured: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Templates {
|
||||
pub templates: Vec<TemplateMinimal>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateTemplateParams {
|
||||
pub view_id: Uuid,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub about: String,
|
||||
pub view_url: String,
|
||||
pub category_ids: Vec<Uuid>,
|
||||
pub creator_id: Uuid,
|
||||
pub is_new_template: bool,
|
||||
pub is_featured: bool,
|
||||
pub related_view_ids: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateTemplateParams {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub about: String,
|
||||
pub view_url: String,
|
||||
pub category_ids: Vec<Uuid>,
|
||||
pub creator_id: Uuid,
|
||||
pub is_new_template: bool,
|
||||
pub is_featured: bool,
|
||||
pub related_view_ids: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetTemplatesQueryParams {
|
||||
pub category_id: Option<Uuid>,
|
||||
pub is_featured: Option<bool>,
|
||||
pub is_new_template: Option<bool>,
|
||||
pub name_contains: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateGroup {
|
||||
pub category: TemplateCategoryMinimal,
|
||||
pub templates: Vec<TemplateMinimal>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateHomePage {
|
||||
pub featured_templates: Vec<TemplateMinimal>,
|
||||
pub new_templates: Vec<TemplateMinimal>,
|
||||
pub template_groups: Vec<TemplateGroup>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateHomePageQueryParams {
|
||||
pub per_count: Option<i64>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::dto::{CollabParams, CollabParamsV0};
|
||||
|
|
|
@ -4,7 +4,8 @@ use chrono::{DateTime, Utc};
|
|||
|
||||
use database_entity::dto::{
|
||||
AFAccessLevel, AFRole, AFUserProfile, AFWebUser, AFWorkspace, AFWorkspaceInvitationStatus,
|
||||
AccountLink, GlobalComment, Reaction, TemplateCategory, TemplateCategoryType, TemplateCreator,
|
||||
AccountLink, GlobalComment, Reaction, Template, TemplateCategory, TemplateCategoryMinimal,
|
||||
TemplateCategoryType, TemplateCreator, TemplateCreatorMinimal, TemplateGroup, TemplateMinimal,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
@ -276,9 +277,9 @@ impl From<AFReactionRow> for Reaction {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
#[derive(Debug, FromRow, Serialize, sqlx::Type)]
|
||||
pub struct AFTemplateCategoryRow {
|
||||
pub id: Uuid,
|
||||
pub category_id: Uuid,
|
||||
pub name: String,
|
||||
pub icon: String,
|
||||
pub bg_color: String,
|
||||
|
@ -290,7 +291,7 @@ pub struct AFTemplateCategoryRow {
|
|||
impl From<AFTemplateCategoryRow> for TemplateCategory {
|
||||
fn from(value: AFTemplateCategoryRow) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
id: value.category_id,
|
||||
name: value.name,
|
||||
icon: value.icon,
|
||||
bg_color: value.bg_color,
|
||||
|
@ -301,6 +302,26 @@ impl From<AFTemplateCategoryRow> for TemplateCategory {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow, Serialize, sqlx::Type)]
|
||||
#[sqlx(type_name = "template_category_minimal_type")]
|
||||
pub struct AFTemplateCategoryMinimalRow {
|
||||
pub category_id: Uuid,
|
||||
pub name: String,
|
||||
pub icon: String,
|
||||
pub bg_color: String,
|
||||
}
|
||||
|
||||
impl From<AFTemplateCategoryMinimalRow> for TemplateCategoryMinimal {
|
||||
fn from(value: AFTemplateCategoryMinimalRow) -> Self {
|
||||
Self {
|
||||
id: value.category_id,
|
||||
name: value.name,
|
||||
icon: value.icon,
|
||||
bg_color: value.bg_color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum AFTemplateCategoryTypeColumn {
|
||||
|
@ -327,6 +348,7 @@ impl From<TemplateCategoryType> for AFTemplateCategoryTypeColumn {
|
|||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Debug)]
|
||||
#[sqlx(type_name = "account_link_type")]
|
||||
pub struct AccountLinkColumn {
|
||||
pub link_type: String,
|
||||
pub url: String,
|
||||
|
@ -341,12 +363,14 @@ impl From<AccountLinkColumn> for AccountLink {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Serialize, sqlx::Type)]
|
||||
#[sqlx(type_name = "template_creator_type")]
|
||||
pub struct AFTemplateCreatorRow {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub avatar_url: String,
|
||||
pub account_links: Option<Vec<AccountLinkColumn>>,
|
||||
pub number_of_templates: i32,
|
||||
}
|
||||
|
||||
impl From<AFTemplateCreatorRow> for TemplateCreator {
|
||||
|
@ -362,6 +386,119 @@ impl From<AFTemplateCreatorRow> for TemplateCreator {
|
|||
name: value.name,
|
||||
avatar_url: value.avatar_url,
|
||||
account_links,
|
||||
number_of_templates: value.number_of_templates,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, sqlx::Type)]
|
||||
#[sqlx(type_name = "template_creator_minimal_type")]
|
||||
pub struct AFTemplateCreatorMinimalColumn {
|
||||
pub creator_id: Uuid,
|
||||
pub name: String,
|
||||
pub avatar_url: String,
|
||||
}
|
||||
|
||||
impl From<AFTemplateCreatorMinimalColumn> for TemplateCreatorMinimal {
|
||||
fn from(value: AFTemplateCreatorMinimalColumn) -> Self {
|
||||
Self {
|
||||
id: value.creator_id,
|
||||
name: value.name,
|
||||
avatar_url: value.avatar_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, FromRow, sqlx::Type)]
|
||||
#[sqlx(type_name = "template_minimal_type")]
|
||||
pub struct AFTemplateMinimalRow {
|
||||
pub view_id: Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub view_url: String,
|
||||
pub creator: AFTemplateCreatorMinimalColumn,
|
||||
pub categories: Vec<AFTemplateCategoryMinimalRow>,
|
||||
pub is_new_template: bool,
|
||||
pub is_featured: bool,
|
||||
}
|
||||
|
||||
impl From<AFTemplateMinimalRow> for TemplateMinimal {
|
||||
fn from(value: AFTemplateMinimalRow) -> Self {
|
||||
Self {
|
||||
view_id: value.view_id,
|
||||
created_at: value.created_at,
|
||||
last_updated_at: value.updated_at,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
creator: value.creator.into(),
|
||||
categories: value.categories.into_iter().map(|x| x.into()).collect(),
|
||||
view_url: value.view_url,
|
||||
is_new_template: value.is_new_template,
|
||||
is_featured: value.is_featured,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, sqlx::Type)]
|
||||
pub struct AFTemplateRow {
|
||||
pub view_id: Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub about: String,
|
||||
pub view_url: String,
|
||||
pub creator: AFTemplateCreatorRow,
|
||||
pub categories: Vec<AFTemplateCategoryRow>,
|
||||
pub related_templates: Vec<AFTemplateMinimalRow>,
|
||||
pub is_new_template: bool,
|
||||
pub is_featured: bool,
|
||||
}
|
||||
|
||||
impl From<AFTemplateRow> for Template {
|
||||
fn from(value: AFTemplateRow) -> Self {
|
||||
let mut related_templates: Vec<TemplateMinimal> = value
|
||||
.related_templates
|
||||
.into_iter()
|
||||
.map(|v| v.into())
|
||||
.collect();
|
||||
related_templates.sort_by_key(|t| t.created_at);
|
||||
related_templates.reverse();
|
||||
|
||||
Self {
|
||||
view_id: value.view_id,
|
||||
created_at: value.created_at,
|
||||
last_updated_at: value.updated_at,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
about: value.about,
|
||||
view_url: value.view_url,
|
||||
creator: value.creator.into(),
|
||||
categories: value.categories.into_iter().map(|v| v.into()).collect(),
|
||||
related_templates,
|
||||
is_new_template: value.is_new_template,
|
||||
is_featured: value.is_featured,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, sqlx::Type)]
|
||||
pub struct AFTemplateGroupRow {
|
||||
pub category: AFTemplateCategoryMinimalRow,
|
||||
pub templates: Vec<AFTemplateMinimalRow>,
|
||||
}
|
||||
|
||||
impl From<AFTemplateGroupRow> for TemplateGroup {
|
||||
fn from(value: AFTemplateGroupRow) -> Self {
|
||||
let mut templates: Vec<TemplateMinimal> =
|
||||
value.templates.into_iter().map(|v| v.into()).collect();
|
||||
templates.sort_by_key(|t| t.created_at);
|
||||
templates.reverse();
|
||||
Self {
|
||||
category: value.category.into(),
|
||||
templates,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
use app_error::AppError;
|
||||
use database_entity::dto::{AccountLink, TemplateCategory, TemplateCategoryType, TemplateCreator};
|
||||
use database_entity::dto::{
|
||||
AccountLink, Template, TemplateCategory, TemplateCategoryType, TemplateCreator, TemplateGroup,
|
||||
TemplateMinimal,
|
||||
};
|
||||
use sqlx::{Executor, Postgres, QueryBuilder};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::pg_row::{
|
||||
AFTemplateCategoryRow, AFTemplateCategoryTypeColumn, AFTemplateCreatorRow, AccountLinkColumn,
|
||||
AFTemplateCategoryMinimalRow, AFTemplateCategoryRow, AFTemplateCategoryTypeColumn,
|
||||
AFTemplateCreatorRow, AFTemplateGroupRow, AFTemplateMinimalRow, AFTemplateRow, AccountLinkColumn,
|
||||
};
|
||||
|
||||
pub async fn insert_new_template_category<'a, E: Executor<'a, Database = Postgres>>(
|
||||
|
@ -98,7 +102,7 @@ pub async fn select_template_categories<'a, E: Executor<'a, Database = Postgres>
|
|||
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
|
||||
r#"
|
||||
SELECT
|
||||
category_id AS id,
|
||||
category_id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
|
@ -157,7 +161,7 @@ pub async fn delete_template_category_by_id<'a, E: Executor<'a, Database = Postg
|
|||
executor: E,
|
||||
category_id: Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
let rows_affected = sqlx::query!(
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM af_template_category
|
||||
WHERE category_id = $1
|
||||
|
@ -165,14 +169,7 @@ pub async fn delete_template_category_by_id<'a, E: Executor<'a, Database = Postg
|
|||
category_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?
|
||||
.rows_affected();
|
||||
if rows_affected == 0 {
|
||||
tracing::error!(
|
||||
"No template category with id {} was found to delete",
|
||||
category_id
|
||||
);
|
||||
}
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -210,10 +207,11 @@ pub async fn insert_template_creator<'a, E: Executor<'a, Database = Postgres>>(
|
|||
new_creator.creator_id AS id,
|
||||
name,
|
||||
avatar_url,
|
||||
ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS "account_links: Vec<AccountLinkColumn>"
|
||||
ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS "account_links: Vec<AccountLinkColumn>",
|
||||
0 AS "number_of_templates!"
|
||||
FROM new_creator
|
||||
LEFT OUTER JOIN account_links
|
||||
ON new_creator.creator_id = account_links.creator_id
|
||||
USING (creator_id)
|
||||
GROUP BY (id, name, avatar_url)
|
||||
"#,
|
||||
name,
|
||||
|
@ -258,16 +256,27 @@ pub async fn update_template_creator_by_id<'a, E: Executor<'a, Database = Postgr
|
|||
creator_id,
|
||||
link_type,
|
||||
url
|
||||
),
|
||||
creator_number_of_templates AS (
|
||||
SELECT
|
||||
creator_id,
|
||||
COUNT(1)::int AS number_of_templates
|
||||
FROM af_template_view
|
||||
WHERE creator_id = $1
|
||||
GROUP BY creator_id
|
||||
)
|
||||
SELECT
|
||||
updated_creator.creator_id AS id,
|
||||
name,
|
||||
avatar_url,
|
||||
ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS "account_links: Vec<AccountLinkColumn>"
|
||||
ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS "account_links: Vec<AccountLinkColumn>",
|
||||
COALESCE(number_of_templates, 0) AS "number_of_templates!"
|
||||
FROM updated_creator
|
||||
LEFT OUTER JOIN account_links
|
||||
ON updated_creator.creator_id = account_links.creator_id
|
||||
GROUP BY (id, name, avatar_url)
|
||||
USING (creator_id)
|
||||
LEFT OUTER JOIN creator_number_of_templates
|
||||
USING (creator_id)
|
||||
GROUP BY (id, name, avatar_url, number_of_templates)
|
||||
"#,
|
||||
creator_id,
|
||||
name,
|
||||
|
@ -304,19 +313,31 @@ pub async fn select_template_creators_by_name<'a, E: Executor<'a, Database = Pos
|
|||
let creator_rows = sqlx::query_as!(
|
||||
AFTemplateCreatorRow,
|
||||
r#"
|
||||
WITH creator_number_of_templates AS (
|
||||
SELECT
|
||||
creator_id,
|
||||
COUNT(1)::int AS number_of_templates
|
||||
FROM af_template_view
|
||||
WHERE name ILIKE $1
|
||||
GROUP BY creator_id
|
||||
)
|
||||
|
||||
SELECT
|
||||
tc.creator_id AS "id!",
|
||||
creator.creator_id AS "id!",
|
||||
name AS "name!",
|
||||
avatar_url AS "avatar_url!",
|
||||
ARRAY_AGG((al.link_type, al.url)) FILTER (WHERE link_type IS NOT NULL) AS "account_links: Vec<AccountLinkColumn>"
|
||||
FROM af_template_creator tc
|
||||
LEFT OUTER JOIN af_template_creator_account_link al
|
||||
ON tc.creator_id = al.creator_id
|
||||
WHERE name LIKE $1
|
||||
GROUP BY (tc.creator_id, name, avatar_url)
|
||||
ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS "account_links: Vec<AccountLinkColumn>",
|
||||
COALESCE(number_of_templates, 0) AS "number_of_templates!"
|
||||
FROM af_template_creator creator
|
||||
LEFT OUTER JOIN af_template_creator_account_link account_link
|
||||
USING (creator_id)
|
||||
LEFT OUTER JOIN creator_number_of_templates
|
||||
USING (creator_id)
|
||||
WHERE name ILIKE $1
|
||||
GROUP BY (creator.creator_id, name, avatar_url, number_of_templates)
|
||||
ORDER BY created_at ASC
|
||||
"#,
|
||||
substr_match
|
||||
format!("%{}%", substr_match)
|
||||
)
|
||||
.fetch_all(executor)
|
||||
.await?;
|
||||
|
@ -331,16 +352,27 @@ pub async fn select_template_creator_by_id<'a, E: Executor<'a, Database = Postgr
|
|||
let creator_row = sqlx::query_as!(
|
||||
AFTemplateCreatorRow,
|
||||
r#"
|
||||
WITH creator_number_of_templates AS (
|
||||
SELECT
|
||||
creator_id,
|
||||
COUNT(1)::int AS number_of_templates
|
||||
FROM af_template_view
|
||||
WHERE creator_id = $1
|
||||
GROUP BY creator_id
|
||||
)
|
||||
SELECT
|
||||
tc.creator_id AS "id!",
|
||||
creator.creator_id AS "id!",
|
||||
name AS "name!",
|
||||
avatar_url AS "avatar_url!",
|
||||
ARRAY_AGG((al.link_type, al.url)) FILTER (WHERE link_type IS NOT NULL) AS "account_links: Vec<AccountLinkColumn>"
|
||||
FROM af_template_creator tc
|
||||
LEFT OUTER JOIN af_template_creator_account_link al
|
||||
ON tc.creator_id = al.creator_id
|
||||
WHERE tc.creator_id = $1
|
||||
GROUP BY (tc.creator_id, name, avatar_url)
|
||||
ARRAY_AGG((link_type, url)) FILTER (WHERE link_type IS NOT NULL) AS "account_links: Vec<AccountLinkColumn>",
|
||||
COALESCE(number_of_templates, 0) AS "number_of_templates!"
|
||||
FROM af_template_creator creator
|
||||
LEFT OUTER JOIN af_template_creator_account_link account_link
|
||||
USING (creator_id)
|
||||
LEFT OUTER JOIN creator_number_of_templates
|
||||
USING (creator_id)
|
||||
WHERE creator.creator_id = $1
|
||||
GROUP BY (creator.creator_id, name, avatar_url, number_of_templates)
|
||||
"#,
|
||||
creator_id
|
||||
)
|
||||
|
@ -354,7 +386,7 @@ pub async fn delete_template_creator_by_id<'a, E: Executor<'a, Database = Postgr
|
|||
executor: E,
|
||||
creator_id: Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
let rows_affected = sqlx::query!(
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM af_template_creator
|
||||
WHERE creator_id = $1
|
||||
|
@ -362,13 +394,493 @@ pub async fn delete_template_creator_by_id<'a, E: Executor<'a, Database = Postgr
|
|||
creator_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_template_view_template_category<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: Uuid,
|
||||
category_ids: &[Uuid],
|
||||
) -> Result<(), AppError> {
|
||||
let rows_affected = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO af_template_view_template_category (view_id, category_id)
|
||||
SELECT $1 as view_id, category_id FROM
|
||||
UNNEST($2::uuid[]) AS category_id
|
||||
"#,
|
||||
view_id,
|
||||
category_ids
|
||||
)
|
||||
.execute(executor)
|
||||
.await?
|
||||
.rows_affected();
|
||||
if rows_affected == 0 {
|
||||
tracing::error!(
|
||||
"No template creator with id {} was found to delete",
|
||||
creator_id
|
||||
"at least one category id is expected to be inserted for view_id {}",
|
||||
view_id
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_template_view_template_categories<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM af_template_view_template_category
|
||||
WHERE view_id = $1
|
||||
"#,
|
||||
view_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_related_templates<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: Uuid,
|
||||
category_ids: &[Uuid],
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO af_related_template_view (view_id, related_view_id)
|
||||
SELECT $1 AS view_id, related_view_id
|
||||
FROM UNNEST($2::uuid[]) AS t(related_view_id)
|
||||
"#,
|
||||
view_id,
|
||||
category_ids
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_related_templates<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM af_related_template_view
|
||||
WHERE view_id = $1
|
||||
"#,
|
||||
view_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn insert_template_view<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: Uuid,
|
||||
name: &str,
|
||||
description: &str,
|
||||
about: &str,
|
||||
view_url: &str,
|
||||
creator_id: Uuid,
|
||||
is_new_template: bool,
|
||||
is_featured: bool,
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO af_template_view (
|
||||
view_id,
|
||||
name,
|
||||
description,
|
||||
about,
|
||||
view_url,
|
||||
creator_id,
|
||||
is_new_template,
|
||||
is_featured
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
"#,
|
||||
view_id,
|
||||
name,
|
||||
description,
|
||||
about,
|
||||
view_url,
|
||||
creator_id,
|
||||
is_new_template,
|
||||
is_featured
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn update_template_view<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: Uuid,
|
||||
name: &str,
|
||||
description: &str,
|
||||
about: &str,
|
||||
view_url: &str,
|
||||
creator_id: Uuid,
|
||||
is_new_template: bool,
|
||||
is_featured: bool,
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE af_template_view SET
|
||||
updated_at = NOW(),
|
||||
name = $2,
|
||||
description = $3,
|
||||
about = $4,
|
||||
view_url = $5,
|
||||
creator_id = $6,
|
||||
is_new_template = $7,
|
||||
is_featured = $8
|
||||
WHERE view_id = $1
|
||||
"#,
|
||||
view_id,
|
||||
name,
|
||||
description,
|
||||
about,
|
||||
view_url,
|
||||
creator_id,
|
||||
is_new_template,
|
||||
is_featured
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn select_template_view_by_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: Uuid,
|
||||
) -> Result<Template, AppError> {
|
||||
let view_row = sqlx::query_as!(
|
||||
AFTemplateRow,
|
||||
r#"
|
||||
WITH template_with_creator_account_link AS (
|
||||
SELECT
|
||||
template.view_id,
|
||||
template.creator_id,
|
||||
COALESCE(
|
||||
ARRAY_AGG((link_type, url)::account_link_type) FILTER (WHERE link_type IS NOT NULL),
|
||||
'{}'
|
||||
) AS account_links
|
||||
FROM af_template_view template
|
||||
JOIN af_template_creator creator
|
||||
USING (creator_id)
|
||||
LEFT OUTER JOIN af_template_creator_account_link account_link
|
||||
USING (creator_id)
|
||||
WHERE view_id = $1
|
||||
GROUP BY (view_id, template.creator_id)
|
||||
),
|
||||
related_template_with_category AS (
|
||||
SELECT
|
||||
template.related_view_id,
|
||||
ARRAY_AGG(
|
||||
(
|
||||
template_category.category_id,
|
||||
template_category.name,
|
||||
template_category.icon,
|
||||
template_category.bg_color
|
||||
)::template_category_minimal_type
|
||||
) AS categories
|
||||
FROM af_related_template_view template
|
||||
JOIN af_template_view_template_category template_template_category
|
||||
ON template.related_view_id = template_template_category.view_id
|
||||
JOIN af_template_category template_category
|
||||
USING (category_id)
|
||||
WHERE template.view_id = $1
|
||||
GROUP BY template.related_view_id
|
||||
),
|
||||
template_with_related_template AS (
|
||||
SELECT
|
||||
template.view_id,
|
||||
ARRAY_AGG(
|
||||
(
|
||||
template.related_view_id,
|
||||
related_template.created_at,
|
||||
related_template.updated_at,
|
||||
related_template.name,
|
||||
related_template.description,
|
||||
related_template.view_url,
|
||||
(
|
||||
creator.creator_id,
|
||||
creator.name,
|
||||
creator.avatar_url
|
||||
)::template_creator_minimal_type,
|
||||
related_template_with_category.categories,
|
||||
related_template.is_new_template,
|
||||
related_template.is_featured
|
||||
)::template_minimal_type
|
||||
) AS related_templates
|
||||
FROM af_related_template_view template
|
||||
JOIN af_template_view related_template
|
||||
ON template.related_view_id = related_template.view_id
|
||||
JOIN af_template_creator creator
|
||||
ON related_template.creator_id = creator.creator_id
|
||||
JOIN related_template_with_category
|
||||
ON template.related_view_id = related_template_with_category.related_view_id
|
||||
WHERE template.view_id = $1
|
||||
GROUP BY template.view_id
|
||||
),
|
||||
template_with_category AS (
|
||||
SELECT
|
||||
view_id,
|
||||
COALESCE(
|
||||
ARRAY_AGG((
|
||||
vtc.category_id,
|
||||
name,
|
||||
icon,
|
||||
bg_color,
|
||||
description,
|
||||
category_type,
|
||||
priority
|
||||
)) FILTER (WHERE vtc.category_id IS NOT NULL),
|
||||
'{}'
|
||||
) AS categories
|
||||
FROM af_template_view_template_category vtc
|
||||
JOIN af_template_category tc
|
||||
ON vtc.category_id = tc.category_id
|
||||
WHERE view_id = $1
|
||||
GROUP BY view_id
|
||||
),
|
||||
creator_number_of_templates AS (
|
||||
SELECT
|
||||
creator_id,
|
||||
COUNT(*) AS number_of_templates
|
||||
FROM af_template_view
|
||||
GROUP BY creator_id
|
||||
)
|
||||
|
||||
SELECT
|
||||
template.view_id,
|
||||
template.created_at,
|
||||
template.updated_at,
|
||||
template.name,
|
||||
template.description,
|
||||
template.about,
|
||||
template.view_url,
|
||||
(
|
||||
creator.creator_id,
|
||||
creator.name,
|
||||
creator.avatar_url,
|
||||
template_with_creator_account_link.account_links,
|
||||
creator_number_of_templates.number_of_templates
|
||||
)::template_creator_type AS "creator!: AFTemplateCreatorRow",
|
||||
template_with_category.categories AS "categories!: Vec<AFTemplateCategoryRow>",
|
||||
COALESCE(template_with_related_template.related_templates, '{}') AS "related_templates!: Vec<AFTemplateMinimalRow>",
|
||||
template.is_new_template,
|
||||
template.is_featured
|
||||
FROM af_template_view template
|
||||
JOIN af_template_creator creator
|
||||
USING (creator_id)
|
||||
JOIN template_with_creator_account_link
|
||||
ON template.view_id = template_with_creator_account_link.view_id
|
||||
LEFT OUTER JOIN template_with_related_template
|
||||
ON template.view_id = template_with_related_template.view_id
|
||||
JOIN template_with_category
|
||||
ON template.view_id = template_with_category.view_id
|
||||
LEFT OUTER JOIN creator_number_of_templates
|
||||
ON template.creator_id = creator_number_of_templates.creator_id
|
||||
WHERE template.view_id = $1
|
||||
|
||||
"#,
|
||||
view_id
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
let view = view_row.into();
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
pub async fn select_templates<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
category_id: Option<Uuid>,
|
||||
is_featured: Option<bool>,
|
||||
is_new_template: Option<bool>,
|
||||
name_contains: Option<&str>,
|
||||
limit: Option<i64>,
|
||||
) -> Result<Vec<TemplateMinimal>, AppError> {
|
||||
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
|
||||
r#"
|
||||
WITH template_with_template_category AS (
|
||||
SELECT
|
||||
template_template_category.view_id,
|
||||
ARRAY_AGG((
|
||||
template_template_category.category_id,
|
||||
category.name,
|
||||
category.icon,
|
||||
category.bg_color
|
||||
)::template_category_minimal_type) AS categories
|
||||
FROM af_template_view_template_category template_template_category
|
||||
JOIN af_template_category category
|
||||
USING (category_id)
|
||||
JOIN af_template_view template
|
||||
USING (view_id)
|
||||
WHERE TRUE
|
||||
"#,
|
||||
);
|
||||
if let Some(category_id) = category_id {
|
||||
query_builder.push(" AND template_template_category.category_id = ");
|
||||
query_builder.push_bind(category_id);
|
||||
};
|
||||
if let Some(is_featured) = is_featured {
|
||||
query_builder.push(" AND template.is_featured = ");
|
||||
query_builder.push_bind(is_featured);
|
||||
};
|
||||
if let Some(is_new_template) = is_new_template {
|
||||
query_builder.push(" AND template.is_new_template = ");
|
||||
query_builder.push_bind(is_new_template);
|
||||
};
|
||||
if let Some(name_contains) = name_contains {
|
||||
query_builder.push(" AND template.name ILIKE CONCAT('%', ");
|
||||
query_builder.push_bind(name_contains);
|
||||
query_builder.push(" , '%')");
|
||||
};
|
||||
query_builder.push(
|
||||
r#"
|
||||
GROUP BY template_template_category.view_id
|
||||
)
|
||||
|
||||
SELECT
|
||||
template.view_id,
|
||||
template.created_at,
|
||||
template.updated_at,
|
||||
template.name,
|
||||
template.description,
|
||||
template.view_url,
|
||||
(
|
||||
template_creator.creator_id,
|
||||
template_creator.name,
|
||||
template_creator.avatar_url
|
||||
)::template_creator_minimal_type AS creator,
|
||||
tc.categories AS categories,
|
||||
template.is_new_template,
|
||||
template.is_featured
|
||||
FROM template_with_template_category tc
|
||||
JOIN af_template_view template
|
||||
USING (view_id)
|
||||
JOIN af_template_creator template_creator
|
||||
USING (creator_id)
|
||||
ORDER BY template.created_at DESC
|
||||
"#,
|
||||
);
|
||||
if let Some(limit) = limit {
|
||||
query_builder.push(" LIMIT ");
|
||||
query_builder.push_bind(limit);
|
||||
};
|
||||
let query = query_builder.build_query_as::<AFTemplateMinimalRow>();
|
||||
let template_rows: Vec<AFTemplateMinimalRow> = query.fetch_all(executor).await?;
|
||||
Ok(template_rows.into_iter().map(|row| row.into()).collect())
|
||||
}
|
||||
|
||||
pub async fn select_template_homepage<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
per_count: i64,
|
||||
) -> Result<Vec<TemplateGroup>, AppError> {
|
||||
let template_group_rows = sqlx::query_as!(
|
||||
AFTemplateGroupRow,
|
||||
r#"
|
||||
WITH recent_template AS (
|
||||
SELECT
|
||||
template_template_category.category_id,
|
||||
template_template_category.view_id,
|
||||
category.name,
|
||||
category.icon,
|
||||
category.bg_color,
|
||||
ROW_NUMBER() OVER (PARTITION BY template_template_category.category_id ORDER BY template.created_at DESC) AS recency
|
||||
FROM af_template_view_template_category template_template_category
|
||||
JOIN af_template_category category
|
||||
USING (category_id)
|
||||
JOIN af_template_view template
|
||||
USING (view_id)
|
||||
),
|
||||
template_group_by_category_and_view AS (
|
||||
SELECT
|
||||
category_id,
|
||||
view_id,
|
||||
ARRAY_AGG((
|
||||
category_id,
|
||||
name,
|
||||
icon,
|
||||
bg_color
|
||||
)::template_category_minimal_type) AS categories
|
||||
FROM recent_template
|
||||
WHERE recency <= $1
|
||||
GROUP BY category_id, view_id
|
||||
),
|
||||
template_group_by_category_and_view_with_creator_and_template_details AS (
|
||||
SELECT
|
||||
template_group_by_category_and_view.category_id,
|
||||
(
|
||||
template.view_id,
|
||||
template.created_at,
|
||||
template.updated_at,
|
||||
template.name,
|
||||
template.description,
|
||||
template.view_url,
|
||||
(
|
||||
creator.creator_id,
|
||||
creator.name,
|
||||
creator.avatar_url
|
||||
)::template_creator_minimal_type,
|
||||
template_group_by_category_and_view.categories,
|
||||
template.is_new_template,
|
||||
template.is_featured
|
||||
)::template_minimal_type AS template
|
||||
FROM template_group_by_category_and_view
|
||||
JOIN af_template_view template
|
||||
USING (view_id)
|
||||
JOIN af_template_creator creator
|
||||
USING (creator_id)
|
||||
),
|
||||
template_group_by_category AS (
|
||||
SELECT
|
||||
category_id,
|
||||
ARRAY_AGG(template) AS templates
|
||||
FROM template_group_by_category_and_view_with_creator_and_template_details
|
||||
GROUP BY category_id
|
||||
)
|
||||
SELECT
|
||||
(
|
||||
template_group_by_category.category_id,
|
||||
category.name,
|
||||
category.icon,
|
||||
category.bg_color
|
||||
)::template_category_minimal_type AS "category!: AFTemplateCategoryMinimalRow",
|
||||
templates AS "templates!: Vec<AFTemplateMinimalRow>"
|
||||
FROM template_group_by_category
|
||||
JOIN af_template_category category
|
||||
USING (category_id)
|
||||
"#,
|
||||
per_count,
|
||||
)
|
||||
.fetch_all(executor)
|
||||
.await?;
|
||||
Ok(
|
||||
template_group_rows
|
||||
.into_iter()
|
||||
.map(|row| row.into())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn delete_template_by_view_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM af_template_view
|
||||
WHERE view_id = $1
|
||||
"#,
|
||||
view_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
85
migrations/20240813040905_template.sql
Normal file
85
migrations/20240813040905_template.sql
Normal file
|
@ -0,0 +1,85 @@
|
|||
-- Appflowy template is based on a published view. Template information should be preserved even if the
|
||||
-- published view is deleted.
|
||||
CREATE TABLE IF NOT EXISTS af_template_view (
|
||||
view_id UUID NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
about TEXT NOT NULL,
|
||||
view_url TEXT NOT NULL,
|
||||
creator_id UUID NOT NULL REFERENCES af_template_creator(creator_id) ON DELETE CASCADE,
|
||||
is_new_template BOOLEAN NOT NULL,
|
||||
is_featured BOOLEAN NOT NULL,
|
||||
|
||||
PRIMARY KEY (view_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS af_template_view_template_category (
|
||||
view_id UUID NOT NULL REFERENCES af_template_view(view_id) ON DELETE CASCADE,
|
||||
category_id UUID NOT NULL REFERENCES af_template_category(category_id) ON DELETE CASCADE,
|
||||
|
||||
PRIMARY KEY (view_id, category_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS af_related_template_view (
|
||||
view_id UUID NOT NULL REFERENCES af_template_view(view_id) ON DELETE CASCADE,
|
||||
related_view_id UUID NOT NULL REFERENCES af_template_view(view_id) ON DELETE CASCADE,
|
||||
|
||||
PRIMARY KEY (view_id, related_view_id)
|
||||
);
|
||||
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'account_link_type') THEN
|
||||
CREATE TYPE account_link_type AS (
|
||||
link_type TEXT,
|
||||
url TEXT
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'template_creator_type') THEN
|
||||
CREATE TYPE template_creator_type AS (
|
||||
creator_id UUID,
|
||||
name TEXT,
|
||||
avatar_url TEXT,
|
||||
account_links account_link_type[],
|
||||
number_of_templates INT
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'template_creator_minimal_type') THEN
|
||||
CREATE TYPE template_creator_minimal_type AS (
|
||||
creator_id UUID,
|
||||
name TEXT,
|
||||
avatar_url TEXT
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'template_category_minimal_type') THEN
|
||||
CREATE TYPE template_category_minimal_type AS (
|
||||
category_id UUID,
|
||||
name TEXT,
|
||||
icon TEXT,
|
||||
bg_color TEXT
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'template_minimal_type') THEN
|
||||
CREATE TYPE template_minimal_type AS (
|
||||
view_id UUID,
|
||||
created_at TIMESTAMP WITH TIME ZONE,
|
||||
updated_at TIMESTAMP WITH TIME ZONE,
|
||||
name TEXT,
|
||||
description TEXT,
|
||||
view_url TEXT,
|
||||
creator template_creator_minimal_type,
|
||||
categories template_category_minimal_type[],
|
||||
is_new_template BOOLEAN,
|
||||
is_featured BOOLEAN
|
||||
);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
|
@ -3,21 +3,16 @@ use actix_web::{
|
|||
Result, Scope,
|
||||
};
|
||||
use database_entity::dto::{
|
||||
CreateTemplateCategoryParams, CreateTemplateCreatorParams, GetTemplateCategoriesQueryParams,
|
||||
GetTemplateCreatorsQueryParams, TemplateCategories, TemplateCategory, TemplateCreator,
|
||||
TemplateCreators, UpdateTemplateCategoryParams, UpdateTemplateCreatorParams,
|
||||
CreateTemplateCategoryParams, CreateTemplateCreatorParams, CreateTemplateParams,
|
||||
GetTemplateCategoriesQueryParams, GetTemplateCreatorsQueryParams, GetTemplatesQueryParams,
|
||||
Template, TemplateCategories, TemplateCategory, TemplateCreator, TemplateCreators,
|
||||
TemplateHomePage, TemplateHomePageQueryParams, Templates, UpdateTemplateCategoryParams,
|
||||
UpdateTemplateCreatorParams, UpdateTemplateParams,
|
||||
};
|
||||
use shared_entity::response::{AppResponse, JsonAppResponse};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
biz::template::ops::{
|
||||
create_new_template_category, create_new_template_creator, delete_template_category,
|
||||
delete_template_creator, get_template_categories, get_template_category, get_template_creator,
|
||||
get_template_creators, update_template_category, update_template_creator,
|
||||
},
|
||||
state::AppState,
|
||||
};
|
||||
use crate::{biz::template::ops::*, state::AppState};
|
||||
|
||||
pub fn template_scope() -> Scope {
|
||||
web::scope("/api/template-center")
|
||||
|
@ -43,6 +38,18 @@ pub fn template_scope() -> Scope {
|
|||
.route(web::get().to(get_template_creator_handler))
|
||||
.route(web::delete().to(delete_template_creator_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/template")
|
||||
.route(web::post().to(post_template_handler))
|
||||
.route(web::get().to(list_templates_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/template/{view_id}")
|
||||
.route(web::put().to(update_template_handler))
|
||||
.route(web::get().to(get_template_handler))
|
||||
.route(web::delete().to(delete_template_handler)),
|
||||
)
|
||||
.service(web::resource("/homepage").route(web::get().to(get_template_homepage_handler)))
|
||||
}
|
||||
|
||||
async fn post_template_category_handler(
|
||||
|
@ -173,3 +180,91 @@ async fn delete_template_creator_handler(
|
|||
delete_template_creator(&state.pg_pool, creator_id).await?;
|
||||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
||||
async fn post_template_handler(
|
||||
data: Json<CreateTemplateParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<Template>> {
|
||||
let new_template = create_new_template(
|
||||
&state.pg_pool,
|
||||
data.view_id,
|
||||
&data.name,
|
||||
&data.description,
|
||||
&data.about,
|
||||
&data.view_url,
|
||||
data.creator_id,
|
||||
data.is_new_template,
|
||||
data.is_featured,
|
||||
&data.category_ids,
|
||||
&data.related_view_ids,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(new_template)))
|
||||
}
|
||||
|
||||
async fn list_templates_handler(
|
||||
data: web::Query<GetTemplatesQueryParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<Templates>> {
|
||||
let data = data.into_inner();
|
||||
let template_summary_list = get_templates(
|
||||
&state.pg_pool,
|
||||
data.category_id,
|
||||
data.is_featured,
|
||||
data.is_new_template,
|
||||
data.name_contains.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(Templates {
|
||||
templates: template_summary_list,
|
||||
})))
|
||||
}
|
||||
|
||||
async fn get_template_handler(
|
||||
view_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<Template>> {
|
||||
let view_id = view_id.into_inner();
|
||||
let template = get_template(&state.pg_pool, view_id).await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(template)))
|
||||
}
|
||||
|
||||
async fn update_template_handler(
|
||||
view_id: web::Path<Uuid>,
|
||||
data: Json<UpdateTemplateParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<Template>> {
|
||||
let view_id = view_id.into_inner();
|
||||
let updated_template = update_template(
|
||||
&state.pg_pool,
|
||||
view_id,
|
||||
&data.name,
|
||||
&data.description,
|
||||
&data.about,
|
||||
&data.view_url,
|
||||
data.creator_id,
|
||||
data.is_new_template,
|
||||
data.is_featured,
|
||||
&data.category_ids,
|
||||
&data.related_view_ids,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(updated_template)))
|
||||
}
|
||||
|
||||
async fn delete_template_handler(
|
||||
view_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
let view_id = view_id.into_inner();
|
||||
delete_template(&state.pg_pool, view_id).await?;
|
||||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
||||
async fn get_template_homepage_handler(
|
||||
query: web::Query<TemplateHomePageQueryParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateHomePage>> {
|
||||
let template_homepage = get_template_homepage(&state.pg_pool, query.per_count).await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(template_homepage)))
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
use std::ops::DerefMut;
|
||||
|
||||
use anyhow::Context;
|
||||
use database::template::{
|
||||
delete_template_category_by_id, delete_template_creator_account_links,
|
||||
delete_template_creator_by_id, insert_new_template_category, insert_template_creator,
|
||||
select_template_categories, select_template_category_by_id, select_template_creator_by_id,
|
||||
select_template_creators_by_name, update_template_category_by_id, update_template_creator_by_id,
|
||||
use database::template::*;
|
||||
use database_entity::dto::{
|
||||
AccountLink, Template, TemplateCategory, TemplateCategoryType, TemplateCreator, TemplateHomePage,
|
||||
TemplateMinimal,
|
||||
};
|
||||
use database_entity::dto::{AccountLink, TemplateCategory, TemplateCategoryType, TemplateCreator};
|
||||
use shared_entity::response::AppResponseError;
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
@ -141,3 +139,134 @@ pub async fn delete_template_creator(
|
|||
delete_template_creator_by_id(pg_pool, creator_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn create_new_template(
|
||||
pg_pool: &PgPool,
|
||||
view_id: Uuid,
|
||||
name: &str,
|
||||
description: &str,
|
||||
about: &str,
|
||||
view_url: &str,
|
||||
creator_id: Uuid,
|
||||
is_new_template: bool,
|
||||
is_featured: bool,
|
||||
category_ids: &[Uuid],
|
||||
related_view_ids: &[Uuid],
|
||||
) -> Result<Template, AppResponseError> {
|
||||
let mut txn = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("Begin transaction to create template creator")?;
|
||||
insert_template_view(
|
||||
txn.deref_mut(),
|
||||
view_id,
|
||||
name,
|
||||
description,
|
||||
about,
|
||||
view_url,
|
||||
creator_id,
|
||||
is_new_template,
|
||||
is_featured,
|
||||
)
|
||||
.await?;
|
||||
insert_template_view_template_category(txn.deref_mut(), view_id, category_ids).await?;
|
||||
insert_related_templates(txn.deref_mut(), view_id, related_view_ids).await?;
|
||||
let new_template = select_template_view_by_id(txn.deref_mut(), view_id).await?;
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("Commit transaction to update template creator")?;
|
||||
Ok(new_template)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn update_template(
|
||||
pg_pool: &PgPool,
|
||||
view_id: Uuid,
|
||||
name: &str,
|
||||
description: &str,
|
||||
about: &str,
|
||||
view_url: &str,
|
||||
creator_id: Uuid,
|
||||
is_new_template: bool,
|
||||
is_featured: bool,
|
||||
category_ids: &[Uuid],
|
||||
related_view_ids: &[Uuid],
|
||||
) -> Result<Template, AppResponseError> {
|
||||
let mut txn = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("Begin transaction to update template")?;
|
||||
delete_template_view_template_categories(txn.deref_mut(), view_id).await?;
|
||||
delete_related_templates(txn.deref_mut(), view_id).await?;
|
||||
update_template_view(
|
||||
txn.deref_mut(),
|
||||
view_id,
|
||||
name,
|
||||
description,
|
||||
about,
|
||||
view_url,
|
||||
creator_id,
|
||||
is_new_template,
|
||||
is_featured,
|
||||
)
|
||||
.await?;
|
||||
insert_template_view_template_category(txn.deref_mut(), view_id, category_ids).await?;
|
||||
insert_related_templates(txn.deref_mut(), view_id, related_view_ids).await?;
|
||||
let updated_template = select_template_view_by_id(txn.deref_mut(), view_id).await?;
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("Commit transaction to update template")?;
|
||||
Ok(updated_template)
|
||||
}
|
||||
|
||||
pub async fn get_templates(
|
||||
pg_pool: &PgPool,
|
||||
category_id: Option<Uuid>,
|
||||
is_featured: Option<bool>,
|
||||
is_new_template: Option<bool>,
|
||||
name_contains: Option<&str>,
|
||||
) -> Result<Vec<TemplateMinimal>, AppResponseError> {
|
||||
let templates = select_templates(
|
||||
pg_pool,
|
||||
category_id,
|
||||
is_featured,
|
||||
is_new_template,
|
||||
name_contains,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(templates)
|
||||
}
|
||||
|
||||
pub async fn get_template(pg_pool: &PgPool, view_id: Uuid) -> Result<Template, AppResponseError> {
|
||||
let template = select_template_view_by_id(pg_pool, view_id).await?;
|
||||
Ok(template)
|
||||
}
|
||||
|
||||
pub async fn delete_template(pg_pool: &PgPool, view_id: Uuid) -> Result<(), AppResponseError> {
|
||||
delete_template_by_view_id(pg_pool, view_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const DEFAULT_HOMEPAGE_CATEGORY_COUNT: i64 = 10;
|
||||
|
||||
pub async fn get_template_homepage(
|
||||
pg_pool: &PgPool,
|
||||
per_count: Option<i64>,
|
||||
) -> Result<TemplateHomePage, AppResponseError> {
|
||||
let per_count = per_count.unwrap_or(DEFAULT_HOMEPAGE_CATEGORY_COUNT);
|
||||
let template_groups = select_template_homepage(pg_pool, per_count).await?;
|
||||
let featured_templates =
|
||||
select_templates(pg_pool, None, Some(true), None, None, Some(per_count)).await?;
|
||||
let new_templates =
|
||||
select_templates(pg_pool, None, None, Some(true), None, Some(per_count)).await?;
|
||||
let homepage = TemplateHomePage {
|
||||
template_groups,
|
||||
featured_templates,
|
||||
new_templates,
|
||||
};
|
||||
Ok(homepage)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use app_error::ErrorCode;
|
||||
use client_api::entity::{
|
||||
AccountLink, CreateTemplateCategoryParams, TemplateCategoryType, UpdateTemplateCategoryParams,
|
||||
AccountLink, CreateTemplateCategoryParams, CreateTemplateParams, PublishCollabItem,
|
||||
PublishCollabMetadata, TemplateCategoryType, UpdateTemplateCategoryParams, UpdateTemplateParams,
|
||||
};
|
||||
use client_api_test::*;
|
||||
use collab::core::collab::DataSource;
|
||||
|
@ -10,6 +13,16 @@ use collab_entity::CollabType;
|
|||
use database_entity::dto::{QueryCollab, QueryCollabParams};
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn get_first_workspace_string(c: &client_api::Client) -> String {
|
||||
c.get_workspaces()
|
||||
.await
|
||||
.unwrap()
|
||||
.first()
|
||||
.unwrap()
|
||||
.workspace_id
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_user_default_workspace_test() {
|
||||
let email = generate_unique_email();
|
||||
|
@ -178,11 +191,13 @@ async fn test_template_creator_crud() {
|
|||
link_type: "reddit".to_string(),
|
||||
url: "reddit_url".to_string(),
|
||||
}];
|
||||
let creator_name_prefix = Uuid::new_v4().to_string();
|
||||
let creator_name = format!("{}-name", creator_name_prefix);
|
||||
let new_creator = authorized_client
|
||||
.create_template_creator("name", "avatar_url", account_links)
|
||||
.create_template_creator(creator_name.as_str(), "avatar_url", account_links)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(new_creator.name, "name");
|
||||
assert_eq!(new_creator.name, creator_name);
|
||||
assert_eq!(new_creator.avatar_url, "avatar_url");
|
||||
assert_eq!(new_creator.account_links.len(), 1);
|
||||
assert_eq!(new_creator.account_links[0].link_type, "reddit");
|
||||
|
@ -197,16 +212,17 @@ async fn test_template_creator_crud() {
|
|||
link_type: "twitter".to_string(),
|
||||
url: "twitter_url".to_string(),
|
||||
}];
|
||||
let updated_creator_name = format!("{}-new_name", creator_name_prefix);
|
||||
let updated_creator = authorized_client
|
||||
.update_template_creator(
|
||||
new_creator.id,
|
||||
"new_name",
|
||||
updated_creator_name.as_str(),
|
||||
"new_avatar_url",
|
||||
updated_account_links,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(updated_creator.name, "new_name");
|
||||
assert_eq!(updated_creator.name, updated_creator_name);
|
||||
assert_eq!(updated_creator.avatar_url, "new_avatar_url");
|
||||
assert_eq!(updated_creator.account_links.len(), 1);
|
||||
assert_eq!(updated_creator.account_links[0].link_type, "twitter");
|
||||
|
@ -216,12 +232,19 @@ async fn test_template_creator_crud() {
|
|||
.get_template_creator(new_creator.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(creator.name, "new_name");
|
||||
assert_eq!(creator.name, updated_creator_name);
|
||||
assert_eq!(creator.avatar_url, "new_avatar_url");
|
||||
assert_eq!(creator.account_links.len(), 1);
|
||||
assert_eq!(creator.account_links[0].link_type, "twitter");
|
||||
assert_eq!(creator.account_links[0].url, "twitter_url");
|
||||
|
||||
let creators = guest_client
|
||||
.get_template_creators(Some(creator_name_prefix).as_deref())
|
||||
.await
|
||||
.unwrap()
|
||||
.creators;
|
||||
assert_eq!(creators.len(), 1);
|
||||
|
||||
let result = guest_client.delete_template_creator(new_creator.id).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::NotLoggedIn);
|
||||
|
@ -233,3 +256,215 @@ async fn test_template_creator_crud() {
|
|||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::RecordNotFound);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_template_crud() {
|
||||
let (authorized_client, _) = generate_unique_registered_user_client().await;
|
||||
let workspace_id = get_first_workspace_string(&authorized_client).await;
|
||||
let published_view_namespace = uuid::Uuid::new_v4().to_string();
|
||||
authorized_client
|
||||
.set_workspace_publish_namespace(&workspace_id.to_string(), &published_view_namespace)
|
||||
.await
|
||||
.unwrap();
|
||||
let published_view_ids: Vec<Uuid> = (0..4).map(|_| Uuid::new_v4()).collect();
|
||||
let published_collab_items: Vec<PublishCollabItem<TemplateMetadata, &[u8]>> = published_view_ids
|
||||
.iter()
|
||||
.map(|view_id| PublishCollabItem {
|
||||
meta: PublishCollabMetadata {
|
||||
view_id: *view_id,
|
||||
publish_name: view_id.to_string(),
|
||||
metadata: TemplateMetadata {},
|
||||
},
|
||||
data: "yrs_encoded_data_1".as_bytes(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
authorized_client
|
||||
.publish_collabs::<TemplateMetadata, &[u8]>(&workspace_id, published_collab_items)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let category_prefix = Uuid::new_v4().to_string();
|
||||
let category_1_name = format!("{}_1", category_prefix);
|
||||
let category_2_name = format!("{}_2", category_prefix);
|
||||
|
||||
let creator_1 = authorized_client
|
||||
.create_template_creator(
|
||||
"template_creator 1",
|
||||
"avatar_url",
|
||||
vec![AccountLink {
|
||||
link_type: "reddit".to_string(),
|
||||
url: "reddit_url".to_string(),
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let creator_2 = authorized_client
|
||||
.create_template_creator(
|
||||
"template_creator 2",
|
||||
"avatar_url",
|
||||
vec![AccountLink {
|
||||
link_type: "facebook".to_string(),
|
||||
url: "facebook_url".to_string(),
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let params = CreateTemplateCategoryParams {
|
||||
name: category_1_name,
|
||||
icon: "icon".to_string(),
|
||||
bg_color: "bg_color".to_string(),
|
||||
description: "description".to_string(),
|
||||
category_type: TemplateCategoryType::Feature,
|
||||
priority: 0,
|
||||
};
|
||||
let category_1 = authorized_client
|
||||
.create_template_category(¶ms)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let params = CreateTemplateCategoryParams {
|
||||
name: category_2_name,
|
||||
icon: "icon".to_string(),
|
||||
bg_color: "bg_color".to_string(),
|
||||
description: "description".to_string(),
|
||||
category_type: TemplateCategoryType::Feature,
|
||||
priority: 0,
|
||||
};
|
||||
let category_2 = authorized_client
|
||||
.create_template_category(¶ms)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let template_name_prefix = Uuid::new_v4().to_string();
|
||||
for (index, view_id) in published_view_ids[0..2].iter().enumerate() {
|
||||
let is_new_template = index % 2 == 0;
|
||||
let is_featured = true;
|
||||
let category_id = category_1.id;
|
||||
let params = CreateTemplateParams {
|
||||
view_id: *view_id,
|
||||
name: format!("{}-{}", template_name_prefix, view_id),
|
||||
description: "description".to_string(),
|
||||
about: "about".to_string(),
|
||||
view_url: "view_url".to_string(),
|
||||
category_ids: vec![category_id],
|
||||
creator_id: creator_1.id,
|
||||
is_new_template,
|
||||
is_featured,
|
||||
related_view_ids: vec![],
|
||||
};
|
||||
let template = authorized_client.create_template(¶ms).await.unwrap();
|
||||
assert_eq!(template.view_id, *view_id);
|
||||
assert_eq!(template.categories.len(), 1);
|
||||
assert_eq!(template.categories[0].id, category_id);
|
||||
assert_eq!(template.creator.id, creator_1.id);
|
||||
assert_eq!(template.creator.name, creator_1.name);
|
||||
assert_eq!(template.creator.account_links.len(), 1);
|
||||
assert_eq!(
|
||||
template.creator.account_links[0].url,
|
||||
creator_1.account_links[0].url
|
||||
);
|
||||
assert!(template.related_templates.is_empty())
|
||||
}
|
||||
|
||||
for (index, view_id) in published_view_ids[2..4].iter().enumerate() {
|
||||
let is_new_template = index % 2 == 0;
|
||||
let is_featured = false;
|
||||
let category_id = category_2.id;
|
||||
let params = CreateTemplateParams {
|
||||
view_id: *view_id,
|
||||
name: format!("{}-{}", template_name_prefix, view_id),
|
||||
description: "description".to_string(),
|
||||
about: "about".to_string(),
|
||||
view_url: "view_url".to_string(),
|
||||
category_ids: vec![category_id],
|
||||
creator_id: creator_2.id,
|
||||
is_new_template,
|
||||
is_featured,
|
||||
related_view_ids: vec![published_view_ids[0]],
|
||||
};
|
||||
let template = authorized_client.create_template(¶ms).await.unwrap();
|
||||
assert_eq!(template.related_templates.len(), 1);
|
||||
assert_eq!(template.related_templates[0].view_id, published_view_ids[0]);
|
||||
assert_eq!(template.related_templates[0].creator.id, creator_1.id);
|
||||
assert_eq!(template.related_templates[0].categories.len(), 1);
|
||||
assert_eq!(
|
||||
template.related_templates[0].categories[0].id,
|
||||
category_1.id
|
||||
);
|
||||
}
|
||||
|
||||
let guest_client = localhost_client();
|
||||
let templates = guest_client
|
||||
.get_templates(
|
||||
Some(category_2.id),
|
||||
None,
|
||||
None,
|
||||
Some(template_name_prefix.clone()),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.templates;
|
||||
let view_ids: HashSet<Uuid> = templates.iter().map(|t| t.view_id).collect();
|
||||
assert_eq!(templates.len(), 2);
|
||||
assert!(view_ids.contains(&published_view_ids[2]));
|
||||
assert!(view_ids.contains(&published_view_ids[3]));
|
||||
|
||||
let featured_templates = guest_client
|
||||
.get_templates(None, Some(true), None, Some(template_name_prefix.clone()))
|
||||
.await
|
||||
.unwrap()
|
||||
.templates;
|
||||
let featured_view_ids: HashSet<Uuid> = featured_templates.iter().map(|t| t.view_id).collect();
|
||||
assert_eq!(featured_templates.len(), 2);
|
||||
assert!(featured_view_ids.contains(&published_view_ids[0]));
|
||||
assert!(featured_view_ids.contains(&published_view_ids[1]));
|
||||
|
||||
let new_templates = guest_client
|
||||
.get_templates(None, None, Some(true), Some(template_name_prefix.clone()))
|
||||
.await
|
||||
.unwrap()
|
||||
.templates;
|
||||
let new_view_ids: HashSet<Uuid> = new_templates.iter().map(|t| t.view_id).collect();
|
||||
assert_eq!(new_templates.len(), 2);
|
||||
assert!(new_view_ids.contains(&published_view_ids[0]));
|
||||
assert!(new_view_ids.contains(&published_view_ids[2]));
|
||||
|
||||
let template = guest_client
|
||||
.get_template(published_view_ids[3])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(template.view_id, published_view_ids[3]);
|
||||
assert_eq!(template.creator.id, creator_2.id);
|
||||
assert_eq!(template.categories.len(), 1);
|
||||
assert_eq!(template.categories[0].id, category_2.id);
|
||||
assert_eq!(template.related_templates.len(), 1);
|
||||
assert_eq!(template.related_templates[0].view_id, published_view_ids[0]);
|
||||
|
||||
let params = UpdateTemplateParams {
|
||||
name: format!("{}-{}", template_name_prefix, published_view_ids[3]),
|
||||
description: "description".to_string(),
|
||||
about: "about".to_string(),
|
||||
view_url: "view_url".to_string(),
|
||||
category_ids: vec![category_1.id],
|
||||
creator_id: creator_2.id,
|
||||
is_new_template: false,
|
||||
is_featured: true,
|
||||
related_view_ids: vec![published_view_ids[0]],
|
||||
};
|
||||
authorized_client
|
||||
.update_template(published_view_ids[3], ¶ms)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
authorized_client
|
||||
.delete_template(published_view_ids[3])
|
||||
.await
|
||||
.unwrap();
|
||||
let resp = guest_client.get_template(published_view_ids[3]).await;
|
||||
assert!(resp.is_err());
|
||||
assert_eq!(resp.unwrap_err().code, ErrorCode::RecordNotFound);
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct TemplateMetadata {}
|
||||
|
|
Loading…
Add table
Reference in a new issue