mirror of
https://github.com/AppFlowy-IO/AppFlowy-Cloud.git
synced 2025-04-19 03:24:42 -04:00
feat: api endpoint for template categories and creators
This commit is contained in:
parent
ea27e87103
commit
f6e78a941f
32 changed files with 1784 additions and 71 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n avr.comment_id,\n avr.reaction_type,\n ARRAY_AGG((au.uuid, au.name, au.metadata ->> 'icon_url')) AS \"react_users!: Vec<AFWebUserType>\"\n FROM af_published_view_reaction avr\n INNER JOIN af_user au ON avr.created_by = au.uid\n WHERE view_id = $1\n GROUP BY comment_id, reaction_type\n ORDER BY MIN(avr.created_at)\n ",
|
||||
"query": "\n SELECT\n avr.comment_id,\n avr.reaction_type,\n ARRAY_AGG((au.uuid, au.name, au.metadata ->> 'icon_url')) AS \"react_users!: Vec<AFWebUserColumn>\"\n FROM af_published_view_reaction avr\n INNER JOIN af_user au ON avr.created_by = au.uid\n WHERE view_id = $1\n GROUP BY comment_id, reaction_type\n ORDER BY MIN(avr.created_at)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -15,7 +15,7 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "react_users!: Vec<AFWebUserType>",
|
||||
"name": "react_users!: Vec<AFWebUserColumn>",
|
||||
"type_info": "RecordArray"
|
||||
}
|
||||
],
|
||||
|
@ -30,5 +30,5 @@
|
|||
null
|
||||
]
|
||||
},
|
||||
"hash": "da2614b887d500a0660930a15ca18a083a5535f1442602475c5d62350d88761f"
|
||||
"hash": "056174448a2ff0744b5943ba6d303b180ca9016cd26d284686f445c060cec4c5"
|
||||
}
|
44
.sqlx/query-14dfaee23af2d3206acf9141b9bdcafb08aaf496f334e4b9292f88740f872855.json
generated
Normal file
44
.sqlx/query-14dfaee23af2d3206acf9141b9bdcafb08aaf496f334e4b9292f88740f872855.json
generated
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH\n updated_creator AS (\n UPDATE af_template_creator\n SET name = $2, avatar_url = $3\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 ",
|
||||
"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",
|
||||
"Text",
|
||||
"Text",
|
||||
"TextArray",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "14dfaee23af2d3206acf9141b9bdcafb08aaf496f334e4b9292f88740f872855"
|
||||
}
|
43
.sqlx/query-23ce30adcad72a7beba4db6a1a9a5947b433aa28d1ac8a1e9fa328aa751fe4a2.json
generated
Normal file
43
.sqlx/query-23ce30adcad72a7beba4db6a1a9a5947b433aa28d1ac8a1e9fa328aa751fe4a2.json
generated
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"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 ",
|
||||
"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",
|
||||
"Text",
|
||||
"TextArray",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "23ce30adcad72a7beba4db6a1a9a5947b433aa28d1ac8a1e9fa328aa751fe4a2"
|
||||
}
|
14
.sqlx/query-425b0b5ffbe3f1b80aedf15b8df1640c879d8d45883eee8b1e2fbd64eaf283d6.json
generated
Normal file
14
.sqlx/query-425b0b5ffbe3f1b80aedf15b8df1640c879d8d45883eee8b1e2fbd64eaf283d6.json
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM af_template_category\n WHERE category_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "425b0b5ffbe3f1b80aedf15b8df1640c879d8d45883eee8b1e2fbd64eaf283d6"
|
||||
}
|
58
.sqlx/query-4f8bb1345f524f5a11ae74357265d8e308eebb562117bb3f36c3b25f9e1c5e1b.json
generated
Normal file
58
.sqlx/query-4f8bb1345f524f5a11ae74357265d8e308eebb562117bb3f36c3b25f9e1c5e1b.json
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n category_id AS id,\n name,\n description,\n icon,\n bg_color,\n category_type AS \"category_type: AFTemplateCategoryTypeColumn\",\n rank\n FROM af_template_category\n WHERE category_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "icon",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "bg_color",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "category_type: AFTemplateCategoryTypeColumn",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "rank",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4f8bb1345f524f5a11ae74357265d8e308eebb562117bb3f36c3b25f9e1c5e1b"
|
||||
}
|
14
.sqlx/query-5d51aef40f7e0716338b406263240dbc5e4a64cec6f1be10a3676e4f86ce4557.json
generated
Normal file
14
.sqlx/query-5d51aef40f7e0716338b406263240dbc5e4a64cec6f1be10a3676e4f86ce4557.json
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM af_template_creator_account_link\n WHERE creator_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5d51aef40f7e0716338b406263240dbc5e4a64cec6f1be10a3676e4f86ce4557"
|
||||
}
|
40
.sqlx/query-74a5cf06694c10d96b19f2d448830ebe95e0713870ad66e16416fa914857aa7d.json
generated
Normal file
40
.sqlx/query-74a5cf06694c10d96b19f2d448830ebe95e0713870ad66e16416fa914857aa7d.json
generated
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"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": "74a5cf06694c10d96b19f2d448830ebe95e0713870ad66e16416fa914857aa7d"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n avr.reaction_type,\n ARRAY_AGG((au.uuid, au.name, au.metadata ->> 'icon_url')) AS \"react_users!: Vec<AFWebUserType>\",\n avr.comment_id\n FROM af_published_view_reaction avr\n INNER JOIN af_user au ON avr.created_by = au.uid\n WHERE comment_id = $1\n GROUP BY comment_id, reaction_type\n ORDER BY MIN(avr.created_at)\n ",
|
||||
"query": "\n SELECT\n avr.reaction_type,\n ARRAY_AGG((au.uuid, au.name, au.metadata ->> 'icon_url')) AS \"react_users!: Vec<AFWebUserColumn>\",\n avr.comment_id\n FROM af_published_view_reaction avr\n INNER JOIN af_user au ON avr.created_by = au.uid\n WHERE comment_id = $1\n GROUP BY comment_id, reaction_type\n ORDER BY MIN(avr.created_at)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "react_users!: Vec<AFWebUserType>",
|
||||
"name": "react_users!: Vec<AFWebUserColumn>",
|
||||
"type_info": "RecordArray"
|
||||
},
|
||||
{
|
||||
|
@ -30,5 +30,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "e4fac6abfa1b722fc1ce6832d9c262a26bcaa97d03dfd48c8bcc6f40fddd3e35"
|
||||
"hash": "b58432fffcf04a9485a7db5908c1801b34f51e51f3b06f679dc62e068e1cc721"
|
||||
}
|
63
.sqlx/query-b9d4e564e23df7ab391c78848b6a9d51e30496c58e70b26f14dfe55b6a0d2e69.json
generated
Normal file
63
.sqlx/query-b9d4e564e23df7ab391c78848b6a9d51e30496c58e70b26f14dfe55b6a0d2e69.json
generated
Normal file
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO af_template_category (name, description, icon, bg_color, category_type, rank)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING\n category_id AS id,\n name,\n description,\n icon,\n bg_color,\n category_type AS \"category_type: AFTemplateCategoryTypeColumn\",\n rank\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "icon",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "bg_color",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "category_type: AFTemplateCategoryTypeColumn",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "rank",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b9d4e564e23df7ab391c78848b6a9d51e30496c58e70b26f14dfe55b6a0d2e69"
|
||||
}
|
14
.sqlx/query-c335b73ad499b67100e4ce3131a526ddf1745488597c3392ae05e4b398a8715e.json
generated
Normal file
14
.sqlx/query-c335b73ad499b67100e4ce3131a526ddf1745488597c3392ae05e4b398a8715e.json
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM af_template_creator\n WHERE creator_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c335b73ad499b67100e4ce3131a526ddf1745488597c3392ae05e4b398a8715e"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n avc.comment_id,\n avc.created_at,\n avc.updated_at AS last_updated_at,\n avc.content,\n avc.reply_comment_id,\n avc.is_deleted,\n (au.uuid, au.name, au.metadata ->> 'icon_url') AS \"user: AFWebUserType\",\n (NOT avc.is_deleted AND ($2 OR au.uuid = $3)) AS \"can_be_deleted!\"\n FROM af_published_view_comment avc\n LEFT OUTER JOIN af_user au ON avc.created_by = au.uid\n WHERE view_id = $1\n ORDER BY avc.created_at DESC\n ",
|
||||
"query": "\n SELECT\n avc.comment_id,\n avc.created_at,\n avc.updated_at AS last_updated_at,\n avc.content,\n avc.reply_comment_id,\n avc.is_deleted,\n (au.uuid, au.name, au.metadata ->> 'icon_url') AS \"user: AFWebUserColumn\",\n (NOT avc.is_deleted AND ($2 OR au.uuid = $3)) AS \"can_be_deleted!\"\n FROM af_published_view_comment avc\n LEFT OUTER JOIN af_user au ON avc.created_by = au.uid\n WHERE view_id = $1\n ORDER BY avc.created_at DESC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -35,7 +35,7 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "user: AFWebUserType",
|
||||
"name": "user: AFWebUserColumn",
|
||||
"type_info": "Record"
|
||||
},
|
||||
{
|
||||
|
@ -62,5 +62,5 @@
|
|||
null
|
||||
]
|
||||
},
|
||||
"hash": "ab70b4ed70ff91c2a8d335a52da92c934fa1c0528b8b5d80e8e36a24539cda26"
|
||||
"hash": "c5c72869f44067d90c3224a17ec0e32b10cdf9378947e2c7a8409e48423377eb"
|
||||
}
|
64
.sqlx/query-e011a91040db0ffdaf46c859e58b14ad6503b2f4eab33d3b32d677c9731aae79.json
generated
Normal file
64
.sqlx/query-e011a91040db0ffdaf46c859e58b14ad6503b2f4eab33d3b32d677c9731aae79.json
generated
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE af_template_category\n SET\n name = $2,\n description = $3,\n icon = $4,\n bg_color = $5,\n category_type = $6,\n rank = $7\n WHERE category_id = $1\n RETURNING\n category_id AS id,\n name,\n description,\n icon,\n bg_color,\n category_type AS \"category_type: AFTemplateCategoryTypeColumn\",\n rank\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "icon",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "bg_color",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "category_type: AFTemplateCategoryTypeColumn",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "rank",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e011a91040db0ffdaf46c859e58b14ad6503b2f4eab33d3b32d677c9731aae79"
|
||||
}
|
40
.sqlx/query-e6be710a038f8fbcc979c9bd52eb42c65941003857e7004ca809d658d3005a4f.json
generated
Normal file
40
.sqlx/query-e6be710a038f8fbcc979c9bd52eb42c65941003857e7004ca809d658d3005a4f.json
generated
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"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 ",
|
||||
"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": "e6be710a038f8fbcc979c9bd52eb42c65941003857e7004ca809d658d3005a4f"
|
||||
}
|
232
libs/client-api/src/http_template.rs
Normal file
232
libs/client-api/src/http_template.rs
Normal file
|
@ -0,0 +1,232 @@
|
|||
use client_api_entity::{
|
||||
AccountLink, CreateTemplateCategoryParams, CreateTemplateCreatorParams,
|
||||
GetTemplateCategoriesQueryParams, GetTemplateCreatorsQueryParams, TemplateCategories,
|
||||
TemplateCategory, TemplateCategoryType, TemplateCreator, TemplateCreators,
|
||||
UpdateTemplateCategoryParams, UpdateTemplateCreatorParams,
|
||||
};
|
||||
use reqwest::Method;
|
||||
use shared_entity::response::{AppResponse, AppResponseError};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::Client;
|
||||
|
||||
fn template_api_prefix(base_url: &str) -> String {
|
||||
format!("{}/api/template-center", base_url)
|
||||
}
|
||||
|
||||
fn category_resources_url(base_url: &str) -> String {
|
||||
format!("{}/category", template_api_prefix(base_url))
|
||||
}
|
||||
|
||||
fn category_resource_url(base_url: &str, category_id: &Uuid) -> String {
|
||||
format!("{}/{}", category_resources_url(base_url), category_id)
|
||||
}
|
||||
|
||||
fn template_creator_resources_url(base_url: &str) -> String {
|
||||
format!("{}/creator", template_api_prefix(base_url))
|
||||
}
|
||||
|
||||
fn template_creator_resource_url(base_url: &str, creator_id: &Uuid) -> String {
|
||||
format!(
|
||||
"{}/{}",
|
||||
template_creator_resources_url(base_url),
|
||||
creator_id
|
||||
)
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub async fn create_template_category(
|
||||
&self,
|
||||
name: &str,
|
||||
icon: &str,
|
||||
bg_color: &str,
|
||||
description: &str,
|
||||
category_type: TemplateCategoryType,
|
||||
rank: i32,
|
||||
) -> Result<TemplateCategory, AppResponseError> {
|
||||
let url = category_resources_url(&self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::POST, &url)
|
||||
.await?
|
||||
.json(&CreateTemplateCategoryParams {
|
||||
name: name.to_string(),
|
||||
icon: icon.to_string(),
|
||||
bg_color: bg_color.to_string(),
|
||||
description: description.to_string(),
|
||||
rank,
|
||||
category_type,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<TemplateCategory>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn get_template_categories(
|
||||
&self,
|
||||
name_contains: Option<&str>,
|
||||
category_type: Option<TemplateCategoryType>,
|
||||
) -> Result<TemplateCategories, AppResponseError> {
|
||||
let url = category_resources_url(&self.base_url);
|
||||
let resp = self
|
||||
.http_client_without_auth(Method::GET, &url)
|
||||
.await?
|
||||
.query(&GetTemplateCategoriesQueryParams {
|
||||
name_contains: name_contains.map(|s| s.to_string()),
|
||||
category_type,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<TemplateCategories>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn get_template_category(
|
||||
&self,
|
||||
category_id: &Uuid,
|
||||
) -> Result<TemplateCategory, AppResponseError> {
|
||||
let url = category_resource_url(&self.base_url, category_id);
|
||||
let resp = self
|
||||
.http_client_without_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<TemplateCategory>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn delete_template_category(&self, category_id: &Uuid) -> Result<(), AppResponseError> {
|
||||
let url = category_resource_url(&self.base_url, category_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::DELETE, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn update_template_category(
|
||||
&self,
|
||||
category_id: &Uuid,
|
||||
name: &str,
|
||||
icon: &str,
|
||||
bg_color: &str,
|
||||
description: &str,
|
||||
category_type: TemplateCategoryType,
|
||||
rank: i32,
|
||||
) -> Result<TemplateCategory, AppResponseError> {
|
||||
let url = category_resource_url(&self.base_url, category_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::PUT, &url)
|
||||
.await?
|
||||
.json(&UpdateTemplateCategoryParams {
|
||||
name: name.to_string(),
|
||||
icon: icon.to_string(),
|
||||
bg_color: bg_color.to_string(),
|
||||
description: description.to_string(),
|
||||
category_type,
|
||||
rank,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<TemplateCategory>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn create_template_creator(
|
||||
&self,
|
||||
name: &str,
|
||||
avatar_url: &str,
|
||||
account_links: Vec<AccountLink>,
|
||||
) -> Result<TemplateCreator, AppResponseError> {
|
||||
let url = template_creator_resources_url(&self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::POST, &url)
|
||||
.await?
|
||||
.json(&CreateTemplateCreatorParams {
|
||||
name: name.to_string(),
|
||||
avatar_url: avatar_url.to_string(),
|
||||
account_links,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<TemplateCreator>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn get_template_creators(
|
||||
&self,
|
||||
name_contains: Option<&str>,
|
||||
) -> Result<TemplateCreators, AppResponseError> {
|
||||
let url = template_creator_resources_url(&self.base_url);
|
||||
let resp = self
|
||||
.http_client_without_auth(Method::GET, &url)
|
||||
.await?
|
||||
.query(&GetTemplateCreatorsQueryParams {
|
||||
name_contains: name_contains.map(|s| s.to_string()),
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<TemplateCreators>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn get_template_creator(
|
||||
&self,
|
||||
creator_id: &Uuid,
|
||||
) -> Result<TemplateCreator, AppResponseError> {
|
||||
let url = template_creator_resource_url(&self.base_url, creator_id);
|
||||
let resp = self
|
||||
.http_client_without_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<TemplateCreator>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn delete_template_creator(&self, creator_id: &Uuid) -> Result<(), AppResponseError> {
|
||||
let url = template_creator_resource_url(&self.base_url, creator_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::DELETE, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
pub async fn update_template_creator(
|
||||
&self,
|
||||
creator_id: &Uuid,
|
||||
name: &str,
|
||||
avatar_url: &str,
|
||||
account_links: Vec<AccountLink>,
|
||||
) -> Result<TemplateCreator, AppResponseError> {
|
||||
let url = template_creator_resource_url(&self.base_url, creator_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::PUT, &url)
|
||||
.await?
|
||||
.json(&UpdateTemplateCreatorParams {
|
||||
name: name.to_string(),
|
||||
avatar_url: avatar_url.to_string(),
|
||||
account_links,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
AppResponse::<TemplateCreator>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ mod http_collab;
|
|||
mod http_history;
|
||||
mod http_member;
|
||||
mod http_publish;
|
||||
mod http_template;
|
||||
pub use http::*;
|
||||
|
||||
#[cfg(feature = "collab-sync")]
|
||||
|
|
|
@ -997,6 +997,93 @@ pub enum IndexingStatus {
|
|||
Indexed,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateCategories {
|
||||
pub categories: Vec<TemplateCategory>,
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Copy, Clone)]
|
||||
#[repr(i32)]
|
||||
pub enum TemplateCategoryType {
|
||||
UseCase = 0,
|
||||
Feature = 1,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateCategory {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub icon: String,
|
||||
pub bg_color: String,
|
||||
pub description: String,
|
||||
pub category_type: TemplateCategoryType,
|
||||
pub rank: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateTemplateCategoryParams {
|
||||
pub name: String,
|
||||
pub icon: String,
|
||||
pub bg_color: String,
|
||||
pub description: String,
|
||||
pub category_type: TemplateCategoryType,
|
||||
pub rank: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetTemplateCategoriesQueryParams {
|
||||
pub name_contains: Option<String>,
|
||||
pub category_type: Option<TemplateCategoryType>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateTemplateCategoryParams {
|
||||
pub name: String,
|
||||
pub icon: String,
|
||||
pub bg_color: String,
|
||||
pub description: String,
|
||||
pub category_type: TemplateCategoryType,
|
||||
pub rank: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateCreators {
|
||||
pub creators: Vec<TemplateCreator>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AccountLink {
|
||||
pub link_type: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TemplateCreator {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub avatar_url: String,
|
||||
pub account_links: Vec<AccountLink>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateTemplateCreatorParams {
|
||||
pub name: String,
|
||||
pub avatar_url: String,
|
||||
pub account_links: Vec<AccountLink>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateTemplateCreatorParams {
|
||||
pub name: String,
|
||||
pub avatar_url: String,
|
||||
pub account_links: Vec<AccountLink>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetTemplateCreatorsQueryParams {
|
||||
pub name_contains: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::dto::{CollabParams, CollabParamsV0};
|
||||
|
|
|
@ -6,5 +6,6 @@ pub mod index;
|
|||
pub mod listener;
|
||||
pub mod pg_row;
|
||||
pub mod resource_usage;
|
||||
pub mod template;
|
||||
pub mod user;
|
||||
pub mod workspace;
|
||||
|
|
|
@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
|
|||
|
||||
use database_entity::dto::{
|
||||
AFAccessLevel, AFRole, AFUserProfile, AFWebUser, AFWorkspace, AFWorkspaceInvitationStatus,
|
||||
GlobalComment, Reaction,
|
||||
AccountLink, GlobalComment, Reaction, TemplateCategory, TemplateCategoryType, TemplateCreator,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
@ -218,14 +218,14 @@ pub struct AFChatMessageRow {
|
|||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Debug)]
|
||||
pub struct AFWebUserType {
|
||||
pub struct AFWebUserColumn {
|
||||
uuid: Uuid,
|
||||
name: String,
|
||||
avatar_url: Option<String>,
|
||||
}
|
||||
|
||||
impl From<AFWebUserType> for AFWebUser {
|
||||
fn from(val: AFWebUserType) -> Self {
|
||||
impl From<AFWebUserColumn> for AFWebUser {
|
||||
fn from(val: AFWebUserColumn) -> Self {
|
||||
AFWebUser {
|
||||
uuid: val.uuid,
|
||||
name: val.name,
|
||||
|
@ -235,7 +235,7 @@ impl From<AFWebUserType> for AFWebUser {
|
|||
}
|
||||
|
||||
pub struct AFGlobalCommentRow {
|
||||
pub user: Option<AFWebUserType>,
|
||||
pub user: Option<AFWebUserColumn>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub last_updated_at: DateTime<Utc>,
|
||||
pub content: String,
|
||||
|
@ -262,7 +262,7 @@ impl From<AFGlobalCommentRow> for GlobalComment {
|
|||
|
||||
pub struct AFReactionRow {
|
||||
pub reaction_type: String,
|
||||
pub react_users: Vec<AFWebUserType>,
|
||||
pub react_users: Vec<AFWebUserColumn>,
|
||||
pub comment_id: Uuid,
|
||||
}
|
||||
|
||||
|
@ -275,3 +275,93 @@ impl From<AFReactionRow> for Reaction {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct AFTemplateCategoryRow {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub icon: String,
|
||||
pub bg_color: String,
|
||||
pub description: String,
|
||||
pub category_type: AFTemplateCategoryTypeColumn,
|
||||
pub rank: i32,
|
||||
}
|
||||
|
||||
impl From<AFTemplateCategoryRow> for TemplateCategory {
|
||||
fn from(value: AFTemplateCategoryRow) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
icon: value.icon,
|
||||
bg_color: value.bg_color,
|
||||
description: value.description,
|
||||
category_type: value.category_type.into(),
|
||||
rank: value.rank,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum AFTemplateCategoryTypeColumn {
|
||||
UseCase = 0,
|
||||
Feature = 1,
|
||||
}
|
||||
|
||||
impl From<AFTemplateCategoryTypeColumn> for TemplateCategoryType {
|
||||
fn from(value: AFTemplateCategoryTypeColumn) -> Self {
|
||||
match value {
|
||||
AFTemplateCategoryTypeColumn::UseCase => TemplateCategoryType::UseCase,
|
||||
AFTemplateCategoryTypeColumn::Feature => TemplateCategoryType::Feature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TemplateCategoryType> for AFTemplateCategoryTypeColumn {
|
||||
fn from(val: TemplateCategoryType) -> Self {
|
||||
match val {
|
||||
TemplateCategoryType::UseCase => AFTemplateCategoryTypeColumn::UseCase,
|
||||
TemplateCategoryType::Feature => AFTemplateCategoryTypeColumn::Feature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Debug)]
|
||||
pub struct AccountLinkColumn {
|
||||
pub link_type: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl From<AccountLinkColumn> for AccountLink {
|
||||
fn from(value: AccountLinkColumn) -> Self {
|
||||
Self {
|
||||
link_type: value.link_type,
|
||||
url: value.url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AFTemplateCreatorRow {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub avatar_url: String,
|
||||
pub account_links: Option<Vec<AccountLinkColumn>>,
|
||||
}
|
||||
|
||||
impl From<AFTemplateCreatorRow> for TemplateCreator {
|
||||
fn from(value: AFTemplateCreatorRow) -> Self {
|
||||
let account_links = value
|
||||
.account_links
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|v| v.into())
|
||||
.collect();
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
avatar_url: value.avatar_url,
|
||||
account_links,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
372
libs/database/src/template.rs
Normal file
372
libs/database/src/template.rs
Normal file
|
@ -0,0 +1,372 @@
|
|||
use app_error::AppError;
|
||||
use database_entity::dto::{AccountLink, TemplateCategory, TemplateCategoryType, TemplateCreator};
|
||||
use sqlx::{Executor, Postgres, QueryBuilder};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::pg_row::{
|
||||
AFTemplateCategoryRow, AFTemplateCategoryTypeColumn, AFTemplateCreatorRow, AccountLinkColumn,
|
||||
};
|
||||
|
||||
pub async fn insert_new_template_category<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
name: &str,
|
||||
description: &str,
|
||||
icon: &str,
|
||||
bg_color: &str,
|
||||
category_type: TemplateCategoryType,
|
||||
rank: i32,
|
||||
) -> Result<TemplateCategory, AppError> {
|
||||
let category_type_column: AFTemplateCategoryTypeColumn = category_type.into();
|
||||
let new_template_category = sqlx::query_as!(
|
||||
TemplateCategory,
|
||||
r#"
|
||||
INSERT INTO af_template_category (name, description, icon, bg_color, category_type, rank)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING
|
||||
category_id AS id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
bg_color,
|
||||
category_type AS "category_type: AFTemplateCategoryTypeColumn",
|
||||
rank
|
||||
"#,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
bg_color,
|
||||
category_type_column as AFTemplateCategoryTypeColumn,
|
||||
rank,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
Ok(new_template_category)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn update_template_category_by_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
id: &Uuid,
|
||||
name: &str,
|
||||
description: &str,
|
||||
icon: &str,
|
||||
bg_color: &str,
|
||||
category_type: TemplateCategoryType,
|
||||
rank: i32,
|
||||
) -> Result<TemplateCategory, AppError> {
|
||||
let category_type_column: AFTemplateCategoryTypeColumn = category_type.into();
|
||||
let new_template_category = sqlx::query_as!(
|
||||
TemplateCategory,
|
||||
r#"
|
||||
UPDATE af_template_category
|
||||
SET
|
||||
name = $2,
|
||||
description = $3,
|
||||
icon = $4,
|
||||
bg_color = $5,
|
||||
category_type = $6,
|
||||
rank = $7
|
||||
WHERE category_id = $1
|
||||
RETURNING
|
||||
category_id AS id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
bg_color,
|
||||
category_type AS "category_type: AFTemplateCategoryTypeColumn",
|
||||
rank
|
||||
"#,
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
bg_color,
|
||||
category_type_column as AFTemplateCategoryTypeColumn,
|
||||
rank,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
Ok(new_template_category)
|
||||
}
|
||||
|
||||
pub async fn select_template_categories<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
name_contains: Option<&str>,
|
||||
category_type: Option<TemplateCategoryType>,
|
||||
) -> Result<Vec<TemplateCategory>, AppError> {
|
||||
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
|
||||
r#"
|
||||
SELECT
|
||||
category_id AS id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
bg_color,
|
||||
category_type,
|
||||
rank
|
||||
FROM af_template_category
|
||||
WHERE TRUE
|
||||
"#,
|
||||
);
|
||||
if let Some(category_type) = category_type {
|
||||
let category_type_column: AFTemplateCategoryTypeColumn = category_type.into();
|
||||
query_builder.push(" AND category_type = ");
|
||||
query_builder.push_bind(category_type_column);
|
||||
};
|
||||
if let Some(name_contains) = name_contains {
|
||||
query_builder.push(" AND name ILIKE CONCAT('%', ");
|
||||
query_builder.push_bind(name_contains);
|
||||
query_builder.push(" , '%')");
|
||||
};
|
||||
query_builder.push(" ORDER BY rank ASC");
|
||||
let query = query_builder.build_query_as::<AFTemplateCategoryRow>();
|
||||
|
||||
let category_rows: Vec<AFTemplateCategoryRow> = query.fetch_all(executor).await?;
|
||||
let categories = category_rows.into_iter().map(|row| row.into()).collect();
|
||||
|
||||
Ok(categories)
|
||||
}
|
||||
|
||||
pub async fn select_template_category_by_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
category_id: &Uuid,
|
||||
) -> Result<TemplateCategory, AppError> {
|
||||
let category = sqlx::query_as!(
|
||||
TemplateCategory,
|
||||
r#"
|
||||
SELECT
|
||||
category_id AS id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
bg_color,
|
||||
category_type AS "category_type: AFTemplateCategoryTypeColumn",
|
||||
rank
|
||||
FROM af_template_category
|
||||
WHERE category_id = $1
|
||||
"#,
|
||||
category_id,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
Ok(category)
|
||||
}
|
||||
|
||||
pub async fn delete_template_category_by_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
category_id: &Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
let rows_affected = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM af_template_category
|
||||
WHERE category_id = $1
|
||||
"#,
|
||||
category_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?
|
||||
.rows_affected();
|
||||
if rows_affected == 0 {
|
||||
tracing::error!(
|
||||
"No template category with id {} was found to delete",
|
||||
category_id
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_template_creator<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
name: &str,
|
||||
avatar_url: &str,
|
||||
account_links: &[AccountLink],
|
||||
) -> Result<TemplateCreator, AppError> {
|
||||
let link_types: Vec<String> = account_links
|
||||
.iter()
|
||||
.map(|link| link.link_type.clone())
|
||||
.collect();
|
||||
let url: Vec<String> = account_links.iter().map(|link| link.url.clone()).collect();
|
||||
let new_template_creator_row = sqlx::query_as!(
|
||||
AFTemplateCreatorRow,
|
||||
r#"
|
||||
WITH
|
||||
new_creator AS (
|
||||
INSERT INTO af_template_creator (name, avatar_url)
|
||||
VALUES ($1, $2)
|
||||
RETURNING creator_id, name, avatar_url
|
||||
),
|
||||
account_links AS (
|
||||
INSERT INTO af_template_creator_account_link (creator_id, link_type, url)
|
||||
SELECT new_creator.creator_id as creator_id, link_type, url FROM
|
||||
UNNEST($3::text[], $4::text[]) AS t(link_type, url)
|
||||
CROSS JOIN new_creator
|
||||
RETURNING
|
||||
creator_id,
|
||||
link_type,
|
||||
url
|
||||
)
|
||||
SELECT
|
||||
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>"
|
||||
FROM new_creator
|
||||
LEFT OUTER JOIN account_links
|
||||
ON new_creator.creator_id = account_links.creator_id
|
||||
GROUP BY (id, name, avatar_url)
|
||||
"#,
|
||||
name,
|
||||
avatar_url,
|
||||
link_types.as_slice(),
|
||||
url.as_slice(),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
let new_template_creator = new_template_creator_row.into();
|
||||
Ok(new_template_creator)
|
||||
}
|
||||
|
||||
pub async fn update_template_creator_by_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
creator_id: &Uuid,
|
||||
name: &str,
|
||||
avatar_url: &str,
|
||||
account_links: &[AccountLink],
|
||||
) -> Result<TemplateCreator, AppError> {
|
||||
let link_types: Vec<String> = account_links
|
||||
.iter()
|
||||
.map(|link| link.link_type.clone())
|
||||
.collect();
|
||||
let url: Vec<String> = account_links.iter().map(|link| link.url.clone()).collect();
|
||||
let updated_template_creator_row = sqlx::query_as!(
|
||||
AFTemplateCreatorRow,
|
||||
r#"
|
||||
WITH
|
||||
updated_creator AS (
|
||||
UPDATE af_template_creator
|
||||
SET name = $2, avatar_url = $3
|
||||
WHERE creator_id = $1
|
||||
RETURNING creator_id, name, avatar_url
|
||||
),
|
||||
account_links AS (
|
||||
INSERT INTO af_template_creator_account_link (creator_id, link_type, url)
|
||||
SELECT updated_creator.creator_id as creator_id, link_type, url FROM
|
||||
UNNEST($4::text[], $5::text[]) AS t(link_type, url)
|
||||
CROSS JOIN updated_creator
|
||||
RETURNING
|
||||
creator_id,
|
||||
link_type,
|
||||
url
|
||||
)
|
||||
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>"
|
||||
FROM updated_creator
|
||||
LEFT OUTER JOIN account_links
|
||||
ON updated_creator.creator_id = account_links.creator_id
|
||||
GROUP BY (id, name, avatar_url)
|
||||
"#,
|
||||
creator_id,
|
||||
name,
|
||||
avatar_url,
|
||||
link_types.as_slice(),
|
||||
url.as_slice(),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
let updated_template_creator = updated_template_creator_row.into();
|
||||
Ok(updated_template_creator)
|
||||
}
|
||||
|
||||
pub async fn delete_template_creator_account_links<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
creator_id: &Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM af_template_creator_account_link
|
||||
WHERE creator_id = $1
|
||||
"#,
|
||||
creator_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn select_template_creators_by_name<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
substr_match: &str,
|
||||
) -> Result<Vec<TemplateCreator>, AppError> {
|
||||
let creator_rows = sqlx::query_as!(
|
||||
AFTemplateCreatorRow,
|
||||
r#"
|
||||
SELECT
|
||||
tc.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)
|
||||
"#,
|
||||
substr_match
|
||||
)
|
||||
.fetch_all(executor)
|
||||
.await?;
|
||||
let creators = creator_rows.into_iter().map(|row| row.into()).collect();
|
||||
Ok(creators)
|
||||
}
|
||||
|
||||
pub async fn select_template_creator_by_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
creator_id: &Uuid,
|
||||
) -> Result<TemplateCreator, AppError> {
|
||||
let creator_row = sqlx::query_as!(
|
||||
AFTemplateCreatorRow,
|
||||
r#"
|
||||
SELECT
|
||||
tc.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)
|
||||
"#,
|
||||
creator_id
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
let creator = creator_row.into();
|
||||
Ok(creator)
|
||||
}
|
||||
|
||||
pub async fn delete_template_creator_by_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
creator_id: &Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
let rows_affected = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM af_template_creator
|
||||
WHERE creator_id = $1
|
||||
"#,
|
||||
creator_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?
|
||||
.rows_affected();
|
||||
if rows_affected == 0 {
|
||||
tracing::error!(
|
||||
"No template creator with id {} was found to delete",
|
||||
creator_id
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -9,7 +9,7 @@ use tracing::{event, instrument};
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::pg_row::{
|
||||
AFGlobalCommentRow, AFPermissionRow, AFReactionRow, AFUserProfileRow, AFWebUserType,
|
||||
AFGlobalCommentRow, AFPermissionRow, AFReactionRow, AFUserProfileRow, AFWebUserColumn,
|
||||
AFWorkspaceInvitationMinimal, AFWorkspaceMemberPermRow, AFWorkspaceMemberRow, AFWorkspaceRow,
|
||||
};
|
||||
use crate::user::select_uid_from_email;
|
||||
|
@ -1158,7 +1158,7 @@ pub async fn select_comments_for_published_view_ordered_by_recency<
|
|||
avc.content,
|
||||
avc.reply_comment_id,
|
||||
avc.is_deleted,
|
||||
(au.uuid, au.name, au.metadata ->> 'icon_url') AS "user: AFWebUserType",
|
||||
(au.uuid, au.name, au.metadata ->> 'icon_url') AS "user: AFWebUserColumn",
|
||||
(NOT avc.is_deleted AND ($2 OR au.uuid = $3)) AS "can_be_deleted!"
|
||||
FROM af_published_view_comment avc
|
||||
LEFT OUTER JOIN af_user au ON avc.created_by = au.uid
|
||||
|
@ -1244,7 +1244,7 @@ pub async fn select_reactions_for_published_view_ordered_by_reaction_type_creati
|
|||
SELECT
|
||||
avr.comment_id,
|
||||
avr.reaction_type,
|
||||
ARRAY_AGG((au.uuid, au.name, au.metadata ->> 'icon_url')) AS "react_users!: Vec<AFWebUserType>"
|
||||
ARRAY_AGG((au.uuid, au.name, au.metadata ->> 'icon_url')) AS "react_users!: Vec<AFWebUserColumn>"
|
||||
FROM af_published_view_reaction avr
|
||||
INNER JOIN af_user au ON avr.created_by = au.uid
|
||||
WHERE view_id = $1
|
||||
|
@ -1272,7 +1272,7 @@ pub async fn select_reactions_for_comment_ordered_by_reaction_type_creation_time
|
|||
r#"
|
||||
SELECT
|
||||
avr.reaction_type,
|
||||
ARRAY_AGG((au.uuid, au.name, au.metadata ->> 'icon_url')) AS "react_users!: Vec<AFWebUserType>",
|
||||
ARRAY_AGG((au.uuid, au.name, au.metadata ->> 'icon_url')) AS "react_users!: Vec<AFWebUserColumn>",
|
||||
avr.comment_id
|
||||
FROM af_published_view_reaction avr
|
||||
INNER JOIN af_user au ON avr.created_by = au.uid
|
||||
|
|
13
migrations/20240806054557_template_category.sql
Normal file
13
migrations/20240806054557_template_category.sql
Normal file
|
@ -0,0 +1,13 @@
|
|||
CREATE TABLE IF NOT EXISTS af_template_category (
|
||||
category_id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
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,
|
||||
icon TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
bg_color TEXT NOT NULL,
|
||||
category_type INT NOT NULL,
|
||||
rank INT NOT NULL,
|
||||
|
||||
PRIMARY KEY (category_id)
|
||||
);
|
16
migrations/20240806103039_template_creator.sql
Normal file
16
migrations/20240806103039_template_creator.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
CREATE TABLE IF NOT EXISTS af_template_creator (
|
||||
creator_id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
avatar_url TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (creator_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS af_template_creator_account_link (
|
||||
creator_id UUID NOT NULL REFERENCES af_template_creator(creator_id) ON DELETE CASCADE,
|
||||
link_type TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
|
||||
UNIQUE(creator_id, link_type)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_creator_id_on_af_template_creator_account_link ON af_template_creator_account_link(creator_id);
|
|
@ -5,6 +5,7 @@ pub mod file_storage;
|
|||
pub mod history;
|
||||
pub mod metrics;
|
||||
pub mod search;
|
||||
pub mod template;
|
||||
pub mod user;
|
||||
pub mod util;
|
||||
pub mod workspace;
|
||||
|
|
175
src/api/template.rs
Normal file
175
src/api/template.rs
Normal file
|
@ -0,0 +1,175 @@
|
|||
use actix_web::{
|
||||
web::{self, Data, Json},
|
||||
Result, Scope,
|
||||
};
|
||||
use database_entity::dto::{
|
||||
CreateTemplateCategoryParams, CreateTemplateCreatorParams, GetTemplateCategoriesQueryParams,
|
||||
GetTemplateCreatorsQueryParams, TemplateCategories, TemplateCategory, TemplateCreator,
|
||||
TemplateCreators, UpdateTemplateCategoryParams, UpdateTemplateCreatorParams,
|
||||
};
|
||||
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,
|
||||
};
|
||||
|
||||
pub fn template_scope() -> Scope {
|
||||
web::scope("/api/template-center")
|
||||
.service(
|
||||
web::resource("/category")
|
||||
.route(web::post().to(post_template_category_handler))
|
||||
.route(web::get().to(list_template_categories_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/category/{category_id}")
|
||||
.route(web::put().to(update_template_category_handler))
|
||||
.route(web::get().to(get_template_category_handler))
|
||||
.route(web::delete().to(delete_template_category_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/creator")
|
||||
.route(web::post().to(post_template_creator_handler))
|
||||
.route(web::get().to(list_template_creators_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/creator/{creator_id}")
|
||||
.route(web::put().to(update_template_creator_handler))
|
||||
.route(web::get().to(get_template_creator_handler))
|
||||
.route(web::delete().to(delete_template_creator_handler)),
|
||||
)
|
||||
}
|
||||
|
||||
async fn post_template_category_handler(
|
||||
data: Json<CreateTemplateCategoryParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCategory>> {
|
||||
let new_template_category = create_new_template_category(
|
||||
&state.pg_pool,
|
||||
&data.name,
|
||||
&data.description,
|
||||
&data.icon,
|
||||
&data.bg_color,
|
||||
data.category_type,
|
||||
data.rank,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(new_template_category)))
|
||||
}
|
||||
|
||||
async fn list_template_categories_handler(
|
||||
query: web::Query<GetTemplateCategoriesQueryParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCategories>> {
|
||||
let categories = get_template_categories(
|
||||
&state.pg_pool,
|
||||
query.name_contains.as_deref(),
|
||||
query.category_type,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(
|
||||
AppResponse::Ok().with_data(TemplateCategories { categories }),
|
||||
))
|
||||
}
|
||||
|
||||
async fn update_template_category_handler(
|
||||
category_id: web::Path<Uuid>,
|
||||
data: Json<UpdateTemplateCategoryParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCategory>> {
|
||||
let category_id = category_id.into_inner();
|
||||
let updated_template_category = update_template_category(
|
||||
&state.pg_pool,
|
||||
&category_id,
|
||||
&data.name,
|
||||
&data.description,
|
||||
&data.icon,
|
||||
&data.bg_color,
|
||||
data.category_type,
|
||||
data.rank,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(updated_template_category)))
|
||||
}
|
||||
|
||||
async fn get_template_category_handler(
|
||||
category_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCategory>> {
|
||||
let category_id = category_id.into_inner();
|
||||
let category = get_template_category(&state.pg_pool, &category_id).await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(category)))
|
||||
}
|
||||
|
||||
async fn delete_template_category_handler(
|
||||
category_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
let category_id = category_id.into_inner();
|
||||
delete_template_category(&state.pg_pool, &category_id).await?;
|
||||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
||||
async fn post_template_creator_handler(
|
||||
data: Json<CreateTemplateCreatorParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCreator>> {
|
||||
let new_template_creator = create_new_template_creator(
|
||||
&state.pg_pool,
|
||||
&data.name,
|
||||
&data.avatar_url,
|
||||
&data.account_links,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(new_template_creator)))
|
||||
}
|
||||
|
||||
async fn list_template_creators_handler(
|
||||
query: web::Query<GetTemplateCreatorsQueryParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCreators>> {
|
||||
let creators = get_template_creators(&state.pg_pool, &query.name_contains).await?;
|
||||
Ok(Json(
|
||||
AppResponse::Ok().with_data(TemplateCreators { creators }),
|
||||
))
|
||||
}
|
||||
|
||||
async fn update_template_creator_handler(
|
||||
creator_id: web::Path<Uuid>,
|
||||
data: Json<UpdateTemplateCreatorParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCreator>> {
|
||||
let creator_id = creator_id.into_inner();
|
||||
let updated_creator = update_template_creator(
|
||||
&state.pg_pool,
|
||||
&creator_id,
|
||||
&data.name,
|
||||
&data.avatar_url,
|
||||
&data.account_links,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(updated_creator)))
|
||||
}
|
||||
|
||||
async fn get_template_creator_handler(
|
||||
creator_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCreator>> {
|
||||
let creator_id = creator_id.into_inner();
|
||||
let template_creator = get_template_creator(&state.pg_pool, &creator_id).await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(template_creator)))
|
||||
}
|
||||
|
||||
async fn delete_template_creator_handler(
|
||||
creator_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<TemplateCreator>> {
|
||||
let creator_id = creator_id.into_inner();
|
||||
delete_template_creator(&state.pg_pool, &creator_id).await?;
|
||||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
|
@ -1153,7 +1153,7 @@ async fn post_published_collab_reaction_handler(
|
|||
view_id: web::Path<Uuid>,
|
||||
data: Json<CreateReactionParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<Reactions>> {
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
let view_id = view_id.into_inner();
|
||||
create_reaction_on_comment(
|
||||
&state.pg_pool,
|
||||
|
@ -1170,7 +1170,7 @@ async fn delete_published_collab_reaction_handler(
|
|||
user_uuid: UserUuid,
|
||||
data: Json<DeleteReactionParams>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<Reactions>> {
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
remove_reaction_on_comment(
|
||||
&state.pg_pool,
|
||||
&data.comment_id,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::api::metrics::metrics_scope;
|
||||
|
||||
use crate::api::file_storage::file_storage_scope;
|
||||
use crate::api::template::template_scope;
|
||||
use crate::api::user::user_scope;
|
||||
use crate::api::workspace::{collab_scope, workspace_scope};
|
||||
use crate::api::ws::ws_scope;
|
||||
|
@ -163,6 +164,7 @@ pub async fn run_actix_server(
|
|||
.service(history_scope())
|
||||
.service(metrics_scope())
|
||||
.service(search_scope())
|
||||
.service(template_scope())
|
||||
.app_data(Data::new(state.metrics.registry.clone()))
|
||||
.app_data(Data::new(state.metrics.request_metrics.clone()))
|
||||
.app_data(Data::new(state.metrics.realtime_metrics.clone()))
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod chat;
|
|||
pub mod collab;
|
||||
pub mod pg_listener;
|
||||
pub mod search;
|
||||
pub mod template;
|
||||
pub mod user;
|
||||
pub mod utils;
|
||||
pub mod workspace;
|
||||
|
|
1
src/biz/template/mod.rs
Normal file
1
src/biz/template/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod ops;
|
143
src/biz/template/ops.rs
Normal file
143
src/biz/template/ops.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
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_entity::dto::{AccountLink, TemplateCategory, TemplateCategoryType, TemplateCreator};
|
||||
use shared_entity::response::AppResponseError;
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn create_new_template_category(
|
||||
pg_pool: &PgPool,
|
||||
name: &str,
|
||||
description: &str,
|
||||
icon: &str,
|
||||
bg_color: &str,
|
||||
category_type: TemplateCategoryType,
|
||||
rank: i32,
|
||||
) -> Result<TemplateCategory, AppResponseError> {
|
||||
let new_template_category = insert_new_template_category(
|
||||
pg_pool,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
bg_color,
|
||||
category_type,
|
||||
rank,
|
||||
)
|
||||
.await?;
|
||||
Ok(new_template_category)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn update_template_category(
|
||||
pg_pool: &PgPool,
|
||||
category_id: &Uuid,
|
||||
name: &str,
|
||||
description: &str,
|
||||
icon: &str,
|
||||
bg_color: &str,
|
||||
category_type: TemplateCategoryType,
|
||||
rank: i32,
|
||||
) -> Result<TemplateCategory, AppResponseError> {
|
||||
let updated_template_category = update_template_category_by_id(
|
||||
pg_pool,
|
||||
category_id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
bg_color,
|
||||
category_type,
|
||||
rank,
|
||||
)
|
||||
.await?;
|
||||
Ok(updated_template_category)
|
||||
}
|
||||
|
||||
pub async fn get_template_categories(
|
||||
pg_pool: &PgPool,
|
||||
name_contains: Option<&str>,
|
||||
category_type: Option<TemplateCategoryType>,
|
||||
) -> Result<Vec<TemplateCategory>, AppResponseError> {
|
||||
let categories = select_template_categories(pg_pool, name_contains, category_type).await?;
|
||||
Ok(categories)
|
||||
}
|
||||
|
||||
pub async fn get_template_category(
|
||||
pg_pool: &PgPool,
|
||||
category_id: &Uuid,
|
||||
) -> Result<TemplateCategory, AppResponseError> {
|
||||
let category = select_template_category_by_id(pg_pool, category_id).await?;
|
||||
Ok(category)
|
||||
}
|
||||
|
||||
pub async fn delete_template_category(
|
||||
pg_pool: &PgPool,
|
||||
category_id: &Uuid,
|
||||
) -> Result<(), AppResponseError> {
|
||||
delete_template_category_by_id(pg_pool, category_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_new_template_creator(
|
||||
pg_pool: &PgPool,
|
||||
name: &str,
|
||||
avatar_url: &str,
|
||||
account_links: &[AccountLink],
|
||||
) -> Result<TemplateCreator, AppResponseError> {
|
||||
let new_template_creator =
|
||||
insert_template_creator(pg_pool, name, avatar_url, account_links).await?;
|
||||
Ok(new_template_creator)
|
||||
}
|
||||
|
||||
pub async fn update_template_creator(
|
||||
pg_pool: &PgPool,
|
||||
creator_id: &Uuid,
|
||||
name: &str,
|
||||
avatar_url: &str,
|
||||
account_links: &[AccountLink],
|
||||
) -> Result<TemplateCreator, AppResponseError> {
|
||||
let mut txn = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("Begin transaction to update template creator")?;
|
||||
delete_template_creator_account_links(txn.deref_mut(), creator_id).await?;
|
||||
let updated_template_creator =
|
||||
update_template_creator_by_id(txn.deref_mut(), creator_id, name, avatar_url, account_links)
|
||||
.await?;
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("Commit transaction to update template creator")?;
|
||||
Ok(updated_template_creator)
|
||||
}
|
||||
|
||||
pub async fn get_template_creators(
|
||||
pg_pool: &PgPool,
|
||||
keyword: &Option<String>,
|
||||
) -> Result<Vec<TemplateCreator>, AppResponseError> {
|
||||
let substr_match = keyword.as_deref().unwrap_or("%");
|
||||
let creators = select_template_creators_by_name(pg_pool, substr_match).await?;
|
||||
Ok(creators)
|
||||
}
|
||||
|
||||
pub async fn get_template_creator(
|
||||
pg_pool: &PgPool,
|
||||
creator_id: &Uuid,
|
||||
) -> Result<TemplateCreator, AppResponseError> {
|
||||
let creator = select_template_creator_by_id(pg_pool, creator_id).await?;
|
||||
Ok(creator)
|
||||
}
|
||||
|
||||
pub async fn delete_template_creator(
|
||||
pg_pool: &PgPool,
|
||||
creator_id: &Uuid,
|
||||
) -> Result<(), AppResponseError> {
|
||||
delete_template_creator_by_id(pg_pool, creator_id).await?;
|
||||
Ok(())
|
||||
}
|
|
@ -2,6 +2,6 @@ mod edit_workspace;
|
|||
mod invitation_crud;
|
||||
mod member_crud;
|
||||
mod publish;
|
||||
mod template_test;
|
||||
mod template;
|
||||
mod workspace_crud;
|
||||
mod workspace_settings;
|
||||
|
|
233
tests/workspace/template.rs
Normal file
233
tests/workspace/template.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
use app_error::ErrorCode;
|
||||
use client_api::entity::{AccountLink, TemplateCategoryType};
|
||||
use client_api_test::*;
|
||||
use collab::core::collab::DataSource;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab_document::document::Document;
|
||||
use collab_entity::CollabType;
|
||||
use database_entity::dto::{QueryCollab, QueryCollabParams};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_user_default_workspace_test() {
|
||||
let email = generate_unique_email();
|
||||
let password = "Hello!123#";
|
||||
let c = localhost_client();
|
||||
c.sign_up(&email, password).await.unwrap();
|
||||
let mut test_client = TestClient::new_user().await;
|
||||
let workspace_id = test_client.workspace_id().await;
|
||||
let folder = test_client.get_user_folder().await;
|
||||
|
||||
let views = folder.get_views_belong_to(&test_client.workspace_id().await);
|
||||
assert_eq!(views.len(), 1);
|
||||
assert_eq!(views[0].name, "Getting started");
|
||||
|
||||
let document_id = views[0].id.clone();
|
||||
let document =
|
||||
get_document_collab_from_remote(&mut test_client, workspace_id, &document_id).await;
|
||||
let document_data = document.get_document_data().unwrap();
|
||||
assert_eq!(document_data.blocks.len(), 25);
|
||||
}
|
||||
|
||||
async fn get_document_collab_from_remote(
|
||||
test_client: &mut TestClient,
|
||||
workspace_id: String,
|
||||
document_id: &str,
|
||||
) -> Document {
|
||||
let params = QueryCollabParams {
|
||||
workspace_id,
|
||||
inner: QueryCollab {
|
||||
object_id: document_id.to_string(),
|
||||
collab_type: CollabType::Document,
|
||||
},
|
||||
};
|
||||
let resp = test_client.get_collab(params).await.unwrap();
|
||||
Document::from_doc_state(
|
||||
CollabOrigin::Empty,
|
||||
DataSource::DocStateV1(resp.encode_collab.doc_state.to_vec()),
|
||||
document_id,
|
||||
vec![],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_template_category_crud() {
|
||||
let (authorized_client, _) = generate_unique_registered_user_client().await;
|
||||
let category_name = Uuid::new_v4().to_string();
|
||||
let new_template_category = authorized_client
|
||||
.create_template_category(
|
||||
category_name.as_str(),
|
||||
"icon",
|
||||
"bg_color",
|
||||
"description",
|
||||
TemplateCategoryType::Feature,
|
||||
1,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(new_template_category.name, category_name);
|
||||
assert_eq!(new_template_category.icon, "icon");
|
||||
assert_eq!(new_template_category.bg_color, "bg_color");
|
||||
assert_eq!(new_template_category.description, "description");
|
||||
assert_eq!(
|
||||
new_template_category.category_type,
|
||||
TemplateCategoryType::Feature
|
||||
);
|
||||
assert_eq!(new_template_category.rank, 1);
|
||||
let updated_category_name = Uuid::new_v4().to_string();
|
||||
let updated_template_category = authorized_client
|
||||
.update_template_category(
|
||||
&new_template_category.id,
|
||||
updated_category_name.as_str(),
|
||||
"new_icon",
|
||||
"new_bg_color",
|
||||
"new_description",
|
||||
TemplateCategoryType::UseCase,
|
||||
2,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(updated_template_category.name, updated_category_name);
|
||||
assert_eq!(updated_template_category.icon, "new_icon");
|
||||
assert_eq!(updated_template_category.bg_color, "new_bg_color");
|
||||
assert_eq!(updated_template_category.description, "new_description");
|
||||
assert_eq!(
|
||||
updated_template_category.category_type,
|
||||
TemplateCategoryType::UseCase
|
||||
);
|
||||
assert_eq!(updated_template_category.rank, 2);
|
||||
|
||||
let guest_client = localhost_client();
|
||||
let template_category = guest_client
|
||||
.get_template_category(&new_template_category.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(template_category.name, updated_category_name);
|
||||
assert_eq!(template_category.icon, "new_icon");
|
||||
assert_eq!(template_category.bg_color, "new_bg_color");
|
||||
assert_eq!(template_category.description, "new_description");
|
||||
assert_eq!(
|
||||
template_category.category_type,
|
||||
TemplateCategoryType::UseCase
|
||||
);
|
||||
assert_eq!(template_category.rank, 2);
|
||||
|
||||
let second_category_name = Uuid::new_v4().to_string();
|
||||
authorized_client
|
||||
.create_template_category(
|
||||
second_category_name.as_str(),
|
||||
"second_icon",
|
||||
"second_bg_color",
|
||||
"second_description",
|
||||
TemplateCategoryType::Feature,
|
||||
3,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let guest_client = localhost_client();
|
||||
let result = guest_client
|
||||
.create_template_category("", "", "", "", TemplateCategoryType::Feature, 0)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::NotLoggedIn);
|
||||
|
||||
let name_search_substr = &second_category_name[0..second_category_name.len() - 1];
|
||||
let category_by_name_search_result = guest_client
|
||||
.get_template_categories(Some(name_search_substr), None)
|
||||
.await
|
||||
.unwrap()
|
||||
.categories;
|
||||
assert_eq!(category_by_name_search_result.len(), 1);
|
||||
assert_eq!(category_by_name_search_result[0].name, second_category_name);
|
||||
let category_by_type_search_result = guest_client
|
||||
.get_template_categories(None, Some(TemplateCategoryType::Feature))
|
||||
.await
|
||||
.unwrap()
|
||||
.categories;
|
||||
// Since the table might not be in a clean state, we can't guarantee that there is only one category of type Feature
|
||||
assert!(!category_by_type_search_result.is_empty());
|
||||
assert!(category_by_type_search_result
|
||||
.iter()
|
||||
.all(|r| r.category_type == TemplateCategoryType::Feature));
|
||||
assert!(category_by_type_search_result
|
||||
.iter()
|
||||
.any(|r| r.name == second_category_name));
|
||||
let result = guest_client
|
||||
.delete_template_category(&new_template_category.id)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::NotLoggedIn);
|
||||
authorized_client
|
||||
.delete_template_category(&new_template_category.id)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = guest_client
|
||||
.get_template_category(&new_template_category.id)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::RecordNotFound);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_template_creator_crud() {
|
||||
let (authorized_client, _) = generate_unique_registered_user_client().await;
|
||||
let account_links = vec![AccountLink {
|
||||
link_type: "reddit".to_string(),
|
||||
url: "reddit_url".to_string(),
|
||||
}];
|
||||
let new_creator = authorized_client
|
||||
.create_template_creator("name", "avatar_url", account_links)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(new_creator.name, "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");
|
||||
assert_eq!(new_creator.account_links[0].url, "reddit_url");
|
||||
|
||||
let guest_client = localhost_client();
|
||||
let result = guest_client.create_template_creator("", "", vec![]).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::NotLoggedIn);
|
||||
|
||||
let updated_account_links = vec![AccountLink {
|
||||
link_type: "twitter".to_string(),
|
||||
url: "twitter_url".to_string(),
|
||||
}];
|
||||
let updated_creator = authorized_client
|
||||
.update_template_creator(
|
||||
&new_creator.id,
|
||||
"new_name",
|
||||
"new_avatar_url",
|
||||
updated_account_links,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(updated_creator.name, "new_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");
|
||||
assert_eq!(updated_creator.account_links[0].url, "twitter_url");
|
||||
|
||||
let creator = guest_client
|
||||
.get_template_creator(&new_creator.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(creator.name, "new_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 result = guest_client.delete_template_creator(&new_creator.id).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::NotLoggedIn);
|
||||
authorized_client
|
||||
.delete_template_creator(&new_creator.id)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = guest_client.get_template_creator(&new_creator.id).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::RecordNotFound);
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
use client_api_test::*;
|
||||
use collab::core::collab::DataSource;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab_document::document::Document;
|
||||
use collab_entity::CollabType;
|
||||
use database_entity::dto::{QueryCollab, QueryCollabParams};
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_user_default_workspace_test() {
|
||||
let email = generate_unique_email();
|
||||
let password = "Hello!123#";
|
||||
let c = localhost_client();
|
||||
c.sign_up(&email, password).await.unwrap();
|
||||
let mut test_client = TestClient::new_user().await;
|
||||
let workspace_id = test_client.workspace_id().await;
|
||||
let folder = test_client.get_user_folder().await;
|
||||
|
||||
let views = folder.get_views_belong_to(&test_client.workspace_id().await);
|
||||
assert_eq!(views.len(), 1);
|
||||
assert_eq!(views[0].name, "Getting started");
|
||||
|
||||
let document_id = views[0].id.clone();
|
||||
let document =
|
||||
get_document_collab_from_remote(&mut test_client, workspace_id, &document_id).await;
|
||||
let document_data = document.get_document_data().unwrap();
|
||||
assert_eq!(document_data.blocks.len(), 25);
|
||||
}
|
||||
|
||||
async fn get_document_collab_from_remote(
|
||||
test_client: &mut TestClient,
|
||||
workspace_id: String,
|
||||
document_id: &str,
|
||||
) -> Document {
|
||||
let params = QueryCollabParams {
|
||||
workspace_id,
|
||||
inner: QueryCollab {
|
||||
object_id: document_id.to_string(),
|
||||
collab_type: CollabType::Document,
|
||||
},
|
||||
};
|
||||
let resp = test_client.get_collab(params).await.unwrap();
|
||||
Document::from_doc_state(
|
||||
CollabOrigin::Empty,
|
||||
DataSource::DocStateV1(resp.encode_collab.doc_state.to_vec()),
|
||||
document_id,
|
||||
vec![],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
Loading…
Add table
Reference in a new issue