Our Personal Data Server from scratch!
0

Configure Feed

Select the types of activity you want to include in your feed.

invite: require owning account for generated codes

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (Jul 24, 2026, 8:27 PM +0300) commit eac45fa6 parent aa345393 change-id otnkwoln
+199 -142
+19 -17
crates/tranquil-api/src/server/invite.rs
··· 43 43 match state 44 44 .repos 45 45 .infra 46 - .create_invite_code(&code, input.use_count, Some(&for_account)) 46 + .create_invite_code(&code, input.use_count, &for_account) 47 47 .await 48 48 { 49 49 Ok(true) => Ok(Json(CreateInviteCodeOutput { code })), ··· 115 115 async move { 116 116 let codes: Vec<InviteCodeValue> = (0..code_count).map(|_| gen_invite_code()).collect(); 117 117 infra_repo 118 - .create_invite_codes_batch(&codes, use_count, admin_user_id, Some(&account)) 118 + .create_invite_codes_batch(&codes, use_count, admin_user_id, &account) 119 119 .await 120 120 .map(|_| AccountCodes { account, codes }) 121 121 } ··· 188 188 let codes = futures::future::join_all(filtered_codes.into_iter().map(|info| { 189 189 let infra_repo = state.repos.infra.clone(); 190 190 async move { 191 - let uses = infra_repo 191 + let uses: Vec<InviteCodeUse> = infra_repo 192 192 .get_invite_code_uses(&info.code) 193 193 .await 194 - .map(|use_rows| { 195 - use_rows 196 - .into_iter() 197 - .map(|u| InviteCodeUse { 198 - used_by: u.used_by_did.to_string(), 199 - used_by_handle: u.used_by_handle.map(|h| h.to_string()), 200 - used_at: u.used_at.to_rfc3339(), 201 - }) 202 - .collect::<Vec<_>>() 194 + .log_db_err("fetching invite code uses")? 195 + .into_iter() 196 + .map(|u| InviteCodeUse { 197 + used_by: u.used_by_did.to_string(), 198 + used_by_handle: u.used_by_handle.map(|h| h.to_string()), 199 + used_at: u.used_at.to_rfc3339(), 203 200 }) 204 - .unwrap_or_default(); 201 + .collect(); 205 202 206 203 let use_count = i32::try_from(uses.len()).unwrap_or(i32::MAX); 207 204 if !include_used && use_count >= info.available_uses { 208 - return None; 205 + return Ok(None); 209 206 } 210 207 211 - Some(InviteCode { 208 + Ok(Some(InviteCode { 212 209 code: info.code, 213 210 available: info.available_uses, 214 211 disabled: false, ··· 219 216 .unwrap_or_else(|| "admin".to_string()), 220 217 created_at: info.created_at.to_rfc3339(), 221 218 uses, 222 - }) 219 + })) 223 220 } 224 221 })) 225 222 .await; 226 223 227 - let codes: Vec<InviteCode> = codes.into_iter().flatten().collect(); 224 + let codes: Vec<InviteCode> = codes 225 + .into_iter() 226 + .collect::<Result<Vec<Option<InviteCode>>, ApiError>>()? 227 + .into_iter() 228 + .flatten() 229 + .collect(); 228 230 Ok(Json(GetAccountInviteCodesOutput { codes })) 229 231 }
+2 -2
crates/tranquil-db-traits/src/infra.rs
··· 250 250 &self, 251 251 code: &InviteCode, 252 252 use_count: i32, 253 - for_account: Option<&Did>, 253 + for_account: &Did, 254 254 ) -> Result<bool, DbError>; 255 255 256 256 async fn create_invite_codes_batch( ··· 258 258 codes: &[InviteCode], 259 259 use_count: i32, 260 260 created_by_user: Uuid, 261 - for_account: Option<&Did>, 261 + for_account: &Did, 262 262 ) -> Result<(), DbError>; 263 263 264 264 async fn get_invite_code_available_uses(
+121 -88
crates/tranquil-db/src/postgres/infra.rs
··· 7 7 InviteCodeSortOrder, InviteCodeState, InviteCodeUse, NotificationHistoryRow, PlcTokenInfo, 8 8 QueuedComms, ReservedSigningKey, ReservedSigningKeyFull, ValidatedInviteCode, 9 9 }; 10 - use tranquil_types::{CidLink, Did, Handle, InviteCode}; 10 + use tranquil_types::{CidLink, Did, InviteCode}; 11 11 use uuid::Uuid; 12 12 13 + use super::col; 13 14 use super::user::map_sqlx_error; 15 + use super::{column, legacy_column, opt_column}; 14 16 15 17 pub struct PostgresInfraRepository { 16 18 pool: PgPool, ··· 153 155 &self, 154 156 code: &InviteCode, 155 157 use_count: i32, 156 - for_account: Option<&Did>, 158 + for_account: &Did, 157 159 ) -> Result<bool, DbError> { 158 - let for_account_str = for_account.map(|d| d.as_str()); 160 + let for_account_str = for_account.as_str(); 159 161 let result = sqlx::query!( 160 162 r#"INSERT INTO invite_codes (code, available_uses, created_by_user, for_account) 161 163 SELECT $1, $2, id, $3 FROM users WHERE is_admin = true LIMIT 1"#, ··· 175 177 codes: &[InviteCode], 176 178 use_count: i32, 177 179 created_by_user: Uuid, 178 - for_account: Option<&Did>, 180 + for_account: &Did, 179 181 ) -> Result<(), DbError> { 180 - let for_account_str = for_account.map(|d| d.as_str()); 182 + let for_account_str = for_account.as_str(); 181 183 let code_strs: Vec<String> = codes.iter().map(|c| c.to_string()).collect(); 182 184 sqlx::query!( 183 185 r#"INSERT INTO invite_codes (code, available_uses, created_by_user, for_account) ··· 250 252 .await 251 253 .map_err(map_sqlx_error)?; 252 254 253 - Ok(results 255 + results 254 256 .into_iter() 255 - .map(|r| InviteCodeInfo { 256 - code: InviteCode::from(r.code), 257 - available_uses: r.available_uses, 258 - state: InviteCodeState::from_optional_disabled_flag(r.disabled), 259 - for_account: Some(Did::from(r.for_account)), 260 - created_at: r.created_at, 261 - created_by: None, 257 + .map(|r| { 258 + Ok(InviteCodeInfo { 259 + code: InviteCode::from(r.code), 260 + available_uses: r.available_uses, 261 + state: InviteCodeState::from_optional_disabled_flag(r.disabled), 262 + for_account: legacy_column(r.for_account, col::INVITE_CODES_FOR_ACCOUNT), 263 + created_at: r.created_at, 264 + created_by: None, 265 + }) 262 266 }) 263 - .collect()) 267 + .collect() 264 268 } 265 269 266 270 async fn get_invite_code_uses(&self, code: &InviteCode) -> Result<Vec<InviteCodeUse>, DbError> { ··· 278 282 279 283 Ok(results 280 284 .into_iter() 281 - .map(|r| InviteCodeUse { 282 - code: code.clone(), 283 - used_by_did: Did::from(r.did), 284 - used_by_handle: Some(Handle::from(r.handle)), 285 - used_at: r.used_at, 285 + .filter_map(|r| { 286 + Some(InviteCodeUse { 287 + code: code.clone(), 288 + used_by_did: legacy_column(r.did, col::USERS_DID)?, 289 + used_by_handle: legacy_column(r.handle, col::USERS_HANDLE), 290 + used_at: r.used_at, 291 + }) 286 292 }) 287 293 .collect()) 288 294 } ··· 436 442 .await 437 443 .map_err(map_sqlx_error)?; 438 444 439 - Ok(results 445 + results 440 446 .into_iter() 441 - .map(|r| (r.id, Did::from(r.did))) 442 - .collect()) 447 + .map(|r| Ok((r.id, column(r.did, col::USERS_DID)?))) 448 + .collect() 443 449 } 444 450 445 451 async fn get_invite_code_uses_batch( ··· 459 465 .await 460 466 .map_err(map_sqlx_error)?; 461 467 462 - Ok(results 468 + results 463 469 .into_iter() 464 - .map(|r| InviteCodeUse { 465 - code: InviteCode::from(r.code), 466 - used_by_did: Did::from(r.did), 467 - used_by_handle: None, 468 - used_at: r.used_at, 470 + .map(|r| { 471 + Ok(InviteCodeUse { 472 + code: InviteCode::from(r.code), 473 + used_by_did: column(r.did, col::USERS_DID)?, 474 + used_by_handle: None, 475 + used_at: r.used_at, 476 + }) 469 477 }) 470 - .collect()) 478 + .collect() 471 479 } 472 480 473 481 async fn get_invites_created_by_user( ··· 485 493 .await 486 494 .map_err(map_sqlx_error)?; 487 495 488 - Ok(results 496 + results 489 497 .into_iter() 490 - .map(|r| InviteCodeInfo { 491 - code: InviteCode::from(r.code), 492 - available_uses: r.available_uses, 493 - state: InviteCodeState::from_optional_disabled_flag(r.disabled), 494 - for_account: Some(Did::from(r.for_account)), 495 - created_at: r.created_at, 496 - created_by: Some(Did::from(r.created_by)), 498 + .map(|r| { 499 + Ok(InviteCodeInfo { 500 + code: InviteCode::from(r.code), 501 + available_uses: r.available_uses, 502 + state: InviteCodeState::from_optional_disabled_flag(r.disabled), 503 + for_account: legacy_column(r.for_account, col::INVITE_CODES_FOR_ACCOUNT), 504 + created_at: r.created_at, 505 + created_by: Some(column(r.created_by, col::USERS_DID)?), 506 + }) 497 507 }) 498 - .collect()) 508 + .collect() 499 509 } 500 510 501 511 async fn get_invite_code_info( ··· 513 523 .await 514 524 .map_err(map_sqlx_error)?; 515 525 516 - Ok(result.map(|r| InviteCodeInfo { 517 - code: InviteCode::from(r.code), 518 - available_uses: r.available_uses, 519 - state: InviteCodeState::from_optional_disabled_flag(r.disabled), 520 - for_account: Some(Did::from(r.for_account)), 521 - created_at: r.created_at, 522 - created_by: Some(Did::from(r.created_by)), 523 - })) 526 + result 527 + .map(|r| { 528 + Ok(InviteCodeInfo { 529 + code: InviteCode::from(r.code), 530 + available_uses: r.available_uses, 531 + state: InviteCodeState::from_optional_disabled_flag(r.disabled), 532 + for_account: legacy_column(r.for_account, col::INVITE_CODES_FOR_ACCOUNT), 533 + created_at: r.created_at, 534 + created_by: Some(column(r.created_by, col::USERS_DID)?), 535 + }) 536 + }) 537 + .transpose() 524 538 } 525 539 526 540 async fn get_invite_codes_by_users( ··· 539 553 .await 540 554 .map_err(map_sqlx_error)?; 541 555 542 - Ok(results 556 + results 543 557 .into_iter() 544 558 .map(|r| { 545 - ( 559 + Ok(( 546 560 r.created_by_user, 547 561 InviteCodeInfo { 548 562 code: InviteCode::from(r.code), 549 563 available_uses: r.available_uses, 550 564 state: InviteCodeState::from_optional_disabled_flag(r.disabled), 551 - for_account: Some(Did::from(r.for_account)), 565 + for_account: legacy_column(r.for_account, col::INVITE_CODES_FOR_ACCOUNT), 552 566 created_at: r.created_at, 553 - created_by: Some(Did::from(r.created_by)), 567 + created_by: Some(column(r.created_by, col::USERS_DID)?), 554 568 }, 555 - ) 569 + )) 556 570 }) 557 - .collect()) 571 + .collect() 558 572 } 559 573 560 574 async fn get_invite_code_used_by_user( ··· 683 697 .await 684 698 .map_err(map_sqlx_error)?; 685 699 686 - Ok(result.map(|r| DeletionRequest { 687 - did: Did::from(r.did), 688 - expires_at: r.expires_at, 689 - })) 700 + result 701 + .map(|r| { 702 + Ok(DeletionRequest { 703 + did: column(r.did, col::ACCOUNT_DELETION_REQUESTS_DID)?, 704 + expires_at: r.expires_at, 705 + }) 706 + }) 707 + .transpose() 690 708 } 691 709 692 710 async fn delete_deletion_request(&self, token: &str) -> Result<(), DbError> { ··· 1027 1045 .await 1028 1046 .map_err(map_sqlx_error)?; 1029 1047 1030 - Ok(result.map(|r| AdminAccountInfo { 1031 - id: r.id, 1032 - did: Did::from(r.did), 1033 - handle: Handle::from(r.handle), 1034 - email: r.email, 1035 - created_at: r.created_at, 1036 - invites_disabled: r.invites_disabled.unwrap_or(false), 1037 - email_verified: r.email_verified, 1038 - deactivated_at: r.deactivated_at, 1039 - })) 1048 + result 1049 + .map(|r| { 1050 + Ok(AdminAccountInfo { 1051 + id: r.id, 1052 + did: column(r.did, col::USERS_DID)?, 1053 + handle: column(r.handle, col::USERS_HANDLE)?, 1054 + email: r.email, 1055 + created_at: r.created_at, 1056 + invites_disabled: r.invites_disabled.unwrap_or(false), 1057 + email_verified: r.email_verified, 1058 + deactivated_at: r.deactivated_at, 1059 + }) 1060 + }) 1061 + .transpose() 1040 1062 } 1041 1063 1042 1064 async fn get_admin_account_infos_by_dids( ··· 1058 1080 1059 1081 Ok(results 1060 1082 .into_iter() 1061 - .map(|r| AdminAccountInfo { 1062 - id: r.id, 1063 - did: Did::from(r.did), 1064 - handle: Handle::from(r.handle), 1065 - email: r.email, 1066 - created_at: r.created_at, 1067 - invites_disabled: r.invites_disabled.unwrap_or(false), 1068 - email_verified: r.email_verified, 1069 - deactivated_at: r.deactivated_at, 1083 + .filter_map(|r| { 1084 + Some(AdminAccountInfo { 1085 + id: r.id, 1086 + did: legacy_column(r.did, col::USERS_DID)?, 1087 + handle: legacy_column(r.handle, col::USERS_HANDLE)?, 1088 + email: r.email, 1089 + created_at: r.created_at, 1090 + invites_disabled: r.invites_disabled.unwrap_or(false), 1091 + email_verified: r.email_verified, 1092 + deactivated_at: r.deactivated_at, 1093 + }) 1070 1094 }) 1071 1095 .collect()) 1072 1096 } ··· 1105 1129 .await 1106 1130 .map_err(map_sqlx_error)?; 1107 1131 1108 - Ok(row.map(|r| DeletionRequestWithToken { 1109 - token: r.token, 1110 - did: Did::new(r.did).expect("valid DID in database"), 1111 - expires_at: r.expires_at, 1112 - })) 1132 + row.map(|r| { 1133 + Ok(DeletionRequestWithToken { 1134 + token: r.token, 1135 + did: column(r.did, col::ACCOUNT_DELETION_REQUESTS_DID)?, 1136 + expires_at: r.expires_at, 1137 + }) 1138 + }) 1139 + .transpose() 1113 1140 } 1114 1141 1115 1142 async fn get_latest_comms_for_user( ··· 1202 1229 .await 1203 1230 .map_err(map_sqlx_error)?; 1204 1231 1205 - Ok(row.map(|r| ReservedSigningKeyFull { 1206 - id: r.id, 1207 - did: r.did.map(|d| Did::new(d).expect("valid DID in database")), 1208 - public_key_did_key: Did::from(r.public_key_did_key), 1209 - private_key_bytes: r.private_key_bytes, 1210 - expires_at: r.expires_at, 1211 - used_at: r.used_at, 1212 - })) 1232 + row.map(|r| { 1233 + Ok(ReservedSigningKeyFull { 1234 + id: r.id, 1235 + did: opt_column(r.did, col::RESERVED_SIGNING_KEYS_DID)?, 1236 + public_key_did_key: column( 1237 + r.public_key_did_key, 1238 + col::RESERVED_SIGNING_KEYS_PUBLIC_KEY_DID_KEY, 1239 + )?, 1240 + private_key_bytes: r.private_key_bytes, 1241 + expires_at: r.expires_at, 1242 + used_at: r.used_at, 1243 + }) 1244 + }) 1245 + .transpose() 1213 1246 } 1214 1247 1215 1248 async fn get_plc_tokens_by_did(&self, did: &Did) -> Result<Vec<PlcTokenInfo>, DbError> {
+9 -13
crates/tranquil-pds/tests/store_parity.rs
··· 117 117 } 118 118 119 119 fn test_cid(seed: u8) -> CidLink { 120 - CidLink::from(helpers::make_cid(&[seed])) 120 + CidLink::from_cid(&helpers::make_cid(&[seed])) 121 121 } 122 122 123 123 fn test_nsid(name: &str) -> Nsid { ··· 139 139 } 140 140 141 141 async fn seed_user(repos: &PostgresRepositories, did: &Did, handle: &Handle) -> Uuid { 142 - let commit_cid = CidLink::from(helpers::make_cid(did.as_str().as_bytes()).to_string()); 142 + let commit_cid = CidLink::from_cid(&helpers::make_cid(did.as_str().as_bytes())); 143 143 let input = tranquil_db_traits::CreatePasswordAccountInput { 144 144 handle: handle.clone(), 145 145 email: None, ··· 155 155 encryption_version: 0, 156 156 reserved_key_id: None, 157 157 commit_cid, 158 - repo_rev: Tid::from("rev0".to_string()), 158 + repo_rev: Tid::new("3k2aaaaaaaaaa").expect("valid TID"), 159 159 genesis_block_cids: vec![], 160 160 invite_code: None, 161 161 birthdate_pref: None, ··· 190 190 &collections, 191 191 &rkeys, 192 192 &cids, 193 - &Tid::from("rev1".to_string()), 193 + &Tid::new("3k2aaaaaaaaab").expect("valid TID"), 194 194 ) 195 195 .await 196 196 .unwrap(); ··· 1109 1109 let _ = seed_repos(&f, &did, &handle).await; 1110 1110 let code = tranquil_types::InviteCode::from(format!("parity-invite-{}", Uuid::new_v4())); 1111 1111 1112 - let pg_created = 1113 - f.pg.infra 1114 - .create_invite_code(&code, 5, Some(&did)) 1115 - .await 1116 - .unwrap(); 1112 + let pg_created = f.pg.infra.create_invite_code(&code, 5, &did).await.unwrap(); 1117 1113 let store_created = f 1118 1114 .store 1119 1115 .infra 1120 - .create_invite_code(&code, 5, Some(&did)) 1116 + .create_invite_code(&code, 5, &did) 1121 1117 .await 1122 1118 .unwrap(); 1123 1119 assert_eq!(pg_created, store_created); ··· 1359 1355 let f = ParityFixture::new().await; 1360 1356 let did = test_did("sigkey"); 1361 1357 let expires = chrono::Utc::now() + chrono::Duration::hours(1); 1362 - let pub_key = Did::from(format!("did:key:z6Mk{}", Uuid::new_v4().simple())); 1358 + let pub_key = Did::new(format!("did:key:z6Mk{}", Uuid::new_v4().simple())).expect("valid DID"); 1363 1359 let priv_bytes = vec![1u8, 2, 3, 4, 5, 6, 7, 8]; 1364 1360 1365 1361 f.pg.infra ··· 1418 1414 assert_eq!(pg_root, store_root); 1419 1415 1420 1416 let new_root = test_cid(99); 1421 - let rev1 = Tid::from("rev1".to_string()); 1417 + let rev1 = Tid::new("3k2aaaaaaaaab").expect("valid TID"); 1422 1418 f.pg.repo 1423 1419 .update_repo_root(pg_uid, &new_root, &rev1) 1424 1420 .await ··· 1632 1628 blobs: None, 1633 1629 blocks: None, 1634 1630 prev_data_cid: None, 1635 - rev: Some(Tid::from("rev0".to_string())), 1631 + rev: Some(Tid::new("3k2aaaaaaaaaa").expect("valid TID")), 1636 1632 }; 1637 1633 1638 1634 let baseline = f.pg.repo.get_max_seq().await.unwrap();
+4 -4
crates/tranquil-store/src/metastore/client.rs
··· 1836 1836 &self, 1837 1837 code: &InviteCode, 1838 1838 use_count: i32, 1839 - for_account: Option<&Did>, 1839 + for_account: &Did, 1840 1840 ) -> Result<bool, DbError> { 1841 1841 let (tx, rx) = oneshot::channel(); 1842 1842 self.pool 1843 1843 .send(MetastoreRequest::Infra(InfraRequest::CreateInviteCode { 1844 1844 code: code.clone(), 1845 1845 use_count, 1846 - for_account: for_account.cloned(), 1846 + for_account: for_account.clone(), 1847 1847 tx, 1848 1848 }))?; 1849 1849 recv(rx).await ··· 1854 1854 codes: &[InviteCode], 1855 1855 use_count: i32, 1856 1856 created_by_user: Uuid, 1857 - for_account: Option<&Did>, 1857 + for_account: &Did, 1858 1858 ) -> Result<(), DbError> { 1859 1859 let (tx, rx) = oneshot::channel(); 1860 1860 self.pool.send(MetastoreRequest::Infra( ··· 1862 1862 codes: codes.to_vec(), 1863 1863 use_count, 1864 1864 created_by_user, 1865 - for_account: for_account.cloned(), 1865 + for_account: for_account.clone(), 1866 1866 tx, 1867 1867 }, 1868 1868 ))?;
+6 -6
crates/tranquil-store/src/metastore/handler.rs
··· 1828 1828 CreateInviteCode { 1829 1829 code: InviteCode, 1830 1830 use_count: i32, 1831 - for_account: Option<Did>, 1831 + for_account: Did, 1832 1832 tx: Tx<bool>, 1833 1833 }, 1834 1834 CreateInviteCodesBatch { 1835 1835 codes: Vec<InviteCode>, 1836 1836 use_count: i32, 1837 1837 created_by_user: Uuid, 1838 - for_account: Option<Did>, 1838 + for_account: Did, 1839 1839 tx: Tx<()>, 1840 1840 }, 1841 1841 GetInviteCodeAvailableUses { ··· 3911 3911 let result = state 3912 3912 .metastore 3913 3913 .infra_ops() 3914 - .create_invite_code(&code, use_count, for_account.as_ref()) 3914 + .create_invite_code(&code, use_count, &for_account) 3915 3915 .map_err(metastore_to_db); 3916 3916 let _ = tx.send(result); 3917 3917 } ··· 3925 3925 let result = state 3926 3926 .metastore 3927 3927 .infra_ops() 3928 - .create_invite_codes_batch(&codes, use_count, created_by_user, for_account.as_ref()) 3928 + .create_invite_codes_batch(&codes, use_count, created_by_user, &for_account) 3929 3929 .map_err(metastore_to_db); 3930 3930 let _ = tx.send(result); 3931 3931 } ··· 6206 6206 let owner = Did::new("did:plc:whelk").expect("valid DID"); 6207 6207 let whelk = InviteCode::new("whelk"); 6208 6208 6209 - assert!(infra.create_invite_code(&squid, 1, Some(&owner)).unwrap()); 6209 + assert!(infra.create_invite_code(&squid, 1, &owner).unwrap()); 6210 6210 6211 6211 infra.reserve_invite_code(&squid).unwrap(); 6212 6212 assert_eq!( ··· 6245 6245 let owner = Did::new("did:plc:whelk").expect("valid DID"); 6246 6246 let whelk = InviteCode::new("whelk"); 6247 6247 6248 - assert!(infra.create_invite_code(&squid, 1, Some(&owner)).unwrap()); 6248 + assert!(infra.create_invite_code(&squid, 1, &owner).unwrap()); 6249 6249 6250 6250 infra.reserve_invite_code(&squid).unwrap(); 6251 6251 infra.refund_invite_code(&squid).unwrap();
+17 -12
crates/tranquil-store/src/metastore/infra_ops.rs
··· 362 362 &self, 363 363 code: &InviteCode, 364 364 use_count: i32, 365 - for_account: Option<&Did>, 365 + for_account: &Did, 366 366 ) -> Result<bool, MetastoreError> { 367 367 let key = invite_code_key(code); 368 368 let existing = self ··· 377 377 code: code.as_str().to_owned(), 378 378 available_uses: use_count, 379 379 disabled: false, 380 - for_account: for_account.map(|d| d.to_string()), 380 + for_account: Some(for_account.to_string()), 381 381 created_by: None, 382 382 created_at_ms: Utc::now().timestamp_millis(), 383 383 }; ··· 393 393 codes: &[InviteCode], 394 394 use_count: i32, 395 395 created_by_user: Uuid, 396 - for_account: Option<&Did>, 396 + for_account: &Did, 397 397 ) -> Result<(), MetastoreError> { 398 398 let now_ms = Utc::now().timestamp_millis(); 399 399 let mut batch = self.db.batch(); ··· 403 403 code: code.as_str().to_owned(), 404 404 available_uses: use_count, 405 405 disabled: false, 406 - for_account: for_account.map(|d| d.to_string()), 406 + for_account: Some(for_account.to_string()), 407 407 created_by: Some(created_by_user), 408 408 created_at_ms: now_ms, 409 409 }; ··· 1458 1458 SigningKeyValue::deserialize, 1459 1459 "corrupt signing key", 1460 1460 )?; 1461 - Ok(val.map(|v| ReservedSigningKeyFull { 1462 - id: v.id, 1463 - did: v.did.and_then(|d| Did::new(d).ok()), 1464 - public_key_did_key: Did::from(v.public_key_did_key), 1465 - private_key_bytes: v.private_key_bytes, 1466 - expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), 1467 - used_at: v.used_at_ms.and_then(DateTime::from_timestamp_millis), 1468 - })) 1461 + val.map(|v| { 1462 + Ok(ReservedSigningKeyFull { 1463 + id: v.id, 1464 + did: v.did.and_then(|d| Did::new(d).ok()), 1465 + public_key_did_key: Did::new(v.public_key_did_key).map_err(|_| { 1466 + MetastoreError::CorruptData("corrupt reserved signing key public_key_did_key") 1467 + })?, 1468 + private_key_bytes: v.private_key_bytes, 1469 + expires_at: DateTime::from_timestamp_millis(v.expires_at_ms).unwrap_or_default(), 1470 + used_at: v.used_at_ms.and_then(DateTime::from_timestamp_millis), 1471 + }) 1472 + }) 1473 + .transpose() 1469 1474 } 1470 1475 1471 1476 pub fn get_plc_tokens_for_user(
+21
migrations/20260721_backfill_invite_code_for_account.sql
··· 1 + -- The old insert path wrote for_account through without validating it 2 + -- so an older row can contain any string at all 3 + -- most often a handle or the literal 'admin' the column defaulted to 4 + -- where the read path warns and drops whatever the Did newtype won't parse 5 + -- so an unconverted row comes back with no for_account at all. 6 + -- The first statement resolves the bare handles that still match a user 7 + -- and the second covers every 'admin' row, since created_by_user is NOT NULL. 8 + -- Nothing else converts 9 + -- so a prefixed handle, a handle whose user has renamed since, 10 + -- and anything that was never an identifier all stay as they are. 11 + UPDATE invite_codes ic 12 + SET for_account = u.did 13 + FROM users u 14 + WHERE LOWER(u.handle) = LOWER(ic.for_account) 15 + AND ic.for_account NOT LIKE 'did:%'; 16 + 17 + UPDATE invite_codes ic 18 + SET for_account = u.did 19 + FROM users u 20 + WHERE u.id = ic.created_by_user 21 + AND ic.for_account = 'admin';