mirror of
https://github.com/Arcadia-Solutions/arcadia.git
synced 2025-12-16 23:14:15 -06:00
feat: add tests for the forum threads and use sqlx::query_as!() more
often
This commit is contained in:
4
backend/api/tests/fixtures/with_test_forum_category.sql
vendored
Normal file
4
backend/api/tests/fixtures/with_test_forum_category.sql
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
INSERT INTO
|
||||
forum_categories (id, name, created_by_id)
|
||||
VALUES
|
||||
(100, 'Test Category', 100);
|
||||
7
backend/api/tests/fixtures/with_test_forum_post.sql
vendored
Normal file
7
backend/api/tests/fixtures/with_test_forum_post.sql
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
INSERT INTO
|
||||
forum_posts (id, forum_thread_id, content, created_at, created_by_id)
|
||||
VALUES
|
||||
(100, 100, 'This is the first post in the test thread', '2025-01-01 10:00:00+00', 100),
|
||||
(101, 101, 'This is the first post in the locked thread', '2025-01-01 11:00:00+00', 100),
|
||||
(102, 102, 'This is the first post in the sticky thread', '2025-01-01 12:00:00+00', 100),
|
||||
(103, 103, 'This is the first post in the thread in different sub category', '2025-01-01 13:00:00+00', 100);
|
||||
5
backend/api/tests/fixtures/with_test_forum_sub_category.sql
vendored
Normal file
5
backend/api/tests/fixtures/with_test_forum_sub_category.sql
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
INSERT INTO
|
||||
forum_sub_categories (id, forum_category_id, name, created_by_id)
|
||||
VALUES
|
||||
(100, 100, 'Test Sub Category', 100),
|
||||
(101, 100, 'Test Sub Category 2', 100);
|
||||
7
backend/api/tests/fixtures/with_test_forum_thread.sql
vendored
Normal file
7
backend/api/tests/fixtures/with_test_forum_thread.sql
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
INSERT INTO
|
||||
forum_threads (id, forum_sub_category_id, name, created_at, created_by_id, posts_amount, sticky, locked)
|
||||
VALUES
|
||||
(100, 100, 'Test Thread', '2025-01-01 10:00:00+00', 100, 1, false, false),
|
||||
(101, 100, 'Locked Thread', '2025-01-01 11:00:00+00', 100, 1, false, true),
|
||||
(102, 100, 'Sticky Thread', '2025-01-01 12:00:00+00', 100, 1, true, false),
|
||||
(103, 101, 'Thread in Different Sub Category', '2025-01-01 13:00:00+00', 100, 1, false, false);
|
||||
@@ -1,4 +1,4 @@
|
||||
INSERT INTO
|
||||
users (username, email, password_hash, registered_from_ip, passkey, class)
|
||||
users (id, username, email, password_hash, registered_from_ip, passkey, class)
|
||||
VALUES
|
||||
('test_user', 'test_email@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3837', 'newbie')
|
||||
(100, 'test_user', 'test_email@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3837', 'newbie')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
INSERT INTO
|
||||
users (username, email, password_hash, registered_from_ip, passkey, class)
|
||||
users (id, username, email, password_hash, registered_from_ip, passkey, class)
|
||||
VALUES
|
||||
('test_user2', 'test_email2@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3838', 'staff')
|
||||
(101, 'test_user2', 'test_email2@testdomain.com', '$argon2id$v=19$m=19456,t=2,p=1$WM6V9pJ2ya7+N+NNIUtolg$n128u9idizCHLwZ9xhKaxOttLaAVZZgvfRZlRAnfyKk', '10.10.4.88', 'd2037c66dd3e13044e0d2f9b891c3838', 'staff')
|
||||
|
||||
1220
backend/api/tests/test_forum_thread.rs
Normal file
1220
backend/api/tests/test_forum_thread.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -152,7 +152,7 @@ async fn test_upload_torrent(pool: PgPool) {
|
||||
.await;
|
||||
|
||||
assert_eq!(torrent.edition_group_id, 1);
|
||||
assert_eq!(torrent.created_by_id, 2);
|
||||
assert_eq!(torrent.created_by_id, 100);
|
||||
}
|
||||
|
||||
#[sqlx::test(
|
||||
|
||||
@@ -231,6 +231,15 @@ pub enum Error {
|
||||
#[error("could not update forum thread")]
|
||||
CouldNotUpdateForumThread(#[source] sqlx::Error),
|
||||
|
||||
#[error("forum thread locked")]
|
||||
ForumThreadLocked,
|
||||
|
||||
#[error("forum thread name cannot be empty")]
|
||||
ForumThreadNameEmpty,
|
||||
|
||||
#[error("forum post empty")]
|
||||
ForumPostEmpty,
|
||||
|
||||
#[error("could not find forum post")]
|
||||
CouldNotFindForumPost(#[source] sqlx::Error),
|
||||
|
||||
@@ -347,7 +356,9 @@ impl actix_web::ResponseError for Error {
|
||||
| Error::InvitationKeyAlreadyUsed
|
||||
| Error::WrongUsernameOrPassword
|
||||
| Error::TorrentFileInvalid
|
||||
| Error::InvalidUserIdOrTorrentId => StatusCode::BAD_REQUEST,
|
||||
| Error::InvalidUserIdOrTorrentId
|
||||
| Error::ForumThreadNameEmpty
|
||||
| Error::ForumPostEmpty => StatusCode::BAD_REQUEST,
|
||||
|
||||
// 401 Unauthorized
|
||||
Error::InvalidOrExpiredRefreshToken | Error::InvalidatedToken => {
|
||||
@@ -355,7 +366,9 @@ impl actix_web::ResponseError for Error {
|
||||
}
|
||||
|
||||
// 403 Forbidden
|
||||
Error::AccountBanned | Error::InsufficientPrivileges => StatusCode::FORBIDDEN,
|
||||
Error::AccountBanned | Error::InsufficientPrivileges | Error::ForumThreadLocked => {
|
||||
StatusCode::FORBIDDEN
|
||||
}
|
||||
|
||||
// 404 Not Found
|
||||
Error::UserNotFound(_)
|
||||
@@ -365,6 +378,8 @@ impl actix_web::ResponseError for Error {
|
||||
| Error::CouldNotFindArtist(_)
|
||||
| Error::TitleGroupTagNotFound
|
||||
| Error::CouldNotFindTitleGroupComment(_)
|
||||
| Error::CouldNotFindForumThread(_)
|
||||
| Error::CouldNotFindForumSubCategory(_)
|
||||
| Error::CssSheetNotFound(_) => StatusCode::NOT_FOUND,
|
||||
|
||||
// 409 Conflict
|
||||
|
||||
64
backend/storage/.sqlx/query-168361f135ba2ea642ce12377d2a08f84b9a4baa3e196f5c0a8e4cf40a7a8dac.json
generated
Normal file
64
backend/storage/.sqlx/query-168361f135ba2ea642ce12377d2a08f84b9a4baa3e196f5c0a8e4cf40a7a8dac.json
generated
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM forum_threads WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "forum_sub_category_id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "created_by_id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "posts_amount",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "sticky",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "locked",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "168361f135ba2ea642ce12377d2a08f84b9a4baa3e196f5c0a8e4cf40a7a8dac"
|
||||
}
|
||||
22
backend/storage/.sqlx/query-54ae7811a6cb02d3b9161fc6e5e5c419f0a7b891033dd0c047be2532c07b0b80.json
generated
Normal file
22
backend/storage/.sqlx/query-54ae7811a6cb02d3b9161fc6e5e5c419f0a7b891033dd0c047be2532c07b0b80.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n json_strip_nulls(\n json_build_object(\n 'id', fsc.id,\n 'name', fsc.name,\n 'threads_amount', fsc.threads_amount,\n 'posts_amount', fsc.posts_amount,\n 'forbidden_classes', fsc.forbidden_classes,\n 'category', json_build_object(\n 'id', fc.id,\n 'name', fc.name\n ),\n 'threads', (\n SELECT\n COALESCE(\n json_agg(\n json_build_object(\n 'id', ft.id,\n 'name', ft.name,\n 'created_at', ft.created_at,\n 'posts_amount', ft.posts_amount,\n 'sticky', ft.sticky,\n 'locked', ft.locked,\n 'created_by', json_build_object(\n 'id', u_thread.id,\n 'username', u_thread.username,\n 'warned', u_thread.warned,\n 'banned', u_thread.banned\n ),\n 'latest_post', json_build_object(\n 'id', fp_latest.id,\n 'thread_id', ft.id,\n 'name', ft.name,\n 'created_at', fp_latest.created_at,\n 'created_by', json_build_object(\n 'id', u_post.id,\n 'username', u_post.username,\n 'warned', u_post.warned,\n 'banned', u_post.banned\n )\n )\n ) ORDER BY ft.created_at DESC\n ),\n '[]'::json\n )\n FROM\n forum_threads ft\n JOIN\n users u_thread ON ft.created_by_id = u_thread.id\n LEFT JOIN LATERAL (\n SELECT\n fp.id,\n fp.created_at,\n fp.created_by_id\n FROM\n forum_posts fp\n WHERE\n fp.forum_thread_id = ft.id\n ORDER BY\n fp.created_at DESC\n LIMIT 1\n ) AS fp_latest ON TRUE\n LEFT JOIN\n users u_post ON fp_latest.created_by_id = u_post.id\n WHERE\n ft.forum_sub_category_id = fsc.id\n )\n )\n ) AS result_json\n FROM\n forum_sub_categories fsc\n JOIN\n forum_categories fc ON fsc.forum_category_id = fc.id\n WHERE\n fsc.id = $1\n GROUP BY\n fsc.id, fc.id;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result_json",
|
||||
"type_info": "Json"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "54ae7811a6cb02d3b9161fc6e5e5c419f0a7b891033dd0c047be2532c07b0b80"
|
||||
}
|
||||
22
backend/storage/.sqlx/query-61907f8f65cb6ef293e8f5ec9bf0248f1e9b81574e5fdc11ca5fb0471b28110c.json
generated
Normal file
22
backend/storage/.sqlx/query-61907f8f65cb6ef293e8f5ec9bf0248f1e9b81574e5fdc11ca5fb0471b28110c.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT locked FROM forum_threads WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "locked",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "61907f8f65cb6ef293e8f5ec9bf0248f1e9b81574e5fdc11ca5fb0471b28110c"
|
||||
}
|
||||
104
backend/storage/.sqlx/query-9df37ddfafd7cee54e187d4b8195d00f67fb73156ba8a7a0cc23ebc062fa6a71.json
generated
Normal file
104
backend/storage/.sqlx/query-9df37ddfafd7cee54e187d4b8195d00f67fb73156ba8a7a0cc23ebc062fa6a71.json
generated
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT fsc.id, fsc.name, fsc.threads_amount, fsc.posts_amount, fsc.forbidden_classes,\n fsc.forum_category_id, fc.name AS category_name,\n fp.id AS \"latest_post_id?\", ft.id AS \"thread_id?\", ft.name AS \"thread_name?\", fp.created_at AS \"latest_post_created_at?\",\n u.id AS \"user_id?\", u.username AS \"username?\", u.warned AS \"warned?\", u.banned AS \"banned?\"\n FROM forum_sub_categories fsc\n INNER JOIN forum_categories fc ON fsc.forum_category_id = fc.id\n LEFT JOIN LATERAL (\n SELECT fp.id, fp.created_at, fp.created_by_id, fp.forum_thread_id\n FROM forum_posts fp\n JOIN forum_threads ft_inner ON fp.forum_thread_id = ft_inner.id\n WHERE ft_inner.forum_sub_category_id = fsc.id\n ORDER BY fp.created_at DESC LIMIT 1\n ) AS fp ON TRUE\n LEFT JOIN forum_threads ft ON fp.forum_thread_id = ft.id\n LEFT JOIN users u ON fp.created_by_id = u.id\n ORDER BY fsc.forum_category_id, fsc.name\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "threads_amount",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "posts_amount",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "forbidden_classes",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "forum_category_id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "category_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "latest_post_id?",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "thread_id?",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "thread_name?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "latest_post_created_at?",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "user_id?",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "username?",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "warned?",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "banned?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9df37ddfafd7cee54e187d4b8195d00f67fb73156ba8a7a0cc23ebc062fa6a71"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n json_strip_nulls(\n json_build_object(\n 'id', fsc.id,\n 'name', fsc.name,\n 'threads_amount', fsc.threads_amount,\n 'posts_amount', fsc.posts_amount,\n 'forbidden_classes', fsc.forbidden_classes,\n 'category', json_build_object(\n 'id', fc.id,\n 'name', fc.name\n ),\n 'threads', (\n SELECT\n COALESCE(\n json_agg(\n json_build_object(\n 'id', ft.id,\n 'name', ft.name,\n 'created_at', ft.created_at,\n 'posts_amount', ft.posts_amount,\n 'latest_post', CASE\n WHEN fp_latest.id IS NOT NULL THEN json_build_object(\n 'id', fp_latest.id,\n 'created_at', fp_latest.created_at,\n 'created_by', json_build_object(\n 'id', u_post.id,\n 'username', u_post.username\n )\n )\n ELSE NULL\n END\n ) ORDER BY ft.created_at DESC\n ),\n '[]'::json\n )\n FROM\n forum_threads ft\n LEFT JOIN LATERAL (\n SELECT\n fp.id,\n fp.created_at,\n fp.created_by_id\n FROM\n forum_posts fp\n WHERE\n fp.forum_thread_id = ft.id\n ORDER BY\n fp.created_at DESC\n LIMIT 1\n ) AS fp_latest ON TRUE\n LEFT JOIN\n users u_post ON fp_latest.created_by_id = u_post.id\n WHERE\n ft.forum_sub_category_id = fsc.id\n )\n )\n ) AS result_json\n FROM\n forum_sub_categories fsc\n JOIN\n forum_categories fc ON fsc.forum_category_id = fc.id\n WHERE\n fsc.id = $1\n GROUP BY\n fsc.id, fc.id;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result_json",
|
||||
"type_info": "Json"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bcad9fe9afe358b71ba0084ea3b19c361479b45fd3937d2a2ac636748cb4cfcd"
|
||||
}
|
||||
26
backend/storage/.sqlx/query-c79d160078a506e5568530d1134001dc5c1c43b6c98e30c64c889ce186712ca9.json
generated
Normal file
26
backend/storage/.sqlx/query-c79d160078a506e5568530d1134001dc5c1c43b6c98e30c64c889ce186712ca9.json
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, name FROM forum_categories ORDER BY id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c79d160078a506e5568530d1134001dc5c1c43b6c98e30c64c889ce186712ca9"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n json_build_object(\n 'forum_categories', json_agg(\n json_build_object(\n 'id', fc.id,\n 'name', fc.name,\n 'sub_categories', (\n SELECT\n json_agg(\n json_build_object(\n 'id', fsc.id,\n 'name', fsc.name,\n 'threads_amount', fsc.threads_amount,\n 'posts_amount', fsc.posts_amount,\n 'forbidden_classes', '[]'::jsonb,\n 'latest_post_in_thread', CASE\n WHEN ft.id IS NOT NULL THEN json_build_object(\n 'id', ft.id,\n 'name', ft.name,\n 'created_at', ft.latest_post_created_at,\n 'created_by', json_build_object( -- Changed to a JSON object for user details\n 'id', ft.latest_post_created_by_id,\n 'username', ft.latest_post_created_by_username\n ),\n 'posts_amount', ft.posts_amount\n )\n ELSE NULL\n END\n ) ORDER BY fsc.name\n )\n FROM\n forum_sub_categories fsc\n LEFT JOIN LATERAL (\n SELECT\n ft_with_latest_post.id,\n ft_with_latest_post.name,\n ft_with_latest_post.posts_amount,\n fp_latest.created_at AS latest_post_created_at,\n fp_latest.created_by_id AS latest_post_created_by_id,\n u.username AS latest_post_created_by_username -- Joined to get the username\n FROM\n forum_posts fp_latest\n JOIN\n forum_threads ft_with_latest_post ON fp_latest.forum_thread_id = ft_with_latest_post.id\n JOIN\n users u ON fp_latest.created_by_id = u.id -- Joined with the users table\n WHERE\n ft_with_latest_post.forum_sub_category_id = fsc.id\n ORDER BY\n fp_latest.created_at DESC\n LIMIT 1\n ) AS ft ON TRUE\n WHERE\n fsc.forum_category_id = fc.id\n )\n ) ORDER BY fc.id\n )\n ) AS forum_overview\n FROM\n forum_categories fc;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "forum_overview",
|
||||
"type_info": "Json"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "d0d5add41466b664ae7a33540e81e774ed0a48e05e8842363e1cf7b28de52668"
|
||||
}
|
||||
@@ -110,7 +110,7 @@ pub struct ForumSubCategoryHierarchy {
|
||||
pub threads_amount: i64,
|
||||
pub posts_amount: i64,
|
||||
pub forbidden_classes: Vec<String>,
|
||||
pub latest_post_in_thread: ForumThreadPostLite,
|
||||
pub latest_post_in_thread: Option<ForumThreadPostLite>,
|
||||
pub threads: Option<Vec<ForumThreadHierarchy>>,
|
||||
pub category: ForumCategoryLite,
|
||||
}
|
||||
@@ -131,6 +131,7 @@ pub struct ForumThreadHierarchy {
|
||||
#[derive(Debug, Deserialize, Serialize, FromRow, ToSchema)]
|
||||
pub struct ForumThreadPostLite {
|
||||
pub id: i64,
|
||||
pub thread_id: i64,
|
||||
pub name: String,
|
||||
#[schema(value_type = String, format = DateTime)]
|
||||
pub created_at: DateTime<Local>,
|
||||
|
||||
@@ -3,30 +3,82 @@ use crate::{
|
||||
models::{
|
||||
common::PaginatedResults,
|
||||
forum::{
|
||||
EditedForumPost, EditedForumThread, ForumPost, ForumPostAndThreadName,
|
||||
ForumPostHierarchy, ForumSearchQuery, ForumSearchResult, ForumThread,
|
||||
ForumThreadEnriched, GetForumThreadPostsQuery, UserCreatedForumPost,
|
||||
EditedForumPost, EditedForumThread, ForumCategoryHierarchy, ForumCategoryLite,
|
||||
ForumPost, ForumPostAndThreadName, ForumPostHierarchy, ForumSearchQuery,
|
||||
ForumSearchResult, ForumSubCategoryHierarchy, ForumThread, ForumThreadEnriched,
|
||||
ForumThreadPostLite, GetForumThreadPostsQuery, UserCreatedForumPost,
|
||||
UserCreatedForumThread,
|
||||
},
|
||||
user::UserLiteAvatar,
|
||||
user::{UserLite, UserLiteAvatar},
|
||||
},
|
||||
};
|
||||
use arcadia_common::error::{Error, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_json::{json, Value};
|
||||
use chrono::{DateTime, Local, Utc};
|
||||
use serde_json::Value;
|
||||
use sqlx::{prelude::FromRow, PgPool};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[derive(FromRow)]
|
||||
struct DBImportSubCategoryWithLatestPost {
|
||||
id: i32,
|
||||
name: String,
|
||||
threads_amount: i64,
|
||||
posts_amount: i64,
|
||||
forbidden_classes: Vec<String>,
|
||||
forum_category_id: i32,
|
||||
category_name: String,
|
||||
latest_post_id: Option<i64>,
|
||||
thread_id: Option<i64>,
|
||||
thread_name: Option<String>,
|
||||
latest_post_created_at: Option<DateTime<Utc>>,
|
||||
user_id: Option<i32>,
|
||||
username: Option<String>,
|
||||
warned: Option<bool>,
|
||||
banned: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow)]
|
||||
struct DBImportForumPost {
|
||||
id: i64,
|
||||
content: String,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
sticky: bool,
|
||||
locked: bool,
|
||||
forum_thread_id: i64,
|
||||
created_by_user_id: i32,
|
||||
created_by_user_username: String,
|
||||
created_by_user_avatar: Option<String>,
|
||||
created_by_user_banned: bool,
|
||||
created_by_user_warned: bool,
|
||||
}
|
||||
|
||||
impl ConnectionPool {
|
||||
pub async fn create_forum_post(
|
||||
&self,
|
||||
forum_post: &UserCreatedForumPost,
|
||||
current_user_id: i32,
|
||||
) -> Result<ForumPost> {
|
||||
if forum_post.content.trim().is_empty() {
|
||||
return Err(Error::ForumPostEmpty);
|
||||
}
|
||||
|
||||
let mut tx = <ConnectionPool as Borrow<PgPool>>::borrow(self)
|
||||
.begin()
|
||||
.await?;
|
||||
|
||||
let thread = sqlx::query!(
|
||||
r#"SELECT locked FROM forum_threads WHERE id = $1"#,
|
||||
forum_post.forum_thread_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(Error::CouldNotCreateForumPost)?;
|
||||
|
||||
if thread.locked {
|
||||
return Err(Error::ForumThreadLocked);
|
||||
}
|
||||
|
||||
let created_forum_post = sqlx::query_as!(
|
||||
ForumPost,
|
||||
r#"
|
||||
@@ -120,6 +172,14 @@ impl ConnectionPool {
|
||||
forum_thread: &mut UserCreatedForumThread,
|
||||
current_user_id: i32,
|
||||
) -> Result<ForumThread> {
|
||||
if forum_thread.name.trim().is_empty() {
|
||||
return Err(Error::ForumThreadNameEmpty);
|
||||
}
|
||||
|
||||
if forum_thread.first_post.content.trim().is_empty() {
|
||||
return Err(Error::ForumPostEmpty);
|
||||
}
|
||||
|
||||
let mut tx = <ConnectionPool as Borrow<PgPool>>::borrow(self)
|
||||
.begin()
|
||||
.await?;
|
||||
@@ -155,11 +215,21 @@ impl ConnectionPool {
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
// TODO: include this in the transaction
|
||||
// Create the first post (this will increment posts_amount)
|
||||
self.create_forum_post(&forum_thread.first_post, current_user_id)
|
||||
.await?;
|
||||
|
||||
Ok(created_forum_thread)
|
||||
// Fetch and return the updated thread with correct posts_amount
|
||||
let updated_thread = sqlx::query_as!(
|
||||
ForumThread,
|
||||
r#"SELECT * FROM forum_threads WHERE id = $1"#,
|
||||
created_forum_thread.id
|
||||
)
|
||||
.fetch_one(self.borrow())
|
||||
.await
|
||||
.map_err(Error::CouldNotFindForumThread)?;
|
||||
|
||||
Ok(updated_thread)
|
||||
}
|
||||
|
||||
pub async fn update_forum_thread(
|
||||
@@ -167,6 +237,10 @@ impl ConnectionPool {
|
||||
edited_thread: &EditedForumThread,
|
||||
user_id: i32,
|
||||
) -> Result<ForumThreadEnriched> {
|
||||
if edited_thread.name.trim().is_empty() {
|
||||
return Err(Error::BadRequest("Thread name cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
let updated_thread = sqlx::query_as!(
|
||||
ForumThreadEnriched,
|
||||
r#"
|
||||
@@ -212,81 +286,109 @@ impl ConnectionPool {
|
||||
Ok(updated_thread)
|
||||
}
|
||||
|
||||
pub async fn find_forum_cateogries_hierarchy(&self) -> Result<Value> {
|
||||
let forum_overview = sqlx::query!(
|
||||
pub async fn find_forum_cateogries_hierarchy(&self) -> Result<Vec<ForumCategoryHierarchy>> {
|
||||
// Query all categories at once
|
||||
let categories = sqlx::query_as!(
|
||||
ForumCategoryLite,
|
||||
"SELECT id, name FROM forum_categories ORDER BY id"
|
||||
)
|
||||
.fetch_all(self.borrow())
|
||||
.await
|
||||
.map_err(Error::CouldNotFindForumSubCategory)?;
|
||||
|
||||
// Query all subcategories with their latest posts in one query
|
||||
let sub_categories_data = sqlx::query_as!(
|
||||
DBImportSubCategoryWithLatestPost,
|
||||
r#"
|
||||
SELECT
|
||||
json_build_object(
|
||||
'forum_categories', json_agg(
|
||||
json_build_object(
|
||||
'id', fc.id,
|
||||
'name', fc.name,
|
||||
'sub_categories', (
|
||||
SELECT
|
||||
json_agg(
|
||||
json_build_object(
|
||||
'id', fsc.id,
|
||||
'name', fsc.name,
|
||||
'threads_amount', fsc.threads_amount,
|
||||
'posts_amount', fsc.posts_amount,
|
||||
'forbidden_classes', '[]'::jsonb,
|
||||
'latest_post_in_thread', CASE
|
||||
WHEN ft.id IS NOT NULL THEN json_build_object(
|
||||
'id', ft.id,
|
||||
'name', ft.name,
|
||||
'created_at', ft.latest_post_created_at,
|
||||
'created_by', json_build_object( -- Changed to a JSON object for user details
|
||||
'id', ft.latest_post_created_by_id,
|
||||
'username', ft.latest_post_created_by_username
|
||||
),
|
||||
'posts_amount', ft.posts_amount
|
||||
)
|
||||
ELSE NULL
|
||||
END
|
||||
) ORDER BY fsc.name
|
||||
)
|
||||
FROM
|
||||
forum_sub_categories fsc
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
ft_with_latest_post.id,
|
||||
ft_with_latest_post.name,
|
||||
ft_with_latest_post.posts_amount,
|
||||
fp_latest.created_at AS latest_post_created_at,
|
||||
fp_latest.created_by_id AS latest_post_created_by_id,
|
||||
u.username AS latest_post_created_by_username -- Joined to get the username
|
||||
FROM
|
||||
forum_posts fp_latest
|
||||
JOIN
|
||||
forum_threads ft_with_latest_post ON fp_latest.forum_thread_id = ft_with_latest_post.id
|
||||
JOIN
|
||||
users u ON fp_latest.created_by_id = u.id -- Joined with the users table
|
||||
WHERE
|
||||
ft_with_latest_post.forum_sub_category_id = fsc.id
|
||||
ORDER BY
|
||||
fp_latest.created_at DESC
|
||||
LIMIT 1
|
||||
) AS ft ON TRUE
|
||||
WHERE
|
||||
fsc.forum_category_id = fc.id
|
||||
)
|
||||
) ORDER BY fc.id
|
||||
)
|
||||
) AS forum_overview
|
||||
FROM
|
||||
forum_categories fc;
|
||||
SELECT fsc.id, fsc.name, fsc.threads_amount, fsc.posts_amount, fsc.forbidden_classes,
|
||||
fsc.forum_category_id, fc.name AS category_name,
|
||||
fp.id AS "latest_post_id?", ft.id AS "thread_id?", ft.name AS "thread_name?", fp.created_at AS "latest_post_created_at?",
|
||||
u.id AS "user_id?", u.username AS "username?", u.warned AS "warned?", u.banned AS "banned?"
|
||||
FROM forum_sub_categories fsc
|
||||
INNER JOIN forum_categories fc ON fsc.forum_category_id = fc.id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT fp.id, fp.created_at, fp.created_by_id, fp.forum_thread_id
|
||||
FROM forum_posts fp
|
||||
JOIN forum_threads ft_inner ON fp.forum_thread_id = ft_inner.id
|
||||
WHERE ft_inner.forum_sub_category_id = fsc.id
|
||||
ORDER BY fp.created_at DESC LIMIT 1
|
||||
) AS fp ON TRUE
|
||||
LEFT JOIN forum_threads ft ON fp.forum_thread_id = ft.id
|
||||
LEFT JOIN users u ON fp.created_by_id = u.id
|
||||
ORDER BY fsc.forum_category_id, fsc.name
|
||||
"#
|
||||
)
|
||||
.fetch_one(self.borrow())
|
||||
.fetch_all(self.borrow())
|
||||
.await
|
||||
.expect("error getting forums");
|
||||
.map_err(Error::CouldNotFindForumSubCategory)?;
|
||||
|
||||
Ok(forum_overview
|
||||
.forum_overview
|
||||
.unwrap()
|
||||
.get("forum_categories")
|
||||
.unwrap_or(&json!([]))
|
||||
.to_owned())
|
||||
// Build hierarchy by grouping subcategories by category
|
||||
use std::collections::HashMap;
|
||||
let mut category_map: HashMap<i32, Vec<ForumSubCategoryHierarchy>> = HashMap::new();
|
||||
|
||||
for sc in sub_categories_data {
|
||||
let sub_category = ForumSubCategoryHierarchy {
|
||||
id: sc.id,
|
||||
name: sc.name,
|
||||
threads_amount: sc.threads_amount,
|
||||
posts_amount: sc.posts_amount,
|
||||
forbidden_classes: sc.forbidden_classes,
|
||||
latest_post_in_thread: match (
|
||||
sc.latest_post_id,
|
||||
sc.thread_id,
|
||||
sc.thread_name,
|
||||
sc.latest_post_created_at,
|
||||
sc.user_id,
|
||||
sc.username,
|
||||
sc.warned,
|
||||
sc.banned,
|
||||
) {
|
||||
(
|
||||
Some(id),
|
||||
Some(thread_id),
|
||||
Some(name),
|
||||
Some(created_at),
|
||||
Some(user_id),
|
||||
Some(username),
|
||||
Some(warned),
|
||||
Some(banned),
|
||||
) => Some(ForumThreadPostLite {
|
||||
id,
|
||||
thread_id,
|
||||
name,
|
||||
created_at: created_at.with_timezone(&Local),
|
||||
created_by: UserLite {
|
||||
id: user_id,
|
||||
username,
|
||||
warned,
|
||||
banned,
|
||||
},
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
threads: None,
|
||||
category: ForumCategoryLite {
|
||||
id: sc.forum_category_id,
|
||||
name: sc.category_name,
|
||||
},
|
||||
};
|
||||
category_map
|
||||
.entry(sc.forum_category_id)
|
||||
.or_default()
|
||||
.push(sub_category);
|
||||
}
|
||||
|
||||
// Build final result with categories in order
|
||||
let forum_categories = categories
|
||||
.into_iter()
|
||||
.map(|category| ForumCategoryHierarchy {
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
sub_categories: category_map.remove(&category.id).unwrap_or_default(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(forum_categories)
|
||||
}
|
||||
|
||||
pub async fn find_forum_sub_category_threads(
|
||||
@@ -316,23 +418,34 @@ impl ConnectionPool {
|
||||
'name', ft.name,
|
||||
'created_at', ft.created_at,
|
||||
'posts_amount', ft.posts_amount,
|
||||
'latest_post', CASE
|
||||
WHEN fp_latest.id IS NOT NULL THEN json_build_object(
|
||||
'id', fp_latest.id,
|
||||
'created_at', fp_latest.created_at,
|
||||
'created_by', json_build_object(
|
||||
'id', u_post.id,
|
||||
'username', u_post.username
|
||||
)
|
||||
'sticky', ft.sticky,
|
||||
'locked', ft.locked,
|
||||
'created_by', json_build_object(
|
||||
'id', u_thread.id,
|
||||
'username', u_thread.username,
|
||||
'warned', u_thread.warned,
|
||||
'banned', u_thread.banned
|
||||
),
|
||||
'latest_post', json_build_object(
|
||||
'id', fp_latest.id,
|
||||
'thread_id', ft.id,
|
||||
'name', ft.name,
|
||||
'created_at', fp_latest.created_at,
|
||||
'created_by', json_build_object(
|
||||
'id', u_post.id,
|
||||
'username', u_post.username,
|
||||
'warned', u_post.warned,
|
||||
'banned', u_post.banned
|
||||
)
|
||||
ELSE NULL
|
||||
END
|
||||
)
|
||||
) ORDER BY ft.created_at DESC
|
||||
),
|
||||
'[]'::json
|
||||
)
|
||||
FROM
|
||||
forum_threads ft
|
||||
JOIN
|
||||
users u_thread ON ft.created_by_id = u_thread.id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
fp.id,
|
||||
@@ -364,12 +477,16 @@ impl ConnectionPool {
|
||||
"#,
|
||||
forum_sub_category_id
|
||||
)
|
||||
.fetch_one(self.borrow())
|
||||
.fetch_optional(self.borrow())
|
||||
.await
|
||||
.map_err(Error::CouldNotFindForumSubCategory)?;
|
||||
|
||||
//TODO: unwrap can fail return Error::CouldNotFindForumSubCategory
|
||||
Ok(forum_sub_category.result_json.unwrap())
|
||||
match forum_sub_category {
|
||||
Some(record) => Ok(record.result_json.unwrap_or(serde_json::json!({}))),
|
||||
None => Err(Error::CouldNotFindForumSubCategory(
|
||||
sqlx::Error::RowNotFound,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_forum_thread(
|
||||
@@ -447,22 +564,6 @@ impl ConnectionPool {
|
||||
((form.page.unwrap_or(1) - 1) as i64) * page_size
|
||||
};
|
||||
|
||||
#[derive(Debug, FromRow)]
|
||||
struct DBImportForumPost {
|
||||
id: i64,
|
||||
content: String,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
sticky: bool,
|
||||
locked: bool,
|
||||
forum_thread_id: i64,
|
||||
created_by_user_id: i32,
|
||||
created_by_user_username: String,
|
||||
created_by_user_avatar: Option<String>,
|
||||
created_by_user_banned: bool,
|
||||
created_by_user_warned: bool,
|
||||
}
|
||||
|
||||
let posts = sqlx::query_as!(
|
||||
DBImportForumPost,
|
||||
r#"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</Column>
|
||||
<Column style="width: 35%" field="latest_post_in_thread.name" :header="t('forum.latest_post')">
|
||||
<template #body="slotProps">
|
||||
<RouterLink :to="'/forum/thread/' + slotProps.data.latest_post_in_thread.id">
|
||||
<RouterLink :to="'/forum/thread/' + slotProps.data.latest_post_in_thread.thread_id">
|
||||
{{ slotProps.data.latest_post_in_thread.name }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
@@ -613,7 +613,7 @@ export interface ForumSubCategoryHierarchy {
|
||||
'category': ForumCategoryLite;
|
||||
'forbidden_classes': Array<string>;
|
||||
'id': number;
|
||||
'latest_post_in_thread': ForumThreadPostLite;
|
||||
'latest_post_in_thread'?: ForumThreadPostLite | null;
|
||||
'name': string;
|
||||
'posts_amount': number;
|
||||
'threads'?: Array<ForumThreadHierarchy> | null;
|
||||
@@ -658,6 +658,7 @@ export interface ForumThreadPostLite {
|
||||
'created_by': UserLite;
|
||||
'id': number;
|
||||
'name': string;
|
||||
'thread_id': number;
|
||||
}
|
||||
export interface GetForumThreadPostsQuery {
|
||||
'page'?: number | null;
|
||||
|
||||
Reference in New Issue
Block a user