1 // This is for db migrations that require code
2 use activitypub_federation::core::signatures::generate_actor_keypair;
4 sql_types::{Nullable, Text},
8 generate_followers_url,
10 generate_local_apub_endpoint,
11 generate_shared_inbox_url,
12 generate_site_inbox_url,
15 use lemmy_db_schema::{
18 community::{Community, CommunityForm},
19 person::{Person, PersonForm},
21 private_message::PrivateMessage,
22 site::{Site, SiteForm},
27 use lemmy_utils::error::LemmyError;
28 use std::default::Default;
32 pub fn run_advanced_migrations(
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)?;
49 fn user_updates_2020_04_02(
51 protocol_and_hostname: &str,
52 ) -> Result<(), LemmyError> {
53 use lemmy_db_schema::schema::person::dsl::*;
55 info!("Running user_updates_2020_04_02");
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)?;
63 for cperson in &incorrect_persons {
64 let keypair = generate_actor_keypair()?;
66 let form = PersonForm {
67 name: cperson.name.to_owned(),
68 actor_id: Some(generate_local_apub_endpoint(
71 protocol_and_hostname,
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()
79 Person::update(conn, cperson.id, &form)?;
82 info!("{} person rows updated.", incorrect_persons.len());
87 fn community_updates_2020_04_02(
89 protocol_and_hostname: &str,
90 ) -> Result<(), LemmyError> {
91 use lemmy_db_schema::schema::community::dsl::*;
93 info!("Running community_updates_2020_04_02");
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)?;
101 for ccommunity in &incorrect_communities {
102 let keypair = generate_actor_keypair()?;
103 let community_actor_id = generate_local_apub_endpoint(
104 EndpointType::Community,
106 protocol_and_hostname,
109 let form = CommunityForm {
110 name: ccommunity.name.to_owned(),
111 title: ccommunity.title.to_owned(),
112 description: ccommunity.description.to_owned(),
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()),
124 Community::update(conn, ccommunity.id, &form)?;
127 info!("{} community rows updated.", incorrect_communities.len());
132 fn post_updates_2020_04_03(
134 protocol_and_hostname: &str,
135 ) -> Result<(), LemmyError> {
136 use lemmy_db_schema::schema::post::dsl::*;
138 info!("Running post_updates_2020_04_03");
141 let incorrect_posts = post
142 .filter(ap_id.like("http://changeme%"))
143 .filter(local.eq(true))
144 .load::<Post>(conn)?;
146 for cpost in &incorrect_posts {
147 let apub_id = generate_local_apub_endpoint(
149 &cpost.id.to_string(),
150 protocol_and_hostname,
152 Post::update_ap_id(conn, cpost.id, apub_id)?;
155 info!("{} post rows updated.", incorrect_posts.len());
160 fn comment_updates_2020_04_03(
162 protocol_and_hostname: &str,
163 ) -> Result<(), LemmyError> {
164 use lemmy_db_schema::schema::comment::dsl::*;
166 info!("Running comment_updates_2020_04_03");
169 let incorrect_comments = comment
170 .filter(ap_id.like("http://changeme%"))
171 .filter(local.eq(true))
172 .load::<Comment>(conn)?;
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,
180 Comment::update_ap_id(conn, ccomment.id, apub_id)?;
183 info!("{} comment rows updated.", incorrect_comments.len());
188 fn private_message_updates_2020_05_05(
190 protocol_and_hostname: &str,
191 ) -> Result<(), LemmyError> {
192 use lemmy_db_schema::schema::private_message::dsl::*;
194 info!("Running private_message_updates_2020_05_05");
197 let incorrect_pms = private_message
198 .filter(ap_id.like("http://changeme%"))
199 .filter(local.eq(true))
200 .load::<PrivateMessage>(conn)?;
202 for cpm in &incorrect_pms {
203 let apub_id = generate_local_apub_endpoint(
204 EndpointType::PrivateMessage,
206 protocol_and_hostname,
208 PrivateMessage::update_ap_id(conn, cpm.id, apub_id)?;
211 info!("{} private message rows updated.", incorrect_pms.len());
216 fn post_thumbnail_url_updates_2020_07_27(
218 protocol_and_hostname: &str,
219 ) -> Result<(), LemmyError> {
220 use lemmy_db_schema::schema::post::dsl::*;
222 info!("Running post_thumbnail_url_updates_2020_07_27");
224 let domain_prefix = format!("{}/pictrs/image/", protocol_and_hostname,);
226 let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%"));
228 // Prepend the rows with the update
229 let res = diesel::update(incorrect_thumbnails)
233 .into_sql::<Nullable<Text>>()
234 .concat(thumbnail_url),
237 .get_results::<Post>(conn)?;
239 info!("{} Post thumbnail_url rows updated.", res.len());
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: &PgConnection) -> Result<(), LemmyError> {
247 info!("Running apub_columns_2021_02_02");
249 use lemmy_db_schema::schema::person::dsl::*;
251 .filter(inbox_url.like("http://changeme%"))
252 .load::<Person>(conn)?;
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))
259 inbox_url.eq(inbox_url_),
260 shared_inbox_url.eq(shared_inbox_url_),
262 .get_result::<Person>(conn)?;
267 use lemmy_db_schema::schema::community::dsl::*;
268 let communities = community
269 .filter(inbox_url.like("http://changeme%"))
270 .load::<Community>(conn)?;
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))
278 followers_url.eq(followers_url_),
279 inbox_url.eq(inbox_url_),
280 shared_inbox_url.eq(shared_inbox_url_),
282 .get_result::<Community>(conn)?;
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(
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() {
303 let key_pair = generate_actor_keypair()?;
304 let actor_id = Url::parse(protocol_and_hostname)?;
305 let site_form = SiteForm {
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),
314 Site::update(conn, site.id, &site_form)?;
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: &PgConnection) -> Result<(), LemmyError> {
325 info!("Running regenerate_public_keys_2022_07_05");
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 {
336 "local community {} has empty public key field, regenerating key",
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)),
347 Community::update(conn, community_.id, &form)?;
352 // update persons with empty pubkey
353 use lemmy_db_schema::schema::person::dsl::*;
355 .filter(local.eq(true))
356 .filter(public_key.eq(""))
357 .load::<Person>(conn)?;
358 for person_ in persons {
360 "local user {} has empty public key field, regenerating key",
363 let key_pair = generate_actor_keypair()?;
364 let form = PersonForm {
366 public_key: Some(key_pair.public_key),
367 private_key: Some(Some(key_pair.private_key)),
370 Person::update(conn, person_.id, &form)?;