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