Skip to main content

max / makenotwork

7.3 KB · 244 lines History Blame Raw
1 //! Multithreaded forum thread provisioning for published items and blog posts.
2
3 use crate::db;
4 use crate::db::{DbBlogPost, DbItem};
5 use crate::AppState;
6
7 /// Fire-and-forget: create an MT discussion thread for a published item.
8 /// Called from the item update handler where the user is available.
9 pub fn spawn_mt_thread_for_item(
10 state: &AppState,
11 item: &DbItem,
12 user: &crate::auth::SessionUser,
13 ) {
14 let Some(ref mt) = state.mt_client else {
15 return;
16 };
17 let mt = mt.clone();
18 let db_pool = state.db.clone();
19 let host_url = state.config.host_url.clone();
20 let item_id = item.id;
21 let item_title = item.title.clone();
22 let project_id = item.project_id;
23 let user_id = *user.id;
24 let username = user.username.to_string();
25 let display_name = user.display_name.clone();
26
27 tokio::spawn(async move {
28 create_mt_thread_for_item(
29 &mt,
30 &db_pool,
31 &host_url,
32 item_id,
33 &item_title,
34 project_id,
35 user_id,
36 &username,
37 display_name.as_deref(),
38 )
39 .await;
40 });
41 }
42
43 /// Fire-and-forget: create an MT discussion thread for a published item
44 /// (scheduler version — looks up the project/user from DB).
45 pub(super) fn spawn_mt_thread_for_item_by_lookup(state: &AppState, item: &DbItem) {
46 let Some(ref mt) = state.mt_client else {
47 return;
48 };
49 let mt = mt.clone();
50 let db_pool = state.db.clone();
51 let host_url = state.config.host_url.clone();
52 let item_id = item.id;
53 let item_title = item.title.clone();
54 let project_id = item.project_id;
55
56 tokio::spawn(async move {
57 let Ok(Some(project)) = db::projects::get_project_by_id(&db_pool, project_id).await
58 else {
59 return;
60 };
61 let Ok(Some(user)) = db::users::get_user_by_id(&db_pool, project.user_id).await else {
62 return;
63 };
64 create_mt_thread_for_item(
65 &mt,
66 &db_pool,
67 &host_url,
68 item_id,
69 &item_title,
70 project_id,
71 *user.id,
72 &user.username,
73 user.display_name.as_deref(),
74 )
75 .await;
76 });
77 }
78
79 #[allow(clippy::too_many_arguments)]
80 async fn create_mt_thread_for_item(
81 mt: &crate::mt_client::MtClient,
82 db_pool: &sqlx::PgPool,
83 host_url: &str,
84 item_id: db::ItemId,
85 item_title: &str,
86 project_id: db::ProjectId,
87 user_id: uuid::Uuid,
88 username: &str,
89 display_name: Option<&str>,
90 ) {
91 let Ok(Some(project)) = db::projects::get_project_by_id(db_pool, project_id).await else {
92 return;
93 };
94
95 let item_url = format!("{}/i/{}", host_url, item_id);
96 let body = format!("Discussion for [{}]({})", item_title, item_url);
97 let external_ref = format!("mnw:item:{}", item_id);
98
99 match mt
100 .create_thread(&crate::mt_client::CreateThreadRequest {
101 community_slug: project.slug.to_string(),
102 category_slug: "items".to_string(),
103 title: item_title.to_string(),
104 body_markdown: body,
105 author_mnw_id: user_id,
106 author_username: username.to_string(),
107 author_display_name: display_name.map(String::from),
108 external_ref,
109 })
110 .await
111 {
112 Ok(resp) => {
113 if let Err(e) = db::items::set_mt_thread_id(db_pool, item_id, resp.thread_id).await {
114 tracing::warn!(error = ?e, "failed to store MT thread ID for item");
115 }
116 }
117 Err(e) => tracing::warn!(error = ?e, %item_id, "MT thread creation failed for item"),
118 }
119 }
120
121 /// Fire-and-forget: create an MT discussion thread for a published blog post.
122 /// Called from the blog post handler where the user is available.
123 pub fn spawn_mt_thread_for_blog_post(
124 state: &AppState,
125 post: &DbBlogPost,
126 user: &crate::auth::SessionUser,
127 ) {
128 let Some(ref mt) = state.mt_client else {
129 return;
130 };
131 let mt = mt.clone();
132 let db_pool = state.db.clone();
133 let host_url = state.config.host_url.clone();
134 let post_id = post.id;
135 let post_title = post.title.clone();
136 let post_slug = post.slug.to_string();
137 let project_id = post.project_id;
138 let user_id = *user.id;
139 let username = user.username.to_string();
140 let display_name = user.display_name.clone();
141
142 tokio::spawn(async move {
143 create_mt_thread_for_blog_post(
144 &mt,
145 &db_pool,
146 &host_url,
147 post_id,
148 &post_title,
149 &post_slug,
150 project_id,
151 user_id,
152 &username,
153 display_name.as_deref(),
154 )
155 .await;
156 });
157 }
158
159 /// Fire-and-forget: create an MT discussion thread for a published blog post
160 /// (scheduler version — looks up the project/user from DB).
161 pub(super) fn spawn_mt_thread_for_blog_post_by_lookup(state: &AppState, post: &DbBlogPost) {
162 let Some(ref mt) = state.mt_client else {
163 return;
164 };
165 let mt = mt.clone();
166 let db_pool = state.db.clone();
167 let host_url = state.config.host_url.clone();
168 let post_id = post.id;
169 let post_title = post.title.clone();
170 let post_slug = post.slug.to_string();
171 let project_id = post.project_id;
172
173 tokio::spawn(async move {
174 let Ok(Some(project)) = db::projects::get_project_by_id(&db_pool, project_id).await
175 else {
176 return;
177 };
178 let Ok(Some(user)) = db::users::get_user_by_id(&db_pool, project.user_id).await else {
179 return;
180 };
181 create_mt_thread_for_blog_post(
182 &mt,
183 &db_pool,
184 &host_url,
185 post_id,
186 &post_title,
187 &post_slug,
188 project_id,
189 *user.id,
190 &user.username,
191 user.display_name.as_deref(),
192 )
193 .await;
194 });
195 }
196
197 #[allow(clippy::too_many_arguments)]
198 async fn create_mt_thread_for_blog_post(
199 mt: &crate::mt_client::MtClient,
200 db_pool: &sqlx::PgPool,
201 host_url: &str,
202 post_id: db::BlogPostId,
203 post_title: &str,
204 post_slug: &str,
205 project_id: db::ProjectId,
206 user_id: uuid::Uuid,
207 username: &str,
208 display_name: Option<&str>,
209 ) {
210 let Ok(Some(project)) = db::projects::get_project_by_id(db_pool, project_id).await else {
211 return;
212 };
213
214 let post_url = format!(
215 "{}/{}/blog/{}",
216 host_url, project.slug, post_slug
217 );
218 let body = format!("Discussion for [{}]({})", post_title, post_url);
219 let external_ref = format!("mnw:blog:{}", post_id);
220
221 match mt
222 .create_thread(&crate::mt_client::CreateThreadRequest {
223 community_slug: project.slug.to_string(),
224 category_slug: "blog".to_string(),
225 title: post_title.to_string(),
226 body_markdown: body,
227 author_mnw_id: user_id,
228 author_username: username.to_string(),
229 author_display_name: display_name.map(String::from),
230 external_ref,
231 })
232 .await
233 {
234 Ok(resp) => {
235 if let Err(e) =
236 db::blog_posts::set_mt_thread_id(db_pool, post_id, resp.thread_id).await
237 {
238 tracing::warn!(error = ?e, "failed to store MT thread ID for blog post");
239 }
240 }
241 Err(e) => tracing::warn!(error = ?e, %post_id, "MT thread creation failed for blog post"),
242 }
243 }
244