diff --git a/migrations/20230421174120_remove-threads-ref.sql b/migrations/20230421174120_remove-threads-ref.sql new file mode 100644 index 000000000..13eeab8bb --- /dev/null +++ b/migrations/20230421174120_remove-threads-ref.sql @@ -0,0 +1,2 @@ +ALTER TABLE threads DROP COLUMN project_id; +ALTER TABLE threads DROP COLUMN report_id; \ No newline at end of file diff --git a/sqlx-data.json b/sqlx-data.json index 180ae8656..5482ee16d 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -130,6 +130,50 @@ }, "query": "\n UPDATE mods\n SET source_url = $1\n WHERE (id = $2)\n " }, + "04a6310fcc8183e6795a63a5017d607174a9a94587ce774e124613b184dfff43": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "thread_type", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "show_in_mod_inbox", + "ordinal": 2, + "type_info": "Bool" + }, + { + "name": "members", + "ordinal": 3, + "type_info": "Int8Array" + }, + { + "name": "messages", + "ordinal": 4, + "type_info": "Jsonb" + } + ], + "nullable": [ + false, + false, + false, + null, + null + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT t.id, t.thread_type, t.show_in_mod_inbox,\n ARRAY_AGG(DISTINCT tm.user_id) filter (where tm.user_id is not null) members,\n JSONB_AGG(DISTINCT jsonb_build_object('id', tmsg.id, 'author_id', tmsg.author_id, 'thread_id', tmsg.thread_id, 'body', tmsg.body, 'created', tmsg.created)) filter (where tmsg.id is not null) messages\n FROM threads t\n LEFT OUTER JOIN threads_messages tmsg ON tmsg.thread_id = t.id\n LEFT OUTER JOIN threads_members tm ON tm.thread_id = t.id\n WHERE t.id = ANY($1)\n GROUP BY t.id\n " + }, "04e5ecb14c526000e9098efb65861f6125e6fcc88f39d6ad811ac8504d229de1": { "describe": { "columns": [], @@ -775,6 +819,38 @@ }, "query": "\n UPDATE notifications\n SET read = TRUE\n WHERE id = ANY($1)\n " }, + "13e9d01d815b415eb8505e2362319cbdb7881f100f80671289f47886f3ed084e": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "status", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 2, + "type_info": "Int8" + } + ], + "nullable": [ + false, + false, + false + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "SELECT m.id, m.status, m.team_id FROM mods m WHERE thread_id = $1" + }, "1411c9ae3af067679aa21d7f45937cd94d457e4eb17a108566776a9bd1ee77e2": { "describe": { "columns": [], @@ -2062,6 +2138,19 @@ }, "query": "\n SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created, r.thread_id, r.closed\n FROM reports r\n INNER JOIN report_types rt ON rt.id = r.report_type_id\n WHERE r.id = ANY($1)\n ORDER BY r.created DESC\n " }, + "3ae7c4a29dab8bce0e84a9c47a4a4f50a3be4bcb86e5b13d7dd60975d62e9ea3": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Varchar" + ] + } + }, + "query": "\n INSERT INTO threads (\n id, thread_type\n )\n VALUES (\n $1, $2\n )\n " + }, "3af747b5543a5a9b10dcce0a1eb9c2a1926dd5a507fe0d8b7f52d8ccc7fcd0af": { "describe": { "columns": [], @@ -2284,62 +2373,6 @@ }, "query": "\n SELECT gv.id id, gv.version version_, gv.type type_, gv.created created, gv.major major FROM game_versions gv\n WHERE major = $1 AND type = $2\n ORDER BY created DESC\n " }, - "447c21dd66f9ac85f9f9d39402d2ceb8440d5951e2c7277d329b65412d330f4e": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "thread_type", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "show_in_mod_inbox", - "ordinal": 2, - "type_info": "Bool" - }, - { - "name": "project_id", - "ordinal": 3, - "type_info": "Int8" - }, - { - "name": "report_id", - "ordinal": 4, - "type_info": "Int8" - }, - { - "name": "members", - "ordinal": 5, - "type_info": "Int8Array" - }, - { - "name": "messages", - "ordinal": 6, - "type_info": "Jsonb" - } - ], - "nullable": [ - false, - false, - false, - true, - true, - null, - null - ], - "parameters": { - "Left": [ - "Int8Array" - ] - } - }, - "query": "\n SELECT t.id, t.thread_type, t.show_in_mod_inbox, t.project_id, t.report_id,\n ARRAY_AGG(DISTINCT tm.user_id) filter (where tm.user_id is not null) members,\n JSONB_AGG(DISTINCT jsonb_build_object('id', tmsg.id, 'author_id', tmsg.author_id, 'thread_id', tmsg.thread_id, 'body', tmsg.body, 'created', tmsg.created)) filter (where tmsg.id is not null) messages\n FROM threads t\n LEFT OUTER JOIN threads_messages tmsg ON tmsg.thread_id = t.id\n LEFT OUTER JOIN threads_members tm ON tm.thread_id = t.id\n WHERE t.id = ANY($1)\n GROUP BY t.id\n " - }, "4567790f0dc98ff20b596a33161d1f6ac8af73da67fe8c54192724626c6bf670": { "describe": { "columns": [], @@ -3353,6 +3386,26 @@ }, "query": "\n INSERT INTO threads_members (\n thread_id, user_id\n )\n VALUES (\n $1, $2\n )\n " }, + "71766ebbecc05c27ae368992f6ec1043661deb57ba62b7f2328016df9c055cba": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "SELECT r.id FROM reports r WHERE thread_id = $1" + }, "71abd207410d123f9a50345ddcddee335fea0d0cc6f28762713ee01a36aee8a0": { "describe": { "columns": [ @@ -3752,32 +3805,6 @@ }, "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, FALSE)\n " }, - "7ee877b149288e53a14b5f0f3417806aee7a8954fa0fc17da80d59bebbe067d2": { - "describe": { - "columns": [ - { - "name": "status", - "ordinal": 0, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 1, - "type_info": "Int8" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT m.status, m.team_id FROM mods m WHERE thread_id = $1" - }, "83d428e1c07d16e356ef26bdf1d707940b1683b5f631ded1f6674a081453d67b": { "describe": { "columns": [], @@ -5404,21 +5431,6 @@ }, "query": "\n SELECT v.id version_id, v.mod_id project_id, h.hash hash FROM hashes h\n INNER JOIN files f on h.file_id = f.id\n INNER JOIN versions v on f.version_id = v.id\n WHERE h.algorithm = 'sha1' AND h.hash = ANY($1)\n " }, - "d0a65443aef9d3781000d0a59d8d81d1a8e51613dfee343d079c233375cebd12": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Varchar", - "Int8", - "Int8" - ] - } - }, - "query": "\n INSERT INTO threads (\n id, thread_type, report_id, project_id\n )\n VALUES (\n $1, $2, $3, $4\n )\n " - }, "d12bc07adb4dc8147d0ddccd72a4f23ed38cd31d7db3d36ebbe2c9b627130f0b": { "describe": { "columns": [], diff --git a/src/database/models/thread_item.rs b/src/database/models/thread_item.rs index 3fb33973e..18c2a04c4 100644 --- a/src/database/models/thread_item.rs +++ b/src/database/models/thread_item.rs @@ -7,8 +7,6 @@ use serde::Deserialize; pub struct ThreadBuilder { pub type_: ThreadType, pub members: Vec, - pub project_id: Option, - pub report_id: Option, } #[derive(Clone)] @@ -18,8 +16,6 @@ pub struct Thread { pub messages: Vec, pub members: Vec, pub show_in_mod_inbox: bool, - pub project_id: Option, - pub report_id: Option, } pub struct ThreadMessageBuilder { @@ -71,20 +67,17 @@ impl ThreadBuilder { transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { let thread_id = generate_thread_id(&mut *transaction).await?; - sqlx::query!( " INSERT INTO threads ( - id, thread_type, report_id, project_id + id, thread_type ) VALUES ( - $1, $2, $3, $4 + $1, $2 ) ", thread_id as ThreadId, - self.type_.as_str(), - self.report_id.map(|x| x.0), - self.project_id.map(|x| x.0), + self.type_.as_str() ) .execute(&mut *transaction) .await?; @@ -132,7 +125,7 @@ impl Thread { let thread_ids_parsed: Vec = thread_ids.iter().map(|x| x.0).collect(); let threads = sqlx::query!( " - SELECT t.id, t.thread_type, t.show_in_mod_inbox, t.project_id, t.report_id, + SELECT t.id, t.thread_type, t.show_in_mod_inbox, ARRAY_AGG(DISTINCT tm.user_id) filter (where tm.user_id is not null) members, JSONB_AGG(DISTINCT jsonb_build_object('id', tmsg.id, 'author_id', tmsg.author_id, 'thread_id', tmsg.thread_id, 'body', tmsg.body, 'created', tmsg.created)) filter (where tmsg.id is not null) messages FROM threads t @@ -159,8 +152,6 @@ impl Thread { }, members: x.members.unwrap_or_default().into_iter().map(UserId).collect(), show_in_mod_inbox: x.show_in_mod_inbox, - project_id: x.project_id.map(ProjectId), - report_id: x.report_id.map(ReportId), })) }) .try_collect::>() diff --git a/src/models/threads.rs b/src/models/threads.rs index 22ff821a5..22ba9a33e 100644 --- a/src/models/threads.rs +++ b/src/models/threads.rs @@ -1,5 +1,4 @@ use super::ids::Base62Id; -use crate::models::ids::{ProjectId, ReportId}; use crate::models::projects::ProjectStatus; use crate::models::users::{User, UserId}; use chrono::{DateTime, Utc}; @@ -22,9 +21,6 @@ pub struct Thread { pub type_: ThreadType, pub messages: Vec, pub members: Vec, - - pub project_id: Option, - pub report_id: Option, } #[derive(Serialize, Deserialize)] diff --git a/src/routes/v2/project_creation.rs b/src/routes/v2/project_creation.rs index 256b2c108..f7096a4bd 100644 --- a/src/routes/v2/project_creation.rs +++ b/src/routes/v2/project_creation.rs @@ -721,8 +721,6 @@ async fn project_create_inner( let thread_id = ThreadBuilder { type_: ThreadType::Project, members: vec![], - project_id: Some(project_id.into()), - report_id: None, } .insert(&mut *transaction) .await?; diff --git a/src/routes/v2/reports.rs b/src/routes/v2/reports.rs index 17c0eb72a..13c88c515 100644 --- a/src/routes/v2/reports.rs +++ b/src/routes/v2/reports.rs @@ -58,8 +58,6 @@ pub async fn report_create( let thread_id = ThreadBuilder { type_: ThreadType::Report, members: vec![], - project_id: None, - report_id: Some(id), } .insert(&mut transaction) .await?; diff --git a/src/routes/v2/threads.rs b/src/routes/v2/threads.rs index e77c31a6a..08bb1296a 100644 --- a/src/routes/v2/threads.rs +++ b/src/routes/v2/threads.rs @@ -1,9 +1,9 @@ use crate::database; use crate::database::models::notification_item::NotificationBuilder; use crate::database::models::thread_item::ThreadMessageBuilder; -use crate::models::ids::{ReportId, ThreadMessageId}; +use crate::models::ids::ThreadMessageId; use crate::models::notifications::NotificationBody; -use crate::models::projects::{ProjectId, ProjectStatus}; +use crate::models::projects::ProjectStatus; use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadMessage, ThreadType}; use crate::models::users::User; use crate::routes::ApiError; @@ -240,8 +240,6 @@ fn convert_thread(data: database::models::Thread, users: Vec, user: &User) .into_iter() .filter(|x| !x.role.is_mod() || user.role.is_mod()) .collect(), - project_id: data.project_id.map(ProjectId::from), - report_id: data.report_id.map(ReportId::from), } } @@ -370,21 +368,19 @@ pub async fn thread_send_message( return Ok(HttpResponse::NotFound().body("")); } - let report = if let Some(report) = thread.report_id { - database::models::report_item::Report::get(report, &**pool).await? - } else { - None - }; + let mut transaction = pool.begin().await?; - if report.as_ref().map(|x| x.closed).unwrap_or(false) && !user.role.is_mod() { - return Err(ApiError::InvalidInput( - "You may not reply to a closed report".to_string(), - )); + let id = ThreadMessageBuilder { + author_id: Some(user.id.into()), + body: new_message.body.clone(), + thread_id: thread.id, } + .insert(&mut transaction) + .await?; - let (mod_notif, (user_notif, team_id)) = if thread.type_ == ThreadType::Project { + let mod_notif = if thread.type_ == ThreadType::Project { let record = sqlx::query!( - "SELECT m.status, m.team_id FROM mods m WHERE thread_id = $1", + "SELECT m.id, m.status, m.team_id FROM mods m WHERE thread_id = $1", thread.id as database::models::ids::ThreadId, ) .fetch_one(&**pool) @@ -392,28 +388,69 @@ pub async fn thread_send_message( let status = ProjectStatus::from_str(&record.status); - ( - status == ProjectStatus::Processing && !user.role.is_mod(), - ( - status != ProjectStatus::Processing && user.role.is_mod(), - Some(record.team_id), - ), + if status != ProjectStatus::Processing && user.role.is_mod() { + let members = database::models::TeamMember::get_from_team_full( + database::models::TeamId(record.team_id), + &**pool, + ) + .await?; + + NotificationBuilder { + body: NotificationBody::ModeratorMessage { + thread_id: thread.id.into(), + message_id: id.into(), + project_id: Some(database::models::ProjectId(record.id).into()), + report_id: None, + }, + } + .insert_many( + members.into_iter().map(|x| x.user.id).collect(), + &mut transaction, + ) + .await?; + } + + status == ProjectStatus::Processing && !user.role.is_mod() + } else if thread.type_ == ThreadType::Report { + let record = sqlx::query!( + "SELECT r.id FROM reports r WHERE thread_id = $1", + thread.id as database::models::ids::ThreadId, ) + .fetch_one(&**pool) + .await?; + + let report = database::models::report_item::Report::get( + database::models::ReportId(record.id), + &**pool, + ) + .await?; + + if let Some(report) = report { + if report.closed && !user.role.is_mod() { + return Err(ApiError::InvalidInput( + "You may not reply to a closed report".to_string(), + )); + } + + if user.id != report.reporter.into() { + NotificationBuilder { + body: NotificationBody::ModeratorMessage { + thread_id: thread.id.into(), + message_id: id.into(), + project_id: None, + report_id: Some(report.id.into()), + }, + } + .insert(report.reporter, &mut transaction) + .await?; + } + } + + !user.role.is_mod() } else { - ( - !user.role.is_mod(), - ( - thread.type_ == ThreadType::Report - && !report - .as_ref() - .map(|x| x.reporter == user.id.into()) - .unwrap_or(false), - None, - ), - ) + false }; - let mut transaction = pool.begin().await?; sqlx::query!( " UPDATE threads @@ -426,43 +463,6 @@ pub async fn thread_send_message( .execute(&mut *transaction) .await?; - let id = ThreadMessageBuilder { - author_id: Some(user.id.into()), - body: new_message.body.clone(), - thread_id: thread.id, - } - .insert(&mut transaction) - .await?; - - if user_notif { - let users_to_notify = if let Some(team_id) = team_id { - let members = database::models::TeamMember::get_from_team_full( - database::models::TeamId(team_id), - &**pool, - ) - .await?; - - members.into_iter().map(|x| x.user.id).collect() - } else if let Some(report) = report { - vec![report.reporter] - } else { - vec![] - }; - - if !users_to_notify.is_empty() { - NotificationBuilder { - body: NotificationBody::ModeratorMessage { - thread_id: thread.id.into(), - message_id: id.into(), - project_id: thread.project_id.map(ProjectId::from), - report_id: thread.report_id.map(ReportId::from), - }, - } - .insert_many(users_to_notify, &mut transaction) - .await?; - } - } - transaction.commit().await?; Ok(HttpResponse::NoContent().body(""))