2 newtypes::{CommentId, DbUrl, PersonId},
12 traits::{Crud, DeleteableOrRemoveable, Likeable, Saveable},
15 use diesel::{dsl::*, result::Error, *};
16 use diesel_ltree::Ltree;
20 pub fn permadelete_for_creator(
21 conn: &mut PgConnection,
22 for_creator_id: PersonId,
23 ) -> Result<Vec<Self>, Error> {
24 use crate::schema::comment::dsl::*;
25 diesel::update(comment.filter(creator_id.eq(for_creator_id)))
27 content.eq("*Permananently Deleted*"),
29 updated.eq(naive_now()),
31 .get_results::<Self>(conn)
34 pub fn update_removed_for_creator(
35 conn: &mut PgConnection,
36 for_creator_id: PersonId,
38 ) -> Result<Vec<Self>, Error> {
39 use crate::schema::comment::dsl::*;
40 diesel::update(comment.filter(creator_id.eq(for_creator_id)))
41 .set((removed.eq(new_removed), updated.eq(naive_now())))
42 .get_results::<Self>(conn)
46 conn: &mut PgConnection,
47 comment_form: &CommentInsertForm,
48 parent_path: Option<&Ltree>,
49 ) -> Result<Comment, Error> {
50 use crate::schema::comment::dsl::*;
52 // Insert, to get the id
53 let inserted_comment = insert_into(comment)
58 .get_result::<Self>(conn);
60 if let Ok(comment_insert) = inserted_comment {
61 let comment_id = comment_insert.id;
63 // You need to update the ltree column
64 let ltree = Ltree(if let Some(parent_path) = parent_path {
65 // The previous parent will already have 0 in it
66 // Append this comment id
67 format!("{}.{}", parent_path.0, comment_id)
69 // '0' is always the first path, append to that
70 format!("{}.{}", 0, comment_id)
73 let updated_comment = diesel::update(comment.find(comment_id))
75 .get_result::<Self>(conn);
77 // Update the child count for the parent comment_aggregates
78 // You could do this with a trigger, but since you have to do this manually anyway,
79 // you can just have it here
80 if let Some(parent_path) = parent_path {
81 // You have to update counts for all parents, not just the immediate one
82 // TODO if the performance of this is terrible, it might be better to do this as part of a
83 // scheduled query... although the counts would often be wrong.
85 // The child_count query for reference:
86 // select c.id, c.path, count(c2.id) as child_count from comment c
87 // left join comment c2 on c2.path <@ c.path and c2.path != c.path
90 let top_parent = format!("0.{}", parent_path.0.split('.').collect::<Vec<&str>>()[1]);
91 let update_child_count_stmt = format!(
93 update comment_aggregates ca set child_count = c.child_count
95 select c.id, c.path, count(c2.id) as child_count from comment c
96 join comment c2 on c2.path <@ c.path and c2.path != c.path
100 where ca.comment_id = c.id",
104 sql_query(update_child_count_stmt).execute(conn)?;
111 pub fn read_from_apub_id(conn: &mut PgConnection, object_id: Url) -> Result<Option<Self>, Error> {
112 use crate::schema::comment::dsl::*;
113 let object_id: DbUrl = object_id.into();
116 .filter(ap_id.eq(object_id))
117 .first::<Comment>(conn)
123 pub fn parent_comment_id(&self) -> Option<CommentId> {
124 let mut ltree_split: Vec<&str> = self.path.0.split('.').collect();
125 ltree_split.remove(0); // The first is always 0
126 if ltree_split.len() > 1 {
127 ltree_split[ltree_split.len() - 2]
137 impl Crud for Comment {
138 type InsertForm = CommentInsertForm;
139 type UpdateForm = CommentUpdateForm;
140 type IdType = CommentId;
141 fn read(conn: &mut PgConnection, comment_id: CommentId) -> Result<Self, Error> {
142 use crate::schema::comment::dsl::*;
143 comment.find(comment_id).first::<Self>(conn)
146 fn delete(conn: &mut PgConnection, comment_id: CommentId) -> Result<usize, Error> {
147 use crate::schema::comment::dsl::*;
148 diesel::delete(comment.find(comment_id)).execute(conn)
151 /// This is unimplemented, use [[Comment::create]]
152 fn create(_conn: &mut PgConnection, _comment_form: &Self::InsertForm) -> Result<Self, Error> {
157 conn: &mut PgConnection,
158 comment_id: CommentId,
159 comment_form: &Self::UpdateForm,
160 ) -> Result<Self, Error> {
161 use crate::schema::comment::dsl::*;
162 diesel::update(comment.find(comment_id))
164 .get_result::<Self>(conn)
168 impl Likeable for CommentLike {
169 type Form = CommentLikeForm;
170 type IdType = CommentId;
171 fn like(conn: &mut PgConnection, comment_like_form: &CommentLikeForm) -> Result<Self, Error> {
172 use crate::schema::comment_like::dsl::*;
173 insert_into(comment_like)
174 .values(comment_like_form)
175 .on_conflict((comment_id, person_id))
177 .set(comment_like_form)
178 .get_result::<Self>(conn)
181 conn: &mut PgConnection,
183 comment_id: CommentId,
184 ) -> Result<usize, Error> {
185 use crate::schema::comment_like::dsl;
188 .filter(dsl::comment_id.eq(comment_id))
189 .filter(dsl::person_id.eq(person_id)),
195 impl Saveable for CommentSaved {
196 type Form = CommentSavedForm;
197 fn save(conn: &mut PgConnection, comment_saved_form: &CommentSavedForm) -> Result<Self, Error> {
198 use crate::schema::comment_saved::dsl::*;
199 insert_into(comment_saved)
200 .values(comment_saved_form)
201 .on_conflict((comment_id, person_id))
203 .set(comment_saved_form)
204 .get_result::<Self>(conn)
207 conn: &mut PgConnection,
208 comment_saved_form: &CommentSavedForm,
209 ) -> Result<usize, Error> {
210 use crate::schema::comment_saved::dsl::*;
213 .filter(comment_id.eq(comment_saved_form.comment_id))
214 .filter(person_id.eq(comment_saved_form.person_id)),
220 impl DeleteableOrRemoveable for Comment {
221 fn blank_out_deleted_or_removed_info(mut self) -> Self {
222 self.content = "".into();
230 newtypes::LanguageId,
233 community::{Community, CommunityInsertForm},
235 person::{Person, PersonInsertForm},
238 traits::{Crud, Likeable, Saveable},
239 utils::establish_unpooled_connection,
241 use diesel_ltree::Ltree;
242 use serial_test::serial;
247 let conn = &mut establish_unpooled_connection();
249 let inserted_instance = Instance::create(conn, "my_domain.tld").unwrap();
251 let new_person = PersonInsertForm::builder()
252 .name("terry".into())
253 .public_key("pubkey".to_string())
254 .instance_id(inserted_instance.id)
257 let inserted_person = Person::create(conn, &new_person).unwrap();
259 let new_community = CommunityInsertForm::builder()
260 .name("test community".to_string())
261 .title("nada".to_owned())
262 .public_key("pubkey".to_string())
263 .instance_id(inserted_instance.id)
266 let inserted_community = Community::create(conn, &new_community).unwrap();
268 let new_post = PostInsertForm::builder()
269 .name("A test post".into())
270 .creator_id(inserted_person.id)
271 .community_id(inserted_community.id)
274 let inserted_post = Post::create(conn, &new_post).unwrap();
276 let comment_form = CommentInsertForm::builder()
277 .content("A test comment".into())
278 .creator_id(inserted_person.id)
279 .post_id(inserted_post.id)
282 let inserted_comment = Comment::create(conn, &comment_form, None).unwrap();
284 let expected_comment = Comment {
285 id: inserted_comment.id,
286 content: "A test comment".into(),
287 creator_id: inserted_person.id,
288 post_id: inserted_post.id,
291 path: Ltree(format!("0.{}", inserted_comment.id)),
292 published: inserted_comment.published,
294 ap_id: inserted_comment.ap_id.to_owned(),
295 distinguished: false,
297 language_id: LanguageId::default(),
300 let child_comment_form = CommentInsertForm::builder()
301 .content("A child comment".into())
302 .creator_id(inserted_person.id)
303 .post_id(inserted_post.id)
306 let inserted_child_comment =
307 Comment::create(conn, &child_comment_form, Some(&inserted_comment.path)).unwrap();
310 let comment_like_form = CommentLikeForm {
311 comment_id: inserted_comment.id,
312 post_id: inserted_post.id,
313 person_id: inserted_person.id,
317 let inserted_comment_like = CommentLike::like(conn, &comment_like_form).unwrap();
319 let expected_comment_like = CommentLike {
320 id: inserted_comment_like.id,
321 comment_id: inserted_comment.id,
322 post_id: inserted_post.id,
323 person_id: inserted_person.id,
324 published: inserted_comment_like.published,
329 let comment_saved_form = CommentSavedForm {
330 comment_id: inserted_comment.id,
331 person_id: inserted_person.id,
334 let inserted_comment_saved = CommentSaved::save(conn, &comment_saved_form).unwrap();
336 let expected_comment_saved = CommentSaved {
337 id: inserted_comment_saved.id,
338 comment_id: inserted_comment.id,
339 person_id: inserted_person.id,
340 published: inserted_comment_saved.published,
343 let comment_update_form = CommentUpdateForm::builder()
344 .content(Some("A test comment".into()))
347 let updated_comment = Comment::update(conn, inserted_comment.id, &comment_update_form).unwrap();
349 let read_comment = Comment::read(conn, inserted_comment.id).unwrap();
350 let like_removed = CommentLike::remove(conn, inserted_person.id, inserted_comment.id).unwrap();
351 let saved_removed = CommentSaved::unsave(conn, &comment_saved_form).unwrap();
352 let num_deleted = Comment::delete(conn, inserted_comment.id).unwrap();
353 Comment::delete(conn, inserted_child_comment.id).unwrap();
354 Post::delete(conn, inserted_post.id).unwrap();
355 Community::delete(conn, inserted_community.id).unwrap();
356 Person::delete(conn, inserted_person.id).unwrap();
357 Instance::delete(conn, inserted_instance.id).unwrap();
359 assert_eq!(expected_comment, read_comment);
360 assert_eq!(expected_comment, inserted_comment);
361 assert_eq!(expected_comment, updated_comment);
362 assert_eq!(expected_comment_like, inserted_comment_like);
363 assert_eq!(expected_comment_saved, inserted_comment_saved);
365 format!("0.{}.{}", expected_comment.id, inserted_child_comment.id),
366 inserted_child_comment.path.0,
368 assert_eq!(1, like_removed);
369 assert_eq!(1, saved_removed);
370 assert_eq!(1, num_deleted);