tuwunel_service/migrations/
migrate_profile_keys.rs1use ruma::{UserId, profile::ProfileFieldName};
2use serde::de::IgnoredAny;
3use tuwunel_core::{
4 Result, info,
5 utils::{ReadyExt, stream::TryExpect},
6 warn,
7};
8use tuwunel_database::Json;
9
10use crate::Services;
11
12pub(super) async fn migrate_profile_keys(services: &Services) -> Result {
19 let db = &services.db;
20 let cork = db.cork_and_sync();
21
22 let userid_displayname = db["userid_displayname"].clone();
23 let userid_avatarurl = db["userid_avatarurl"].clone();
24 let userid_blurhash = db["userid_blurhash"].clone();
25 let useridprofilekey_value = db["useridprofilekey_value"].clone();
26
27 warn!(
28 "Relocating displaynames, avatar_urls and blurhashes into the unified profile-key store"
29 );
30
31 let displaynames = userid_displayname
32 .stream()
33 .expect_ok()
34 .ready_fold(0_usize, |count, (user_id, displayname): (&UserId, &str)| {
35 let key = (user_id, ProfileFieldName::DisplayName.as_str());
36 let value = displayname.to_owned();
37
38 useridprofilekey_value.put(key, Json(value));
39
40 count.saturating_add(1)
41 })
42 .await;
43
44 let avatar_urls = userid_avatarurl
45 .stream()
46 .expect_ok()
47 .ready_fold(0_usize, |count, (user_id, avatar_url): (&UserId, &str)| {
48 let key = (user_id, ProfileFieldName::AvatarUrl.as_str());
49 let value = avatar_url.to_owned();
50
51 useridprofilekey_value.put(key, Json(value));
52
53 count.saturating_add(1)
54 })
55 .await;
56
57 let blurhashes = userid_blurhash
58 .stream()
59 .expect_ok()
60 .ready_fold(0_usize, |count, (user_id, blurhash): (&UserId, &str)| {
61 let key = (user_id, "xyz.amorgan.blurhash");
62 let value = blurhash.to_owned();
63
64 useridprofilekey_value.put(key, Json(value));
65
66 count.saturating_add(1)
67 })
68 .await;
69
70 let fixed_strings = useridprofilekey_value
71 .raw_stream()
72 .expect_ok()
73 .ready_fold(0_usize, |count, (key, value)| {
74 if serde_json::from_slice::<IgnoredAny>(value).is_err() {
75 let Ok(string) = str::from_utf8(value) else {
76 warn!("Non-UTF8 data in profile value: {key:?} => {value:?}");
77 useridprofilekey_value.remove(key);
78 return count;
79 };
80 useridprofilekey_value.raw_put(key, Json(string));
81 return count.saturating_add(1);
82 }
83
84 count
85 })
86 .await;
87
88 drop(cork);
89 info!(%displaynames, %avatar_urls, %blurhashes, %fixed_strings, "Relocated profile keys into useridprofilekey_value");
90
91 db["global"].insert(b"migrate_profile_keys_to_useridprofilekey", []);
92 useridprofilekey_value.sort()
93}