]> Untitled Git - lemmy.git/blob - src/code_migrations.rs
Diesel 2.0.0 upgrade (#2452)
[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   *,
6 };
7 use lemmy_apub::{
8   generate_followers_url,
9   generate_inbox_url,
10   generate_local_apub_endpoint,
11   generate_shared_inbox_url,
12   generate_site_inbox_url,
13   EndpointType,
14 };
15 use lemmy_db_schema::{
16   source::{
17     comment::Comment,
18     community::{Community, CommunityForm},
19     person::{Person, PersonForm},
20     post::Post,
21     private_message::PrivateMessage,
22     site::{Site, SiteForm},
23   },
24   traits::Crud,
25   utils::naive_now,
26 };
27 use lemmy_utils::error::LemmyError;
28 use std::default::Default;
29 use tracing::info;
30 use url::Url;
31
32 pub fn run_advanced_migrations(
33   conn: &mut PgConnection,
34   protocol_and_hostname: &str,
35 ) -> Result<(), LemmyError> {
36   user_updates_2020_04_02(conn, protocol_and_hostname)?;
37   community_updates_2020_04_02(conn, protocol_and_hostname)?;
38   post_updates_2020_04_03(conn, protocol_and_hostname)?;
39   comment_updates_2020_04_03(conn, protocol_and_hostname)?;
40   private_message_updates_2020_05_05(conn, protocol_and_hostname)?;
41   post_thumbnail_url_updates_2020_07_27(conn, protocol_and_hostname)?;
42   apub_columns_2021_02_02(conn)?;
43   instance_actor_2022_01_28(conn, protocol_and_hostname)?;
44   regenerate_public_keys_2022_07_05(conn)?;
45
46   Ok(())
47 }
48
49 fn user_updates_2020_04_02(
50   conn: &mut PgConnection,
51   protocol_and_hostname: &str,
52 ) -> Result<(), LemmyError> {
53   use lemmy_db_schema::schema::person::dsl::*;
54
55   info!("Running user_updates_2020_04_02");
56
57   // Update the actor_id, private_key, and public_key, last_refreshed_at
58   let incorrect_persons = person
59     .filter(actor_id.like("http://changeme%"))
60     .filter(local.eq(true))
61     .load::<Person>(conn)?;
62
63   for cperson in &incorrect_persons {
64     let keypair = generate_actor_keypair()?;
65
66     let form = PersonForm {
67       name: cperson.name.to_owned(),
68       actor_id: Some(generate_local_apub_endpoint(
69         EndpointType::Person,
70         &cperson.name,
71         protocol_and_hostname,
72       )?),
73       private_key: Some(Some(keypair.private_key)),
74       public_key: Some(keypair.public_key),
75       last_refreshed_at: Some(naive_now()),
76       ..PersonForm::default()
77     };
78
79     Person::update(conn, cperson.id, &form)?;
80   }
81
82   info!("{} person rows updated.", incorrect_persons.len());
83
84   Ok(())
85 }
86
87 fn community_updates_2020_04_02(
88   conn: &mut PgConnection,
89   protocol_and_hostname: &str,
90 ) -> Result<(), LemmyError> {
91   use lemmy_db_schema::schema::community::dsl::*;
92
93   info!("Running community_updates_2020_04_02");
94
95   // Update the actor_id, private_key, and public_key, last_refreshed_at
96   let incorrect_communities = community
97     .filter(actor_id.like("http://changeme%"))
98     .filter(local.eq(true))
99     .load::<Community>(conn)?;
100
101   for ccommunity in &incorrect_communities {
102     let keypair = generate_actor_keypair()?;
103     let community_actor_id = generate_local_apub_endpoint(
104       EndpointType::Community,
105       &ccommunity.name,
106       protocol_and_hostname,
107     )?;
108
109     let form = CommunityForm {
110       name: ccommunity.name.to_owned(),
111       title: ccommunity.title.to_owned(),
112       description: Some(ccommunity.description.to_owned()),
113       hidden: Some(false),
114       actor_id: Some(community_actor_id.to_owned()),
115       local: Some(ccommunity.local),
116       private_key: Some(Some(keypair.private_key)),
117       public_key: Some(keypair.public_key),
118       last_refreshed_at: Some(naive_now()),
119       icon: Some(ccommunity.icon.to_owned()),
120       banner: Some(ccommunity.banner.to_owned()),
121       ..Default::default()
122     };
123
124     Community::update(conn, ccommunity.id, &form)?;
125   }
126
127   info!("{} community rows updated.", incorrect_communities.len());
128
129   Ok(())
130 }
131
132 fn post_updates_2020_04_03(
133   conn: &mut PgConnection,
134   protocol_and_hostname: &str,
135 ) -> Result<(), LemmyError> {
136   use lemmy_db_schema::schema::post::dsl::*;
137
138   info!("Running post_updates_2020_04_03");
139
140   // Update the ap_id
141   let incorrect_posts = post
142     .filter(ap_id.like("http://changeme%"))
143     .filter(local.eq(true))
144     .load::<Post>(conn)?;
145
146   for cpost in &incorrect_posts {
147     let apub_id = generate_local_apub_endpoint(
148       EndpointType::Post,
149       &cpost.id.to_string(),
150       protocol_and_hostname,
151     )?;
152     Post::update_ap_id(conn, cpost.id, apub_id)?;
153   }
154
155   info!("{} post rows updated.", incorrect_posts.len());
156
157   Ok(())
158 }
159
160 fn comment_updates_2020_04_03(
161   conn: &mut PgConnection,
162   protocol_and_hostname: &str,
163 ) -> Result<(), LemmyError> {
164   use lemmy_db_schema::schema::comment::dsl::*;
165
166   info!("Running comment_updates_2020_04_03");
167
168   // Update the ap_id
169   let incorrect_comments = comment
170     .filter(ap_id.like("http://changeme%"))
171     .filter(local.eq(true))
172     .load::<Comment>(conn)?;
173
174   for ccomment in &incorrect_comments {
175     let apub_id = generate_local_apub_endpoint(
176       EndpointType::Comment,
177       &ccomment.id.to_string(),
178       protocol_and_hostname,
179     )?;
180     Comment::update_ap_id(conn, ccomment.id, apub_id)?;
181   }
182
183   info!("{} comment rows updated.", incorrect_comments.len());
184
185   Ok(())
186 }
187
188 fn private_message_updates_2020_05_05(
189   conn: &mut PgConnection,
190   protocol_and_hostname: &str,
191 ) -> Result<(), LemmyError> {
192   use lemmy_db_schema::schema::private_message::dsl::*;
193
194   info!("Running private_message_updates_2020_05_05");
195
196   // Update the ap_id
197   let incorrect_pms = private_message
198     .filter(ap_id.like("http://changeme%"))
199     .filter(local.eq(true))
200     .load::<PrivateMessage>(conn)?;
201
202   for cpm in &incorrect_pms {
203     let apub_id = generate_local_apub_endpoint(
204       EndpointType::PrivateMessage,
205       &cpm.id.to_string(),
206       protocol_and_hostname,
207     )?;
208     PrivateMessage::update_ap_id(conn, cpm.id, apub_id)?;
209   }
210
211   info!("{} private message rows updated.", incorrect_pms.len());
212
213   Ok(())
214 }
215
216 fn post_thumbnail_url_updates_2020_07_27(
217   conn: &mut PgConnection,
218   protocol_and_hostname: &str,
219 ) -> Result<(), LemmyError> {
220   use lemmy_db_schema::schema::post::dsl::*;
221
222   info!("Running post_thumbnail_url_updates_2020_07_27");
223
224   let domain_prefix = format!("{}/pictrs/image/", protocol_and_hostname,);
225
226   let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%"));
227
228   // Prepend the rows with the update
229   let res = diesel::update(incorrect_thumbnails)
230     .set(
231       thumbnail_url.eq(
232         domain_prefix
233           .into_sql::<Nullable<Text>>()
234           .concat(thumbnail_url),
235       ),
236     )
237     .get_results::<Post>(conn)?;
238
239   info!("{} Post thumbnail_url rows updated.", res.len());
240
241   Ok(())
242 }
243
244 /// We are setting inbox and follower URLs for local and remote actors alike, because for now
245 /// all federated instances are also Lemmy and use the same URL scheme.
246 fn apub_columns_2021_02_02(conn: &mut PgConnection) -> Result<(), LemmyError> {
247   info!("Running apub_columns_2021_02_02");
248   {
249     use lemmy_db_schema::schema::person::dsl::*;
250     let persons = person
251       .filter(inbox_url.like("http://changeme%"))
252       .load::<Person>(conn)?;
253
254     for p in &persons {
255       let inbox_url_ = generate_inbox_url(&p.actor_id)?;
256       let shared_inbox_url_ = generate_shared_inbox_url(&p.actor_id)?;
257       diesel::update(person.find(p.id))
258         .set((
259           inbox_url.eq(inbox_url_),
260           shared_inbox_url.eq(shared_inbox_url_),
261         ))
262         .get_result::<Person>(conn)?;
263     }
264   }
265
266   {
267     use lemmy_db_schema::schema::community::dsl::*;
268     let communities = community
269       .filter(inbox_url.like("http://changeme%"))
270       .load::<Community>(conn)?;
271
272     for c in &communities {
273       let followers_url_ = generate_followers_url(&c.actor_id)?;
274       let inbox_url_ = generate_inbox_url(&c.actor_id)?;
275       let shared_inbox_url_ = generate_shared_inbox_url(&c.actor_id)?;
276       diesel::update(community.find(c.id))
277         .set((
278           followers_url.eq(followers_url_),
279           inbox_url.eq(inbox_url_),
280           shared_inbox_url.eq(shared_inbox_url_),
281         ))
282         .get_result::<Community>(conn)?;
283     }
284   }
285
286   Ok(())
287 }
288
289 /// Site object turns into an actor, so that things like instance description can be federated. This
290 /// means we need to add actor columns to the site table, and initialize them with correct values.
291 /// Before this point, there is only a single value in the site table which refers to the local
292 /// Lemmy instance, so thats all we need to update.
293 fn instance_actor_2022_01_28(
294   conn: &mut PgConnection,
295   protocol_and_hostname: &str,
296 ) -> Result<(), LemmyError> {
297   info!("Running instance_actor_2021_09_29");
298   if let Ok(site) = Site::read_local_site(conn) {
299     // if site already has public key, we dont need to do anything here
300     if !site.public_key.is_empty() {
301       return Ok(());
302     }
303     let key_pair = generate_actor_keypair()?;
304     let actor_id = Url::parse(protocol_and_hostname)?;
305     let site_form = SiteForm {
306       name: site.name,
307       actor_id: Some(actor_id.clone().into()),
308       last_refreshed_at: Some(naive_now()),
309       inbox_url: Some(generate_site_inbox_url(&actor_id.into())?),
310       private_key: Some(Some(key_pair.private_key)),
311       public_key: Some(key_pair.public_key),
312       ..Default::default()
313     };
314     Site::update(conn, site.id, &site_form)?;
315   }
316   Ok(())
317 }
318
319 /// Fix for bug #2347, which can result in community/person public keys being overwritten with
320 /// empty string when the database value is updated. We go through all actors, and if the public
321 /// key field is empty, generate a new keypair. It would be possible to regenerate only the pubkey,
322 /// but thats more complicated and has no benefit, as federation is already broken for these actors.
323 /// https://github.com/LemmyNet/lemmy/issues/2347
324 fn regenerate_public_keys_2022_07_05(conn: &mut PgConnection) -> Result<(), LemmyError> {
325   info!("Running regenerate_public_keys_2022_07_05");
326
327   {
328     // update communities with empty pubkey
329     use lemmy_db_schema::schema::community::dsl::*;
330     let communities: Vec<Community> = community
331       .filter(local.eq(true))
332       .filter(public_key.eq(""))
333       .load::<Community>(conn)?;
334     for community_ in communities {
335       info!(
336         "local community {} has empty public key field, regenerating key",
337         community_.name
338       );
339       let key_pair = generate_actor_keypair()?;
340       let form = CommunityForm {
341         name: community_.name,
342         title: community_.title,
343         public_key: Some(key_pair.public_key),
344         private_key: Some(Some(key_pair.private_key)),
345         ..Default::default()
346       };
347       Community::update(conn, community_.id, &form)?;
348     }
349   }
350
351   {
352     // update persons with empty pubkey
353     use lemmy_db_schema::schema::person::dsl::*;
354     let persons = person
355       .filter(local.eq(true))
356       .filter(public_key.eq(""))
357       .load::<Person>(conn)?;
358     for person_ in persons {
359       info!(
360         "local user {} has empty public key field, regenerating key",
361         person_.name
362       );
363       let key_pair = generate_actor_keypair()?;
364       let form = PersonForm {
365         name: person_.name,
366         public_key: Some(key_pair.public_key),
367         private_key: Some(Some(key_pair.private_key)),
368         ..Default::default()
369       };
370       Person::update(conn, person_.id, &form)?;
371     }
372   }
373   Ok(())
374 }