]> Untitled Git - lemmy.git/blob - src/code_migrations.rs
Merge branch 'vijaykramesh_clear_deleted_posts_comments'
[lemmy.git] / src / code_migrations.rs
1 // This is for db migrations that require code
2 use activitypub_federation::http_signatures::generate_actor_keypair;
3 use diesel::{
4   sql_types::{Nullable, Text},
5   ExpressionMethods,
6   IntoSql,
7   QueryDsl,
8   TextExpressionMethods,
9 };
10 use diesel_async::RunQueryDsl;
11 use lemmy_api_common::{
12   lemmy_db_views::structs::SiteView,
13   utils::{
14     generate_followers_url,
15     generate_inbox_url,
16     generate_local_apub_endpoint,
17     generate_shared_inbox_url,
18     generate_site_inbox_url,
19     EndpointType,
20   },
21 };
22 use lemmy_db_schema::{
23   source::{
24     comment::{Comment, CommentUpdateForm},
25     community::{Community, CommunityUpdateForm},
26     instance::Instance,
27     local_site::{LocalSite, LocalSiteInsertForm},
28     local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitInsertForm},
29     local_user::{LocalUser, LocalUserInsertForm},
30     person::{Person, PersonInsertForm, PersonUpdateForm},
31     post::{Post, PostUpdateForm},
32     private_message::{PrivateMessage, PrivateMessageUpdateForm},
33     site::{Site, SiteInsertForm, SiteUpdateForm},
34   },
35   traits::Crud,
36   utils::{get_conn, naive_now, DbPool},
37 };
38 use lemmy_utils::{error::LemmyError, settings::structs::Settings};
39 use tracing::info;
40 use url::Url;
41
42 pub async fn run_advanced_migrations(pool: &DbPool, settings: &Settings) -> Result<(), LemmyError> {
43   let protocol_and_hostname = &settings.get_protocol_and_hostname();
44   user_updates_2020_04_02(pool, protocol_and_hostname).await?;
45   community_updates_2020_04_02(pool, protocol_and_hostname).await?;
46   post_updates_2020_04_03(pool, protocol_and_hostname).await?;
47   comment_updates_2020_04_03(pool, protocol_and_hostname).await?;
48   private_message_updates_2020_05_05(pool, protocol_and_hostname).await?;
49   post_thumbnail_url_updates_2020_07_27(pool, protocol_and_hostname).await?;
50   apub_columns_2021_02_02(pool).await?;
51   instance_actor_2022_01_28(pool, protocol_and_hostname).await?;
52   regenerate_public_keys_2022_07_05(pool).await?;
53   initialize_local_site_2022_10_10(pool, settings).await?;
54
55   Ok(())
56 }
57
58 async fn user_updates_2020_04_02(
59   pool: &DbPool,
60   protocol_and_hostname: &str,
61 ) -> Result<(), LemmyError> {
62   use lemmy_db_schema::schema::person::dsl::{actor_id, local, person};
63   let conn = &mut get_conn(pool).await?;
64
65   info!("Running user_updates_2020_04_02");
66
67   // Update the actor_id, private_key, and public_key, last_refreshed_at
68   let incorrect_persons = person
69     .filter(actor_id.like("http://changeme%"))
70     .filter(local.eq(true))
71     .load::<Person>(conn)
72     .await?;
73
74   for cperson in &incorrect_persons {
75     let keypair = generate_actor_keypair()?;
76
77     let form = PersonUpdateForm::builder()
78       .actor_id(Some(generate_local_apub_endpoint(
79         EndpointType::Person,
80         &cperson.name,
81         protocol_and_hostname,
82       )?))
83       .private_key(Some(Some(keypair.private_key)))
84       .public_key(Some(keypair.public_key))
85       .last_refreshed_at(Some(naive_now()))
86       .build();
87
88     Person::update(pool, cperson.id, &form).await?;
89   }
90
91   info!("{} person rows updated.", incorrect_persons.len());
92
93   Ok(())
94 }
95
96 async fn community_updates_2020_04_02(
97   pool: &DbPool,
98   protocol_and_hostname: &str,
99 ) -> Result<(), LemmyError> {
100   use lemmy_db_schema::schema::community::dsl::{actor_id, community, local};
101   let conn = &mut get_conn(pool).await?;
102
103   info!("Running community_updates_2020_04_02");
104
105   // Update the actor_id, private_key, and public_key, last_refreshed_at
106   let incorrect_communities = community
107     .filter(actor_id.like("http://changeme%"))
108     .filter(local.eq(true))
109     .load::<Community>(conn)
110     .await?;
111
112   for ccommunity in &incorrect_communities {
113     let keypair = generate_actor_keypair()?;
114     let community_actor_id = generate_local_apub_endpoint(
115       EndpointType::Community,
116       &ccommunity.name,
117       protocol_and_hostname,
118     )?;
119
120     let form = CommunityUpdateForm::builder()
121       .actor_id(Some(community_actor_id.clone()))
122       .private_key(Some(Some(keypair.private_key)))
123       .public_key(Some(keypair.public_key))
124       .last_refreshed_at(Some(naive_now()))
125       .build();
126
127     Community::update(pool, ccommunity.id, &form).await?;
128   }
129
130   info!("{} community rows updated.", incorrect_communities.len());
131
132   Ok(())
133 }
134
135 async fn post_updates_2020_04_03(
136   pool: &DbPool,
137   protocol_and_hostname: &str,
138 ) -> Result<(), LemmyError> {
139   use lemmy_db_schema::schema::post::dsl::{ap_id, local, post};
140   let conn = &mut get_conn(pool).await?;
141
142   info!("Running post_updates_2020_04_03");
143
144   // Update the ap_id
145   let incorrect_posts = post
146     .filter(ap_id.like("http://changeme%"))
147     .filter(local.eq(true))
148     .load::<Post>(conn)
149     .await?;
150
151   for cpost in &incorrect_posts {
152     let apub_id = generate_local_apub_endpoint(
153       EndpointType::Post,
154       &cpost.id.to_string(),
155       protocol_and_hostname,
156     )?;
157     Post::update(
158       pool,
159       cpost.id,
160       &PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
161     )
162     .await?;
163   }
164
165   info!("{} post rows updated.", incorrect_posts.len());
166
167   Ok(())
168 }
169
170 async fn comment_updates_2020_04_03(
171   pool: &DbPool,
172   protocol_and_hostname: &str,
173 ) -> Result<(), LemmyError> {
174   use lemmy_db_schema::schema::comment::dsl::{ap_id, comment, local};
175   let conn = &mut get_conn(pool).await?;
176
177   info!("Running comment_updates_2020_04_03");
178
179   // Update the ap_id
180   let incorrect_comments = comment
181     .filter(ap_id.like("http://changeme%"))
182     .filter(local.eq(true))
183     .load::<Comment>(conn)
184     .await?;
185
186   for ccomment in &incorrect_comments {
187     let apub_id = generate_local_apub_endpoint(
188       EndpointType::Comment,
189       &ccomment.id.to_string(),
190       protocol_and_hostname,
191     )?;
192     Comment::update(
193       pool,
194       ccomment.id,
195       &CommentUpdateForm::builder().ap_id(Some(apub_id)).build(),
196     )
197     .await?;
198   }
199
200   info!("{} comment rows updated.", incorrect_comments.len());
201
202   Ok(())
203 }
204
205 async fn private_message_updates_2020_05_05(
206   pool: &DbPool,
207   protocol_and_hostname: &str,
208 ) -> Result<(), LemmyError> {
209   use lemmy_db_schema::schema::private_message::dsl::{ap_id, local, private_message};
210   let conn = &mut get_conn(pool).await?;
211
212   info!("Running private_message_updates_2020_05_05");
213
214   // Update the ap_id
215   let incorrect_pms = private_message
216     .filter(ap_id.like("http://changeme%"))
217     .filter(local.eq(true))
218     .load::<PrivateMessage>(conn)
219     .await?;
220
221   for cpm in &incorrect_pms {
222     let apub_id = generate_local_apub_endpoint(
223       EndpointType::PrivateMessage,
224       &cpm.id.to_string(),
225       protocol_and_hostname,
226     )?;
227     PrivateMessage::update(
228       pool,
229       cpm.id,
230       &PrivateMessageUpdateForm::builder()
231         .ap_id(Some(apub_id))
232         .build(),
233     )
234     .await?;
235   }
236
237   info!("{} private message rows updated.", incorrect_pms.len());
238
239   Ok(())
240 }
241
242 async fn post_thumbnail_url_updates_2020_07_27(
243   pool: &DbPool,
244   protocol_and_hostname: &str,
245 ) -> Result<(), LemmyError> {
246   use lemmy_db_schema::schema::post::dsl::{post, thumbnail_url};
247   let conn = &mut get_conn(pool).await?;
248
249   info!("Running post_thumbnail_url_updates_2020_07_27");
250
251   let domain_prefix = format!("{protocol_and_hostname}/pictrs/image/",);
252
253   let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%"));
254
255   // Prepend the rows with the update
256   let res = diesel::update(incorrect_thumbnails)
257     .set(
258       thumbnail_url.eq(
259         domain_prefix
260           .into_sql::<Nullable<Text>>()
261           .concat(thumbnail_url),
262       ),
263     )
264     .get_results::<Post>(conn)
265     .await?;
266
267   info!("{} Post thumbnail_url rows updated.", res.len());
268
269   Ok(())
270 }
271
272 /// We are setting inbox and follower URLs for local and remote actors alike, because for now
273 /// all federated instances are also Lemmy and use the same URL scheme.
274 async fn apub_columns_2021_02_02(pool: &DbPool) -> Result<(), LemmyError> {
275   let conn = &mut get_conn(pool).await?;
276   info!("Running apub_columns_2021_02_02");
277   {
278     use lemmy_db_schema::schema::person::dsl::{inbox_url, person, shared_inbox_url};
279     let persons = person
280       .filter(inbox_url.like("http://changeme%"))
281       .load::<Person>(conn)
282       .await?;
283
284     for p in &persons {
285       let inbox_url_ = generate_inbox_url(&p.actor_id)?;
286       let shared_inbox_url_ = generate_shared_inbox_url(&p.actor_id)?;
287       diesel::update(person.find(p.id))
288         .set((
289           inbox_url.eq(inbox_url_),
290           shared_inbox_url.eq(shared_inbox_url_),
291         ))
292         .get_result::<Person>(conn)
293         .await?;
294     }
295   }
296
297   {
298     use lemmy_db_schema::schema::community::dsl::{
299       community,
300       followers_url,
301       inbox_url,
302       shared_inbox_url,
303     };
304     let communities = community
305       .filter(inbox_url.like("http://changeme%"))
306       .load::<Community>(conn)
307       .await?;
308
309     for c in &communities {
310       let followers_url_ = generate_followers_url(&c.actor_id)?;
311       let inbox_url_ = generate_inbox_url(&c.actor_id)?;
312       let shared_inbox_url_ = generate_shared_inbox_url(&c.actor_id)?;
313       diesel::update(community.find(c.id))
314         .set((
315           followers_url.eq(followers_url_),
316           inbox_url.eq(inbox_url_),
317           shared_inbox_url.eq(shared_inbox_url_),
318         ))
319         .get_result::<Community>(conn)
320         .await?;
321     }
322   }
323
324   Ok(())
325 }
326
327 /// Site object turns into an actor, so that things like instance description can be federated. This
328 /// means we need to add actor columns to the site table, and initialize them with correct values.
329 /// Before this point, there is only a single value in the site table which refers to the local
330 /// Lemmy instance, so thats all we need to update.
331 async fn instance_actor_2022_01_28(
332   pool: &DbPool,
333   protocol_and_hostname: &str,
334 ) -> Result<(), LemmyError> {
335   info!("Running instance_actor_2021_09_29");
336   if let Ok(site_view) = SiteView::read_local(pool).await {
337     let site = site_view.site;
338     // if site already has public key, we dont need to do anything here
339     if !site.public_key.is_empty() {
340       return Ok(());
341     }
342     let key_pair = generate_actor_keypair()?;
343     let actor_id = Url::parse(protocol_and_hostname)?;
344     let site_form = SiteUpdateForm::builder()
345       .actor_id(Some(actor_id.clone().into()))
346       .last_refreshed_at(Some(naive_now()))
347       .inbox_url(Some(generate_site_inbox_url(&actor_id.into())?))
348       .private_key(Some(Some(key_pair.private_key)))
349       .public_key(Some(key_pair.public_key))
350       .build();
351     Site::update(pool, site.id, &site_form).await?;
352   }
353   Ok(())
354 }
355
356 /// Fix for bug #2347, which can result in community/person public keys being overwritten with
357 /// empty string when the database value is updated. We go through all actors, and if the public
358 /// key field is empty, generate a new keypair. It would be possible to regenerate only the pubkey,
359 /// but thats more complicated and has no benefit, as federation is already broken for these actors.
360 /// https://github.com/LemmyNet/lemmy/issues/2347
361 async fn regenerate_public_keys_2022_07_05(pool: &DbPool) -> Result<(), LemmyError> {
362   let conn = &mut get_conn(pool).await?;
363   info!("Running regenerate_public_keys_2022_07_05");
364
365   {
366     // update communities with empty pubkey
367     use lemmy_db_schema::schema::community::dsl::{community, local, public_key};
368     let communities: Vec<Community> = community
369       .filter(local.eq(true))
370       .filter(public_key.eq(""))
371       .load::<Community>(conn)
372       .await?;
373     for community_ in communities {
374       info!(
375         "local community {} has empty public key field, regenerating key",
376         community_.name
377       );
378       let key_pair = generate_actor_keypair()?;
379       let form = CommunityUpdateForm::builder()
380         .public_key(Some(key_pair.public_key))
381         .private_key(Some(Some(key_pair.private_key)))
382         .build();
383       Community::update(pool, community_.id, &form).await?;
384     }
385   }
386
387   {
388     // update persons with empty pubkey
389     use lemmy_db_schema::schema::person::dsl::{local, person, public_key};
390     let persons = person
391       .filter(local.eq(true))
392       .filter(public_key.eq(""))
393       .load::<Person>(conn)
394       .await?;
395     for person_ in persons {
396       info!(
397         "local user {} has empty public key field, regenerating key",
398         person_.name
399       );
400       let key_pair = generate_actor_keypair()?;
401       let form = PersonUpdateForm::builder()
402         .public_key(Some(key_pair.public_key))
403         .private_key(Some(Some(key_pair.private_key)))
404         .build();
405       Person::update(pool, person_.id, &form).await?;
406     }
407   }
408   Ok(())
409 }
410
411 /// This ensures that your local site is initialized and exists.
412 ///
413 /// If a site already exists, the DB migration should generate a local_site row.
414 /// This will only be run for brand new sites.
415 async fn initialize_local_site_2022_10_10(
416   pool: &DbPool,
417   settings: &Settings,
418 ) -> Result<(), LemmyError> {
419   info!("Running initialize_local_site_2022_10_10");
420
421   // Check to see if local_site exists
422   if LocalSite::read(pool).await.is_ok() {
423     return Ok(());
424   }
425   info!("No Local Site found, creating it.");
426
427   let domain = settings
428     .get_hostname_without_port()
429     .expect("must have domain");
430
431   // Upsert this to the instance table
432   let instance = Instance::read_or_create(pool, domain).await?;
433
434   if let Some(setup) = &settings.setup {
435     let person_keypair = generate_actor_keypair()?;
436     let person_actor_id = generate_local_apub_endpoint(
437       EndpointType::Person,
438       &setup.admin_username,
439       &settings.get_protocol_and_hostname(),
440     )?;
441
442     // Register the user if there's a site setup
443     let person_form = PersonInsertForm::builder()
444       .name(setup.admin_username.clone())
445       .admin(Some(true))
446       .instance_id(instance.id)
447       .actor_id(Some(person_actor_id.clone()))
448       .private_key(Some(person_keypair.private_key))
449       .public_key(person_keypair.public_key)
450       .inbox_url(Some(generate_inbox_url(&person_actor_id)?))
451       .shared_inbox_url(Some(generate_shared_inbox_url(&person_actor_id)?))
452       .build();
453     let person_inserted = Person::create(pool, &person_form).await?;
454
455     let local_user_form = LocalUserInsertForm::builder()
456       .person_id(person_inserted.id)
457       .password_encrypted(setup.admin_password.clone())
458       .email(setup.admin_email.clone())
459       .build();
460     LocalUser::create(pool, &local_user_form).await?;
461   };
462
463   // Add an entry for the site table
464   let site_key_pair = generate_actor_keypair()?;
465   let site_actor_id = Url::parse(&settings.get_protocol_and_hostname())?;
466
467   let site_form = SiteInsertForm::builder()
468     .name(
469       settings
470         .setup
471         .clone()
472         .map(|s| s.site_name)
473         .unwrap_or_else(|| "New Site".to_string()),
474     )
475     .instance_id(instance.id)
476     .actor_id(Some(site_actor_id.clone().into()))
477     .last_refreshed_at(Some(naive_now()))
478     .inbox_url(Some(generate_site_inbox_url(&site_actor_id.into())?))
479     .private_key(Some(site_key_pair.private_key))
480     .public_key(Some(site_key_pair.public_key))
481     .build();
482   let site = Site::create(pool, &site_form).await?;
483
484   // Finally create the local_site row
485   let local_site_form = LocalSiteInsertForm::builder()
486     .site_id(site.id)
487     .site_setup(Some(settings.setup.is_some()))
488     .build();
489   let local_site = LocalSite::create(pool, &local_site_form).await?;
490
491   // Create the rate limit table
492   let local_site_rate_limit_form = LocalSiteRateLimitInsertForm::builder()
493     // TODO these have to be set, because the database defaults are too low for the federation
494     // tests to pass, and there's no way to live update the rate limits without restarting the
495     // server.
496     // This can be removed once live rate limits are enabled.
497     .message(Some(999))
498     .post(Some(999))
499     .register(Some(999))
500     .image(Some(999))
501     .comment(Some(999))
502     .search(Some(999))
503     .local_site_id(local_site.id)
504     .build();
505   LocalSiteRateLimit::create(pool, &local_site_rate_limit_form).await?;
506
507   Ok(())
508 }