Files
tranquil-pds/crates/tranquil-store/src/metastore/handler.rs
T

6380 lines
200 KiB
Rust

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread::JoinHandle;
use chrono::{DateTime, Utc};
use tokio::sync::oneshot;
use tranquil_db_traits::DbScope;
use tranquil_db_traits::{
AccountSearchResult, AccountStatus, AdminAccountInfo, ApplyCommitError, ApplyCommitInput,
ApplyCommitResult, Backlink, CommitEventData, CommsChannel, CommsType,
CompletePasskeySetupInput, CreateAccountError, CreateDelegatedAccountInput,
CreatePasskeyAccountInput, CreatePasswordAccountInput, CreatePasswordAccountResult,
CreateSsoAccountInput, DbError, DelegationActionType, DeletionRequest,
DeletionRequestWithToken, DidWebOverrides, ImportBlock, ImportRecord, ImportRepoError,
InviteCodeError, InviteCodeInfo, InviteCodeRow, InviteCodeSortOrder, InviteCodeUse,
MigrationReactivationError, MigrationReactivationInput, NotificationHistoryRow,
NotificationPrefs, OAuthTokenWithUser, PasswordResetResult, PlcTokenInfo, QueuedComms,
ReactivatedAccountInfo, RecoverPasskeyAccountInput, RecoverPasskeyAccountResult,
RefreshSessionResult, ReservedSigningKey, ReservedSigningKeyFull, ScheduledDeletionAccount,
ScopePreference, SequenceNumber, SequencedEvent, SessionId, StoredBackupCode, StoredPasskey,
TokenFamilyId, TotpRecord, TotpRecordState, User2faStatus, UserAuthInfo, UserCommsPrefs,
UserConfirmSignup, UserDidWebInfo, UserEmailInfo, UserForDeletion, UserForDidDoc,
UserForDidDocBuild, UserForPasskeyRecovery, UserForPasskeySetup, UserForRecovery,
UserForVerification, UserIdAndHandle, UserIdAndPasswordHash, UserIdHandleEmail,
UserInfoForAuth, UserKeyInfo, UserKeyWithId, UserLegacyLoginPref, UserLoginCheck,
UserLoginFull, UserLoginInfo, UserNeedingRecordBlobsBackfill, UserPasswordInfo,
UserResendVerification, UserResetCodeInfo, UserRow, UserSessionInfo, UserStatus,
UserVerificationInfo, UserWithKey, UserWithoutBlocks, ValidatedInviteCode,
WebauthnChallengeType,
};
use tranquil_oauth::{AuthorizedClientData, DeviceData, RequestData, TokenData};
use tranquil_types::{
AtUri, AuthorizationCode, CidLink, ClientId, DPoPProofId, DeviceId, Did, Handle, InviteCode,
Nsid, RefreshToken, RequestId, Rkey, Tid, TokenId,
};
use uuid::Uuid;
use super::MetastoreError;
use super::commit_ops::CommitOps;
use super::event_ops::EventOps;
use super::infra_ops::InfraOps;
use super::keys::UserHash;
use super::record_ops::ListRecordsQuery;
use super::user_hash::UserHashMap;
use crate::blockstore::TranquilBlockStore;
use crate::clock::SystemClock;
use crate::eventlog::EventLogBridge;
use crate::io::{RealIO, StorageIO};
use crate::metastore::Metastore;
type Tx<T> = oneshot::Sender<Result<T, DbError>>;
fn reserve_invite(infra: &InfraOps, code: Option<&InviteCode>) -> Result<(), CreateAccountError> {
match code {
Some(code) => infra.reserve_invite_code(code).map_err(|e| match e {
InviteCodeError::DatabaseError(e) => CreateAccountError::Database(e.to_string()),
_ => CreateAccountError::InviteCodeUnavailable,
}),
None => Ok(()),
}
}
fn record_invite_use(
infra: &InfraOps,
code: Option<&InviteCode>,
user_id: Uuid,
) -> Result<(), CreateAccountError> {
match code {
Some(code) => {
let validated = ValidatedInviteCode::new_validated(code);
infra
.record_invite_code_use(&validated, user_id)
.map_err(|e| CreateAccountError::Database(e.to_string()))
}
None => Ok(()),
}
}
fn refund_invite(infra: &InfraOps, code: Option<&InviteCode>) {
if let Some(code) = code
&& let Err(e) = infra.refund_invite_code(code)
{
tracing::error!("failed to refund invite code after account creation failed: {e:?}");
}
}
fn finalize_account<T>(
infra: &InfraOps,
code: Option<&InviteCode>,
created: Result<T, CreateAccountError>,
after: impl FnOnce(&T) -> Result<Uuid, CreateAccountError>,
) -> Result<T, CreateAccountError> {
match created {
Ok(result) => {
let user_id = after(&result)?;
record_invite_use(infra, code, user_id)?;
Ok(result)
}
Err(e) => {
refund_invite(infra, code);
Err(e)
}
}
}
fn metastore_to_db(e: MetastoreError) -> DbError {
match e {
MetastoreError::Fjall(e) => DbError::Query(e.to_string()),
MetastoreError::Lsm(e) => DbError::Query(e.to_string()),
MetastoreError::VersionMismatch { expected, found } => DbError::Query(format!(
"format version mismatch: expected {expected}, found {found}"
)),
MetastoreError::CorruptData(msg) => DbError::CorruptData(msg),
MetastoreError::InvalidInput(msg) => DbError::Query(msg.to_string()),
MetastoreError::UserHashCollision {
hash,
existing_uuid,
new_uuid,
} => DbError::Constraint(format!(
"user hash collision: {hash} maps to both {existing_uuid} and {new_uuid}"
)),
MetastoreError::UniqueViolation(constraint) => {
DbError::Constraint(format!("unique constraint violated: {constraint}"))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Routing {
Sharded(u64),
Global,
}
fn uuid_to_routing(user_hashes: &UserHashMap, user_id: &Uuid) -> Routing {
match user_hashes.get(user_id) {
Some(h) => Routing::Sharded(h.raw()),
None => Routing::Sharded(user_id.as_u128() as u64),
}
}
fn did_to_routing(did: &str) -> Routing {
Routing::Sharded(UserHash::from_did(did).raw())
}
fn cid_to_routing(cid: &CidLink) -> Routing {
use siphasher::sip::SipHasher24;
use std::hash::{Hash, Hasher};
let mut hasher = SipHasher24::new();
cid.as_str().hash(&mut hasher);
Routing::Sharded(hasher.finish())
}
pub enum MetastoreRequest {
Repo(RepoRequest),
Record(RecordRequest),
UserBlock(UserBlockRequest),
Event(EventRequest),
Commit(Box<CommitRequest>),
Backlink(BacklinkRequest),
Blob(BlobRequest),
Delegation(DelegationRequest),
Sso(SsoRequest),
Session(SessionRequest),
Infra(InfraRequest),
OAuth(OAuthRequest),
User(UserRequest),
}
impl MetastoreRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::Repo(r) => r.routing(user_hashes),
Self::Record(r) => r.routing(user_hashes),
Self::UserBlock(r) => r.routing(user_hashes),
Self::Event(r) => r.routing(),
Self::Commit(r) => r.routing(user_hashes),
Self::Backlink(r) => r.routing(user_hashes),
Self::Blob(r) => r.routing(user_hashes),
Self::Delegation(r) => r.routing(),
Self::Sso(r) => r.routing(),
Self::Session(r) => r.routing(user_hashes),
Self::Infra(r) => r.routing(user_hashes),
Self::OAuth(r) => r.routing(),
Self::User(r) => r.routing(user_hashes),
}
}
}
pub enum RepoRequest {
CreateRepoFull {
user_id: Uuid,
did: Did,
handle: Handle,
repo_root_cid: CidLink,
repo_rev: Tid,
tx: Tx<()>,
},
UpdateRepoRoot {
user_id: Uuid,
repo_root_cid: CidLink,
repo_rev: Tid,
tx: Tx<()>,
},
UpdateRepoRev {
user_id: Uuid,
repo_rev: Tid,
tx: Tx<()>,
},
DeleteRepo {
user_id: Uuid,
tx: Tx<()>,
},
GetRepoRootForUpdate {
user_id: Uuid,
tx: Tx<Option<CidLink>>,
},
GetRepo {
user_id: Uuid,
tx: Tx<Option<tranquil_db_traits::RepoInfo>>,
},
GetRepoRootByDid {
did: Did,
tx: Tx<Option<CidLink>>,
},
CountRepos {
tx: Tx<i64>,
},
GetReposWithoutRev {
tx: Tx<Vec<tranquil_db_traits::RepoWithoutRev>>,
},
GetRepoRootCidByUserId {
user_id: Uuid,
tx: Tx<Option<CidLink>>,
},
GetAccountWithRepo {
did: Did,
tx: Tx<Option<tranquil_db_traits::RepoAccountInfo>>,
},
ListReposPaginated {
cursor_user_hash: Option<u64>,
limit: usize,
tx: Tx<Vec<tranquil_db_traits::RepoListItem>>,
},
UpdateRepoStatus {
did: Did,
takedown: Option<bool>,
takedown_ref: Option<String>,
deactivated: Option<bool>,
tx: Tx<()>,
},
}
impl RepoRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::CreateRepoFull { did, .. } => did_to_routing(did),
Self::UpdateRepoRoot { user_id, .. }
| Self::UpdateRepoRev { user_id, .. }
| Self::DeleteRepo { user_id, .. }
| Self::GetRepoRootForUpdate { user_id, .. }
| Self::GetRepo { user_id, .. }
| Self::GetRepoRootCidByUserId { user_id, .. } => uuid_to_routing(user_hashes, user_id),
Self::GetRepoRootByDid { did, .. }
| Self::GetAccountWithRepo { did, .. }
| Self::UpdateRepoStatus { did, .. } => did_to_routing(did),
Self::CountRepos { .. }
| Self::GetReposWithoutRev { .. }
| Self::ListReposPaginated { .. } => Routing::Global,
}
}
}
pub enum RecordRequest {
UpsertRecords {
repo_id: Uuid,
collections: Vec<Nsid>,
rkeys: Vec<Rkey>,
record_cids: Vec<CidLink>,
repo_rev: Tid,
tx: Tx<()>,
},
DeleteRecords {
repo_id: Uuid,
collections: Vec<Nsid>,
rkeys: Vec<Rkey>,
tx: Tx<()>,
},
DeleteAllRecords {
repo_id: Uuid,
tx: Tx<()>,
},
GetRecordCid {
repo_id: Uuid,
collection: Nsid,
rkey: Rkey,
tx: Tx<Option<CidLink>>,
},
ListRecords {
repo_id: Uuid,
collection: Nsid,
cursor: Option<Rkey>,
limit: i64,
reverse: bool,
rkey_start: Option<Rkey>,
rkey_end: Option<Rkey>,
tx: Tx<Vec<tranquil_db_traits::RecordInfo>>,
},
GetAllRecords {
repo_id: Uuid,
tx: Tx<Vec<tranquil_db_traits::FullRecordInfo>>,
},
ListCollections {
repo_id: Uuid,
tx: Tx<Vec<Nsid>>,
},
CountRecords {
repo_id: Uuid,
tx: Tx<i64>,
},
CountAllRecords {
tx: Tx<i64>,
},
GetRecordByCid {
cid: CidLink,
tx: Tx<Option<tranquil_db_traits::RecordWithTakedown>>,
},
ReferencedRecordCids {
repo_id: Uuid,
cids: Vec<CidLink>,
excluded_keys: Vec<(Nsid, Rkey)>,
tx: Tx<Vec<CidLink>>,
},
SetRecordTakedown {
cid: CidLink,
takedown_ref: Option<String>,
scope_user: Option<Uuid>,
tx: Tx<()>,
},
}
impl RecordRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::UpsertRecords { repo_id, .. }
| Self::DeleteRecords { repo_id, .. }
| Self::DeleteAllRecords { repo_id, .. }
| Self::GetRecordCid { repo_id, .. }
| Self::ListRecords { repo_id, .. }
| Self::GetAllRecords { repo_id, .. }
| Self::ListCollections { repo_id, .. }
| Self::CountRecords { repo_id, .. }
| Self::ReferencedRecordCids { repo_id, .. } => uuid_to_routing(user_hashes, repo_id),
Self::CountAllRecords { .. } | Self::GetRecordByCid { .. } => Routing::Global,
Self::SetRecordTakedown {
scope_user: Some(user_id),
..
} => uuid_to_routing(user_hashes, user_id),
Self::SetRecordTakedown { .. } => Routing::Global,
}
}
}
pub enum UserBlockRequest {
InsertUserBlocks {
user_id: Uuid,
block_cids: Vec<Vec<u8>>,
repo_rev: Tid,
tx: Tx<()>,
},
DeleteUserBlocks {
user_id: Uuid,
block_cids: Vec<Vec<u8>>,
tx: Tx<()>,
},
GetUserBlockCidsSinceRev {
user_id: Uuid,
since_rev: Option<Tid>,
tx: Tx<Vec<Vec<u8>>>,
},
CountUserBlocks {
user_id: Uuid,
tx: Tx<i64>,
},
}
impl UserBlockRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::InsertUserBlocks { user_id, .. }
| Self::DeleteUserBlocks { user_id, .. }
| Self::GetUserBlockCidsSinceRev { user_id, .. }
| Self::CountUserBlocks { user_id, .. } => uuid_to_routing(user_hashes, user_id),
}
}
}
pub enum EventRequest {
InsertCommitEvent {
data: CommitEventData,
tx: Tx<SequenceNumber>,
},
InsertIdentityEvent {
did: Did,
handle: Option<Handle>,
tx: Tx<SequenceNumber>,
},
InsertAccountEvent {
did: Did,
status: AccountStatus,
tx: Tx<SequenceNumber>,
},
InsertSyncEvent {
did: Did,
commit_cid: CidLink,
rev: Option<Tid>,
commit_bytes: Vec<u8>,
tx: Tx<SequenceNumber>,
},
InsertGenesisCommitEvent {
did: Did,
commit_cid: CidLink,
mst_root_cid: CidLink,
rev: Tid,
commit_bytes: Vec<u8>,
mst_root_bytes: Vec<u8>,
tx: Tx<SequenceNumber>,
},
PurgeDidEventsKeepingLatest {
did: Did,
tx: Tx<()>,
},
GetMaxSeq {
tx: Tx<SequenceNumber>,
},
GetMinSeqSince {
since: DateTime<Utc>,
tx: Tx<Option<SequenceNumber>>,
},
GetEventsSinceSeq {
since_seq: SequenceNumber,
limit: Option<i64>,
tx: Tx<Vec<SequencedEvent>>,
},
GetEventsInSeqRange {
start_seq: SequenceNumber,
end_seq: SequenceNumber,
tx: Tx<Vec<SequencedEvent>>,
},
GetEventBySeq {
seq: SequenceNumber,
tx: Tx<Option<SequencedEvent>>,
},
GetEventsSinceCursor {
cursor: SequenceNumber,
limit: i64,
tx: Tx<Vec<SequencedEvent>>,
},
}
impl EventRequest {
fn routing(&self) -> Routing {
match self {
Self::InsertCommitEvent { data, .. } => {
Routing::Sharded(UserHash::from_did(data.did.as_str()).raw())
}
Self::InsertIdentityEvent { did, .. }
| Self::InsertAccountEvent { did, .. }
| Self::InsertSyncEvent { did, .. }
| Self::InsertGenesisCommitEvent { did, .. }
| Self::PurgeDidEventsKeepingLatest { did, .. } => {
Routing::Sharded(UserHash::from_did(did.as_str()).raw())
}
Self::GetMaxSeq { .. }
| Self::GetMinSeqSince { .. }
| Self::GetEventsSinceSeq { .. }
| Self::GetEventsInSeqRange { .. }
| Self::GetEventBySeq { .. }
| Self::GetEventsSinceCursor { .. } => Routing::Global,
}
}
}
pub enum CommitRequest {
ApplyCommit {
input: Box<ApplyCommitInput>,
tx: oneshot::Sender<Result<ApplyCommitResult, ApplyCommitError>>,
},
ImportRepoData {
user_id: Uuid,
blocks: Vec<ImportBlock>,
records: Vec<ImportRecord>,
expected_root_cid: Option<CidLink>,
tx: oneshot::Sender<Result<(), ImportRepoError>>,
},
GetUsersWithoutBlocks {
tx: Tx<Vec<UserWithoutBlocks>>,
},
GetUsersNeedingRecordBlobsBackfill {
limit: i64,
tx: Tx<Vec<UserNeedingRecordBlobsBackfill>>,
},
InsertRecordBlobs {
repo_id: Uuid,
record_uris: Vec<AtUri>,
blob_cids: Vec<CidLink>,
tx: Tx<()>,
},
}
impl CommitRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::ApplyCommit { input, .. } => did_to_routing(&input.did),
Self::ImportRepoData { user_id, .. }
| Self::InsertRecordBlobs {
repo_id: user_id, ..
} => uuid_to_routing(user_hashes, user_id),
Self::GetUsersWithoutBlocks { .. }
| Self::GetUsersNeedingRecordBlobsBackfill { .. } => Routing::Global,
}
}
}
pub enum BacklinkRequest {
GetBacklinkConflicts {
repo_id: Uuid,
collection: Nsid,
backlinks: Vec<Backlink>,
tx: Tx<Vec<AtUri>>,
},
AddBacklinks {
repo_id: Uuid,
backlinks: Vec<Backlink>,
tx: Tx<()>,
},
RemoveBacklinksByUri {
uri: AtUri,
tx: Tx<()>,
},
RemoveBacklinksByRepo {
repo_id: Uuid,
tx: Tx<()>,
},
}
impl BacklinkRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::GetBacklinkConflicts { repo_id, .. }
| Self::AddBacklinks { repo_id, .. }
| Self::RemoveBacklinksByRepo { repo_id, .. } => uuid_to_routing(user_hashes, repo_id),
Self::RemoveBacklinksByUri { uri, .. } => match uri.did() {
Some(did) => did_to_routing(did),
None => Routing::Global,
},
}
}
}
pub enum BlobRequest {
InsertBlob {
cid: CidLink,
mime_type: String,
size_bytes: i64,
created_by_user: Uuid,
storage_key: String,
tx: Tx<Option<CidLink>>,
},
GetBlobMetadata {
cid: CidLink,
tx: Tx<Option<tranquil_db_traits::BlobMetadata>>,
},
GetBlobWithTakedown {
cid: CidLink,
tx: Tx<Option<tranquil_db_traits::BlobWithTakedown>>,
},
GetBlobStorageKey {
cid: CidLink,
tx: Tx<Option<String>>,
},
ListBlobsByUser {
user_id: Uuid,
cursor: Option<String>,
limit: i64,
tx: Tx<Vec<CidLink>>,
},
ListBlobsSinceRev {
did: Did,
since: Tid,
tx: Tx<Vec<CidLink>>,
},
CountBlobsByUser {
user_id: Uuid,
tx: Tx<i64>,
},
SumBlobStorage {
tx: Tx<i64>,
},
UpdateBlobTakedown {
cid: CidLink,
takedown_ref: Option<String>,
tx: Tx<bool>,
},
DeleteBlobsByUser {
user_id: Uuid,
tx: Tx<u64>,
},
GetBlobStorageKeysByUser {
user_id: Uuid,
tx: Tx<Vec<String>>,
},
ListMissingBlobs {
repo_id: Uuid,
cursor: Option<String>,
limit: i64,
tx: Tx<Vec<tranquil_db_traits::MissingBlobInfo>>,
},
CountDistinctRecordBlobs {
repo_id: Uuid,
tx: Tx<i64>,
},
GetBlobsForExport {
repo_id: Uuid,
tx: Tx<Vec<tranquil_db_traits::BlobForExport>>,
},
}
impl BlobRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::InsertBlob { cid, .. } | Self::UpdateBlobTakedown { cid, .. } => {
cid_to_routing(cid)
}
Self::DeleteBlobsByUser { user_id, .. } => uuid_to_routing(user_hashes, user_id),
Self::GetBlobMetadata { .. }
| Self::GetBlobWithTakedown { .. }
| Self::GetBlobStorageKey { .. }
| Self::SumBlobStorage { .. } => Routing::Global,
Self::ListBlobsByUser { user_id, .. }
| Self::CountBlobsByUser { user_id, .. }
| Self::GetBlobStorageKeysByUser { user_id, .. } => {
uuid_to_routing(user_hashes, user_id)
}
Self::ListMissingBlobs { repo_id, .. }
| Self::CountDistinctRecordBlobs { repo_id, .. }
| Self::GetBlobsForExport { repo_id, .. } => uuid_to_routing(user_hashes, repo_id),
Self::ListBlobsSinceRev { did, .. } => did_to_routing(did),
}
}
}
pub enum DelegationRequest {
IsDelegatedAccount {
did: Did,
tx: Tx<bool>,
},
CreateDelegation {
delegated_did: Did,
controller_did: Did,
granted_scopes: DbScope,
granted_by: Did,
tx: Tx<Uuid>,
},
RevokeDelegation {
delegated_did: Did,
controller_did: Did,
revoked_by: Did,
tx: Tx<bool>,
},
UpdateDelegationScopes {
delegated_did: Did,
controller_did: Did,
new_scopes: DbScope,
tx: Tx<bool>,
},
GetDelegation {
delegated_did: Did,
controller_did: Did,
tx: Tx<Option<tranquil_db_traits::DelegationGrant>>,
},
GetDelegationsForAccount {
delegated_did: Did,
tx: Tx<Vec<tranquil_db_traits::ControllerInfo>>,
},
GetAccountsControlledBy {
controller_did: Did,
tx: Tx<Vec<tranquil_db_traits::DelegatedAccountInfo>>,
},
CountActiveControllers {
delegated_did: Did,
tx: Tx<i64>,
},
ControlsAnyAccounts {
did: Did,
tx: Tx<bool>,
},
LogDelegationAction {
delegated_did: Did,
actor_did: Did,
controller_did: Option<Did>,
action_type: DelegationActionType,
action_details: Option<serde_json::Value>,
ip_address: Option<String>,
user_agent: Option<String>,
tx: Tx<Uuid>,
},
GetAuditLogForAccount {
delegated_did: Did,
limit: i64,
offset: i64,
tx: Tx<Vec<tranquil_db_traits::AuditLogEntry>>,
},
CountAuditLogEntries {
delegated_did: Did,
tx: Tx<i64>,
},
}
impl DelegationRequest {
fn routing(&self) -> Routing {
match self {
Self::IsDelegatedAccount { did, .. }
| Self::GetDelegationsForAccount {
delegated_did: did, ..
}
| Self::CountActiveControllers {
delegated_did: did, ..
}
| Self::GetAuditLogForAccount {
delegated_did: did, ..
}
| Self::CountAuditLogEntries {
delegated_did: did, ..
} => did_to_routing(did),
Self::CreateDelegation { delegated_did, .. }
| Self::RevokeDelegation { delegated_did, .. }
| Self::UpdateDelegationScopes { delegated_did, .. }
| Self::GetDelegation { delegated_did, .. }
| Self::LogDelegationAction { delegated_did, .. } => did_to_routing(delegated_did),
Self::GetAccountsControlledBy { controller_did, .. }
| Self::ControlsAnyAccounts {
did: controller_did,
..
} => did_to_routing(controller_did),
}
}
}
pub enum SsoRequest {
CreateExternalIdentity {
did: Did,
provider: tranquil_db_traits::SsoProviderType,
provider_user_id: String,
provider_username: Option<String>,
provider_email: Option<String>,
tx: Tx<Uuid>,
},
GetExternalIdentityByProvider {
provider: tranquil_db_traits::SsoProviderType,
provider_user_id: String,
tx: Tx<Option<tranquil_db_traits::ExternalIdentity>>,
},
GetExternalIdentitiesByDid {
did: Did,
tx: Tx<Vec<tranquil_db_traits::ExternalIdentity>>,
},
UpdateExternalIdentityLogin {
id: Uuid,
provider_username: Option<String>,
provider_email: Option<String>,
tx: Tx<()>,
},
DeleteExternalIdentity {
id: Uuid,
did: Did,
tx: Tx<bool>,
},
CreateSsoAuthState {
state: String,
request_uri: String,
provider: tranquil_db_traits::SsoProviderType,
action: tranquil_db_traits::SsoAction,
nonce: Option<String>,
code_verifier: Option<String>,
did: Option<Did>,
tx: Tx<()>,
},
ConsumeSsoAuthState {
state: String,
tx: Tx<Option<tranquil_db_traits::SsoAuthState>>,
},
CleanupExpiredSsoAuthStates {
tx: Tx<u64>,
},
CreatePendingRegistration {
token: String,
request_uri: String,
provider: tranquil_db_traits::SsoProviderType,
provider_user_id: String,
provider_username: Option<String>,
provider_email: Option<String>,
provider_email_verified: bool,
tx: Tx<()>,
},
GetPendingRegistration {
token: String,
tx: Tx<Option<tranquil_db_traits::SsoPendingRegistration>>,
},
ConsumePendingRegistration {
token: String,
tx: Tx<Option<tranquil_db_traits::SsoPendingRegistration>>,
},
CleanupExpiredPendingRegistrations {
tx: Tx<u64>,
},
}
impl SsoRequest {
fn routing(&self) -> Routing {
match self {
Self::CreateExternalIdentity { did, .. }
| Self::GetExternalIdentitiesByDid { did, .. }
| Self::DeleteExternalIdentity { did, .. } => did_to_routing(did),
Self::GetExternalIdentityByProvider { .. }
| Self::UpdateExternalIdentityLogin { .. }
| Self::ConsumeSsoAuthState { .. }
| Self::CreateSsoAuthState { .. }
| Self::CleanupExpiredSsoAuthStates { .. }
| Self::CreatePendingRegistration { .. }
| Self::GetPendingRegistration { .. }
| Self::ConsumePendingRegistration { .. }
| Self::CleanupExpiredPendingRegistrations { .. } => Routing::Global,
}
}
}
pub enum SessionRequest {
CreateSession {
data: tranquil_db_traits::SessionTokenCreate,
tx: Tx<SessionId>,
},
GetSessionByAccessJti {
access_jti: String,
tx: Tx<Option<tranquil_db_traits::SessionToken>>,
},
GetSessionForRefresh {
refresh_jti: String,
tx: Tx<Option<tranquil_db_traits::SessionForRefresh>>,
},
DeleteSessionByAccessJti {
access_jti: String,
did: Did,
tx: Tx<u64>,
},
DeleteSessionById {
session_id: SessionId,
did: Did,
tx: Tx<u64>,
},
DeleteSessionsByDid {
did: Did,
tx: Tx<u64>,
},
DeleteSessionsByDidExceptJti {
did: Did,
except_jti: String,
tx: Tx<u64>,
},
ListSessionsByDid {
did: Did,
tx: Tx<Vec<tranquil_db_traits::SessionListItem>>,
},
GetSessionAccessJtiById {
session_id: SessionId,
did: Did,
tx: Tx<Option<String>>,
},
DeleteSessionsByAppPassword {
did: Did,
app_password_name: String,
tx: Tx<u64>,
},
GetSessionJtisByAppPassword {
did: Did,
app_password_name: String,
tx: Tx<Vec<String>>,
},
LookupRefreshGrace {
refresh_jti: String,
tx: Tx<tranquil_db_traits::RefreshGraceLookup>,
},
ListAppPasswords {
user_id: Uuid,
tx: Tx<Vec<tranquil_db_traits::AppPasswordRecord>>,
},
GetAppPasswordsForLogin {
user_id: Uuid,
tx: Tx<Vec<tranquil_db_traits::AppPasswordRecord>>,
},
GetAppPasswordByName {
user_id: Uuid,
name: String,
tx: Tx<Option<tranquil_db_traits::AppPasswordRecord>>,
},
CreateAppPassword {
data: tranquil_db_traits::AppPasswordCreate,
tx: Tx<Uuid>,
},
DeleteAppPassword {
user_id: Uuid,
name: String,
tx: Tx<u64>,
},
DeleteAppPasswordsByController {
did: Did,
controller_did: Did,
tx: Tx<u64>,
},
GetLastReauthAt {
did: Did,
tx: Tx<Option<DateTime<Utc>>>,
},
UpdateLastReauth {
did: Did,
tx: Tx<DateTime<Utc>>,
},
GetSessionMfaStatus {
did: Did,
tx: Tx<Option<tranquil_db_traits::SessionMfaStatus>>,
},
UpdateMfaVerified {
did: Did,
tx: Tx<()>,
},
GetAppPasswordHashesByDid {
did: Did,
tx: Tx<Vec<String>>,
},
RefreshSessionAtomic {
data: tranquil_db_traits::SessionRefreshData,
tx: Tx<RefreshSessionResult>,
},
}
impl SessionRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::CreateSession { .. }
| Self::GetSessionByAccessJti { .. }
| Self::GetSessionForRefresh { .. }
| Self::LookupRefreshGrace { .. } => Routing::Global,
Self::DeleteSessionByAccessJti { did, .. }
| Self::DeleteSessionById { did, .. }
| Self::DeleteSessionsByDid { did, .. }
| Self::DeleteSessionsByDidExceptJti { did, .. }
| Self::ListSessionsByDid { did, .. }
| Self::GetSessionAccessJtiById { did, .. }
| Self::DeleteSessionsByAppPassword { did, .. }
| Self::GetSessionJtisByAppPassword { did, .. }
| Self::DeleteAppPasswordsByController { did, .. }
| Self::GetLastReauthAt { did, .. }
| Self::UpdateLastReauth { did, .. }
| Self::GetSessionMfaStatus { did, .. }
| Self::UpdateMfaVerified { did, .. }
| Self::GetAppPasswordHashesByDid { did, .. } => did_to_routing(did),
Self::RefreshSessionAtomic { data, .. } => did_to_routing(data.did.as_str()),
Self::ListAppPasswords { user_id, .. }
| Self::GetAppPasswordsForLogin { user_id, .. }
| Self::GetAppPasswordByName { user_id, .. }
| Self::CreateAppPassword {
data: tranquil_db_traits::AppPasswordCreate { user_id, .. },
..
}
| Self::DeleteAppPassword { user_id, .. } => uuid_to_routing(user_hashes, user_id),
}
}
}
pub enum UserRequest {
GetByDid {
did: Did,
tx: Tx<Option<UserRow>>,
},
GetByHandle {
handle: Handle,
tx: Tx<Option<UserRow>>,
},
GetWithKeyByDid {
did: Did,
tx: Tx<Option<UserWithKey>>,
},
GetStatusByDid {
did: Did,
tx: Tx<Option<UserStatus>>,
},
CountUsers {
tx: Tx<i64>,
},
GetSessionAccessExpiry {
did: Did,
access_jti: String,
tx: Tx<Option<DateTime<Utc>>>,
},
GetOAuthTokenWithUser {
token_id: String,
tx: Tx<Option<OAuthTokenWithUser>>,
},
GetUserInfoByDid {
did: Did,
tx: Tx<Option<UserInfoForAuth>>,
},
GetAnyAdminUserId {
tx: Tx<Option<Uuid>>,
},
SetInvitesDisabled {
did: Did,
disabled: bool,
tx: Tx<bool>,
},
SearchAccounts {
cursor_did: Option<Did>,
email_filter: Option<String>,
handle_filter: Option<String>,
limit: i64,
tx: Tx<Vec<AccountSearchResult>>,
},
GetAuthInfoByDid {
did: Did,
tx: Tx<Option<UserAuthInfo>>,
},
GetByEmail {
email: String,
tx: Tx<Option<UserForVerification>>,
},
GetLoginCheckByIdentifier {
identifier: String,
tx: Tx<Option<UserLoginCheck>>,
},
GetLoginInfoByIdentifier {
identifier: String,
tx: Tx<Option<UserLoginInfo>>,
},
Get2faStatusByDid {
did: Did,
tx: Tx<Option<User2faStatus>>,
},
GetCommsPrefs {
user_id: Uuid,
tx: Tx<Option<UserCommsPrefs>>,
},
GetIdByDid {
did: Did,
tx: Tx<Option<Uuid>>,
},
GetUserKeyById {
user_id: Uuid,
tx: Tx<Option<UserKeyInfo>>,
},
GetIdAndHandleByDid {
did: Did,
tx: Tx<Option<UserIdAndHandle>>,
},
GetDidWebInfoByHandle {
handle: Handle,
tx: Tx<Option<UserDidWebInfo>>,
},
GetDidWebOverrides {
user_id: Uuid,
tx: Tx<Option<DidWebOverrides>>,
},
GetHandleByDid {
did: Did,
tx: Tx<Option<Handle>>,
},
IsAccountActiveByDid {
did: Did,
tx: Tx<Option<bool>>,
},
GetUserForDeletion {
did: Did,
tx: Tx<Option<UserForDeletion>>,
},
CheckHandleExists {
handle: Handle,
exclude_user_id: Uuid,
tx: Tx<bool>,
},
UpdateHandle {
user_id: Uuid,
handle: Handle,
tx: Tx<()>,
},
GetUserWithKeyByDid {
did: Did,
tx: Tx<Option<UserKeyWithId>>,
},
IsAccountMigrated {
did: Did,
tx: Tx<bool>,
},
HasVerifiedCommsChannel {
did: Did,
tx: Tx<bool>,
},
GetIdByHandle {
handle: Handle,
tx: Tx<Option<Uuid>>,
},
GetEmailInfoByDid {
did: Did,
tx: Tx<Option<UserEmailInfo>>,
},
CheckEmailExists {
email: String,
exclude_user_id: Uuid,
tx: Tx<bool>,
},
UpdateEmail {
user_id: Uuid,
email: String,
tx: Tx<()>,
},
SetEmailVerified {
user_id: Uuid,
verified: bool,
tx: Tx<()>,
},
CheckEmailVerifiedByIdentifier {
identifier: String,
tx: Tx<Option<bool>>,
},
CheckChannelVerifiedByDid {
did: Did,
channel: CommsChannel,
tx: Tx<Option<bool>>,
},
AdminUpdateEmail {
did: Did,
email: String,
tx: Tx<u64>,
},
AdminUpdateHandle {
did: Did,
handle: Handle,
tx: Tx<u64>,
},
AdminUpdatePassword {
did: Did,
password_hash: String,
tx: Tx<u64>,
},
SetAdminStatus {
did: Did,
is_admin: bool,
tx: Tx<()>,
},
GetNotificationPrefs {
did: Did,
tx: Tx<Option<NotificationPrefs>>,
},
GetIdHandleEmailByDid {
did: Did,
tx: Tx<Option<UserIdHandleEmail>>,
},
UpdatePreferredCommsChannel {
did: Did,
channel: CommsChannel,
tx: Tx<()>,
},
ClearDiscord {
user_id: Uuid,
tx: Tx<()>,
},
ClearTelegram {
user_id: Uuid,
tx: Tx<()>,
},
ClearSignal {
user_id: Uuid,
tx: Tx<()>,
},
SetUnverifiedSignal {
user_id: Uuid,
signal_username: String,
tx: Tx<()>,
},
SetUnverifiedTelegram {
user_id: Uuid,
telegram_username: String,
tx: Tx<()>,
},
StoreTelegramChatId {
telegram_username: String,
chat_id: i64,
handle: Option<String>,
tx: Tx<Option<Uuid>>,
},
GetTelegramChatId {
user_id: Uuid,
tx: Tx<Option<i64>>,
},
SetUnverifiedDiscord {
user_id: Uuid,
discord_username: String,
tx: Tx<()>,
},
StoreDiscordUserId {
discord_username: String,
discord_id: String,
handle: Option<String>,
tx: Tx<Option<Uuid>>,
},
GetVerificationInfo {
did: Did,
tx: Tx<Option<UserVerificationInfo>>,
},
VerifyEmailChannel {
user_id: Uuid,
email: String,
tx: Tx<bool>,
},
VerifyDiscordChannel {
user_id: Uuid,
discord_id: String,
tx: Tx<()>,
},
VerifyTelegramChannel {
user_id: Uuid,
telegram_username: String,
tx: Tx<()>,
},
VerifySignalChannel {
user_id: Uuid,
signal_username: String,
tx: Tx<()>,
},
SetEmailVerifiedFlag {
user_id: Uuid,
tx: Tx<()>,
},
SetDiscordVerifiedFlag {
user_id: Uuid,
tx: Tx<()>,
},
SetTelegramVerifiedFlag {
user_id: Uuid,
tx: Tx<()>,
},
SetSignalVerifiedFlag {
user_id: Uuid,
tx: Tx<()>,
},
HasTotpEnabled {
did: Did,
tx: Tx<bool>,
},
HasPasskeys {
did: Did,
tx: Tx<bool>,
},
GetPasswordHashByDid {
did: Did,
tx: Tx<Option<String>>,
},
GetPasskeysForUser {
did: Did,
tx: Tx<Vec<StoredPasskey>>,
},
GetPasskeyByCredentialId {
credential_id: Vec<u8>,
tx: Tx<Option<StoredPasskey>>,
},
SavePasskey {
did: Did,
credential_id: Vec<u8>,
public_key: Vec<u8>,
friendly_name: Option<String>,
tx: Tx<Uuid>,
},
UpdatePasskeyCounter {
credential_id: Vec<u8>,
new_counter: i32,
tx: Tx<bool>,
},
DeletePasskey {
id: Uuid,
did: Did,
tx: Tx<bool>,
},
UpdatePasskeyName {
id: Uuid,
did: Did,
name: String,
tx: Tx<bool>,
},
SaveWebauthnChallenge {
did: Did,
challenge_type: WebauthnChallengeType,
state_json: String,
tx: Tx<Uuid>,
},
LoadWebauthnChallenge {
did: Did,
challenge_type: WebauthnChallengeType,
tx: Tx<Option<String>>,
},
DeleteWebauthnChallenge {
did: Did,
challenge_type: WebauthnChallengeType,
tx: Tx<()>,
},
SaveDiscoverableChallenge {
request_key: String,
state_json: String,
tx: Tx<Uuid>,
},
LoadDiscoverableChallenge {
request_key: String,
tx: Tx<Option<String>>,
},
DeleteDiscoverableChallenge {
request_key: String,
tx: Tx<()>,
},
GetTotpRecord {
did: Did,
tx: Tx<Option<TotpRecord>>,
},
GetTotpRecordState {
did: Did,
tx: Tx<Option<TotpRecordState>>,
},
UpsertTotpSecret {
did: Did,
secret_encrypted: Vec<u8>,
encryption_version: i32,
tx: Tx<()>,
},
SetTotpVerified {
did: Did,
tx: Tx<()>,
},
UpdateTotpLastUsed {
did: Did,
tx: Tx<()>,
},
DeleteTotp {
did: Did,
tx: Tx<()>,
},
GetUnusedBackupCodes {
did: Did,
tx: Tx<Vec<StoredBackupCode>>,
},
MarkBackupCodeUsed {
code_id: Uuid,
tx: Tx<bool>,
},
CountUnusedBackupCodes {
did: Did,
tx: Tx<i64>,
},
DeleteBackupCodes {
did: Did,
tx: Tx<u64>,
},
InsertBackupCodes {
did: Did,
code_hashes: Vec<String>,
tx: Tx<()>,
},
EnableTotpWithBackupCodes {
did: Did,
code_hashes: Vec<String>,
tx: Tx<()>,
},
DeleteTotpAndBackupCodes {
did: Did,
tx: Tx<()>,
},
ReplaceBackupCodes {
did: Did,
code_hashes: Vec<String>,
tx: Tx<()>,
},
GetSessionInfoByDid {
did: Did,
tx: Tx<Option<UserSessionInfo>>,
},
GetLegacyLoginPref {
did: Did,
tx: Tx<Option<UserLegacyLoginPref>>,
},
UpdateLegacyLogin {
did: Did,
allow: bool,
tx: Tx<bool>,
},
UpdateLocale {
did: Did,
locale: String,
tx: Tx<bool>,
},
GetLoginFullByIdentifier {
identifier: String,
tx: Tx<Option<UserLoginFull>>,
},
GetConfirmSignupByDid {
did: Did,
tx: Tx<Option<UserConfirmSignup>>,
},
GetResendVerificationByDid {
did: Did,
tx: Tx<Option<UserResendVerification>>,
},
SetChannelVerified {
did: Did,
channel: CommsChannel,
tx: Tx<()>,
},
GetIdByEmailOrHandle {
email: String,
handle: String,
tx: Tx<Option<Uuid>>,
},
CountAccountsByEmail {
email: String,
tx: Tx<i64>,
},
GetHandlesByEmail {
email: String,
tx: Tx<Vec<Handle>>,
},
SetPasswordResetCode {
user_id: Uuid,
code: String,
expires_at: DateTime<Utc>,
tx: Tx<()>,
},
GetUserByResetCode {
code: String,
tx: Tx<Option<UserResetCodeInfo>>,
},
ClearPasswordResetCode {
user_id: Uuid,
tx: Tx<()>,
},
GetIdAndPasswordHashByDid {
did: Did,
tx: Tx<Option<UserIdAndPasswordHash>>,
},
UpdatePasswordHash {
user_id: Uuid,
password_hash: String,
tx: Tx<()>,
},
ResetPasswordWithSessions {
user_id: Uuid,
password_hash: String,
tx: Tx<PasswordResetResult>,
},
ActivateAccount {
did: Did,
tx: Tx<bool>,
},
DeactivateAccount {
did: Did,
delete_after: Option<DateTime<Utc>>,
tx: Tx<bool>,
},
HasPasswordByDid {
did: Did,
tx: Tx<Option<bool>>,
},
GetPasswordInfoByDid {
did: Did,
tx: Tx<Option<UserPasswordInfo>>,
},
RemoveUserPassword {
user_id: Uuid,
tx: Tx<()>,
},
SetNewUserPassword {
user_id: Uuid,
password_hash: String,
tx: Tx<()>,
},
GetUserKeyByDid {
did: Did,
tx: Tx<Option<UserKeyInfo>>,
},
DeleteAccountComplete {
user_id: Uuid,
did: Did,
tx: Tx<()>,
},
SetUserTakedown {
did: Did,
takedown_ref: Option<String>,
tx: Tx<bool>,
},
AdminDeleteAccountComplete {
user_id: Uuid,
did: Did,
tx: Tx<()>,
},
GetUserForDidDoc {
did: Did,
tx: Tx<Option<UserForDidDoc>>,
},
GetUserForDidDocBuild {
did: Did,
tx: Tx<Option<UserForDidDocBuild>>,
},
UpsertDidWebOverrides {
user_id: Uuid,
verification_methods: Option<serde_json::Value>,
also_known_as: Option<Vec<String>>,
tx: Tx<()>,
},
UpdateMigratedToPds {
did: Did,
endpoint: String,
tx: Tx<()>,
},
GetUserForPasskeySetup {
did: Did,
tx: Tx<Option<UserForPasskeySetup>>,
},
GetUserForPasskeyRecovery {
identifier: String,
normalized_handle: String,
tx: Tx<Option<UserForPasskeyRecovery>>,
},
SetRecoveryToken {
did: Did,
token_hash: String,
expires_at: DateTime<Utc>,
tx: Tx<()>,
},
GetUserForRecovery {
did: Did,
tx: Tx<Option<UserForRecovery>>,
},
GetAccountsScheduledForDeletion {
limit: i64,
tx: Tx<Vec<ScheduledDeletionAccount>>,
},
DeleteAccountWithFirehose {
user_id: Uuid,
did: Did,
tx: Tx<()>,
},
CreatePasswordAccount {
input: CreatePasswordAccountInput,
tx: oneshot::Sender<Result<CreatePasswordAccountResult, CreateAccountError>>,
},
CreateDelegatedAccount {
input: CreateDelegatedAccountInput,
tx: oneshot::Sender<Result<Uuid, CreateAccountError>>,
},
CreatePasskeyAccount {
input: CreatePasskeyAccountInput,
tx: oneshot::Sender<Result<CreatePasswordAccountResult, CreateAccountError>>,
},
CreateSsoAccount {
input: CreateSsoAccountInput,
tx: oneshot::Sender<Result<CreatePasswordAccountResult, CreateAccountError>>,
},
ReactivateMigrationAccount {
input: MigrationReactivationInput,
tx: oneshot::Sender<Result<ReactivatedAccountInfo, MigrationReactivationError>>,
},
CheckHandleAvailableForNewAccount {
handle: Handle,
tx: Tx<bool>,
},
ReserveHandle {
handle: Handle,
reserved_by: String,
tx: Tx<bool>,
},
ReleaseHandleReservation {
handle: Handle,
tx: Tx<()>,
},
CleanupExpiredHandleReservations {
tx: Tx<u64>,
},
CompletePasskeySetup {
input: CompletePasskeySetupInput,
tx: Tx<()>,
},
RecoverPasskeyAccount {
input: RecoverPasskeyAccountInput,
tx: Tx<RecoverPasskeyAccountResult>,
},
GetPasswordResetInfo {
email: String,
tx: Tx<Option<tranquil_db_traits::PasswordResetInfo>>,
},
EnableTotpVerified {
did: Did,
encrypted_secret: Vec<u8>,
tx: Tx<()>,
},
SetTwoFactorEnabled {
did: Did,
enabled: bool,
tx: Tx<()>,
},
ExpirePasswordResetCode {
email: String,
tx: Tx<()>,
},
}
impl UserRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::GetByDid { did, .. }
| Self::GetWithKeyByDid { did, .. }
| Self::GetStatusByDid { did, .. }
| Self::GetSessionAccessExpiry { did, .. }
| Self::GetUserInfoByDid { did, .. }
| Self::SetInvitesDisabled { did, .. }
| Self::GetAuthInfoByDid { did, .. }
| Self::Get2faStatusByDid { did, .. }
| Self::GetIdByDid { did, .. }
| Self::GetIdAndHandleByDid { did, .. }
| Self::GetHandleByDid { did, .. }
| Self::IsAccountActiveByDid { did, .. }
| Self::GetUserForDeletion { did, .. }
| Self::GetUserWithKeyByDid { did, .. }
| Self::IsAccountMigrated { did, .. }
| Self::HasVerifiedCommsChannel { did, .. }
| Self::GetEmailInfoByDid { did, .. }
| Self::CheckChannelVerifiedByDid { did, .. }
| Self::AdminUpdateEmail { did, .. }
| Self::AdminUpdateHandle { did, .. }
| Self::AdminUpdatePassword { did, .. }
| Self::SetAdminStatus { did, .. }
| Self::GetNotificationPrefs { did, .. }
| Self::GetIdHandleEmailByDid { did, .. }
| Self::UpdatePreferredCommsChannel { did, .. }
| Self::GetVerificationInfo { did, .. }
| Self::HasTotpEnabled { did, .. }
| Self::HasPasskeys { did, .. }
| Self::GetPasswordHashByDid { did, .. }
| Self::GetPasskeysForUser { did, .. }
| Self::SavePasskey { did, .. }
| Self::DeletePasskey { did, .. }
| Self::UpdatePasskeyName { did, .. }
| Self::SaveWebauthnChallenge { did, .. }
| Self::LoadWebauthnChallenge { did, .. }
| Self::DeleteWebauthnChallenge { did, .. }
| Self::GetTotpRecord { did, .. }
| Self::GetTotpRecordState { did, .. }
| Self::UpsertTotpSecret { did, .. }
| Self::SetTotpVerified { did, .. }
| Self::UpdateTotpLastUsed { did, .. }
| Self::DeleteTotp { did, .. }
| Self::GetUnusedBackupCodes { did, .. }
| Self::CountUnusedBackupCodes { did, .. }
| Self::DeleteBackupCodes { did, .. }
| Self::InsertBackupCodes { did, .. }
| Self::EnableTotpWithBackupCodes { did, .. }
| Self::DeleteTotpAndBackupCodes { did, .. }
| Self::ReplaceBackupCodes { did, .. }
| Self::GetSessionInfoByDid { did, .. }
| Self::GetLegacyLoginPref { did, .. }
| Self::UpdateLegacyLogin { did, .. }
| Self::UpdateLocale { did, .. }
| Self::GetConfirmSignupByDid { did, .. }
| Self::GetResendVerificationByDid { did, .. }
| Self::SetChannelVerified { did, .. }
| Self::GetIdAndPasswordHashByDid { did, .. }
| Self::ActivateAccount { did, .. }
| Self::DeactivateAccount { did, .. }
| Self::HasPasswordByDid { did, .. }
| Self::GetPasswordInfoByDid { did, .. }
| Self::GetUserKeyByDid { did, .. }
| Self::DeleteAccountComplete { did, .. }
| Self::SetUserTakedown { did, .. }
| Self::AdminDeleteAccountComplete { did, .. }
| Self::GetUserForDidDoc { did, .. }
| Self::GetUserForDidDocBuild { did, .. }
| Self::UpdateMigratedToPds { did, .. }
| Self::GetUserForPasskeySetup { did, .. }
| Self::GetUserForRecovery { did, .. }
| Self::DeleteAccountWithFirehose { did, .. }
| Self::CreatePasswordAccount {
input: CreatePasswordAccountInput { did, .. },
..
}
| Self::CreateDelegatedAccount {
input: CreateDelegatedAccountInput { did, .. },
..
}
| Self::CreatePasskeyAccount {
input: CreatePasskeyAccountInput { did, .. },
..
}
| Self::CreateSsoAccount {
input: CreateSsoAccountInput { did, .. },
..
}
| Self::ReactivateMigrationAccount {
input: MigrationReactivationInput { did, .. },
..
}
| Self::CompletePasskeySetup {
input: CompletePasskeySetupInput { did, .. },
..
}
| Self::RecoverPasskeyAccount {
input: RecoverPasskeyAccountInput { did, .. },
..
}
| Self::SetRecoveryToken { did, .. }
| Self::EnableTotpVerified { did, .. }
| Self::SetTwoFactorEnabled { did, .. } => did_to_routing(did),
Self::GetCommsPrefs { user_id, .. }
| Self::GetUserKeyById { user_id, .. }
| Self::GetDidWebOverrides { user_id, .. }
| Self::UpdateHandle { user_id, .. }
| Self::UpdateEmail { user_id, .. }
| Self::SetEmailVerified { user_id, .. }
| Self::ClearDiscord { user_id, .. }
| Self::ClearTelegram { user_id, .. }
| Self::ClearSignal { user_id, .. }
| Self::SetUnverifiedSignal { user_id, .. }
| Self::SetUnverifiedTelegram { user_id, .. }
| Self::GetTelegramChatId { user_id, .. }
| Self::SetUnverifiedDiscord { user_id, .. }
| Self::VerifyEmailChannel { user_id, .. }
| Self::VerifyDiscordChannel { user_id, .. }
| Self::VerifyTelegramChannel { user_id, .. }
| Self::VerifySignalChannel { user_id, .. }
| Self::SetEmailVerifiedFlag { user_id, .. }
| Self::SetDiscordVerifiedFlag { user_id, .. }
| Self::SetTelegramVerifiedFlag { user_id, .. }
| Self::SetSignalVerifiedFlag { user_id, .. }
| Self::SetPasswordResetCode { user_id, .. }
| Self::ClearPasswordResetCode { user_id, .. }
| Self::UpdatePasswordHash { user_id, .. }
| Self::ResetPasswordWithSessions { user_id, .. }
| Self::RemoveUserPassword { user_id, .. }
| Self::SetNewUserPassword { user_id, .. }
| Self::UpsertDidWebOverrides { user_id, .. } => uuid_to_routing(user_hashes, user_id),
Self::CheckHandleExists {
exclude_user_id, ..
}
| Self::CheckEmailExists {
exclude_user_id, ..
} => uuid_to_routing(user_hashes, exclude_user_id),
Self::MarkBackupCodeUsed { .. } => Routing::Global,
Self::GetByHandle { .. }
| Self::GetDidWebInfoByHandle { .. }
| Self::GetIdByHandle { .. }
| Self::CheckHandleAvailableForNewAccount { .. }
| Self::ReserveHandle { .. }
| Self::ReleaseHandleReservation { .. } => Routing::Global,
Self::CountUsers { .. }
| Self::GetOAuthTokenWithUser { .. }
| Self::GetAnyAdminUserId { .. }
| Self::SearchAccounts { .. }
| Self::GetByEmail { .. }
| Self::GetLoginCheckByIdentifier { .. }
| Self::GetLoginInfoByIdentifier { .. }
| Self::CheckEmailVerifiedByIdentifier { .. }
| Self::StoreTelegramChatId { .. }
| Self::StoreDiscordUserId { .. }
| Self::GetPasskeyByCredentialId { .. }
| Self::UpdatePasskeyCounter { .. }
| Self::GetLoginFullByIdentifier { .. }
| Self::GetIdByEmailOrHandle { .. }
| Self::CountAccountsByEmail { .. }
| Self::GetHandlesByEmail { .. }
| Self::GetUserByResetCode { .. }
| Self::GetUserForPasskeyRecovery { .. }
| Self::GetAccountsScheduledForDeletion { .. }
| Self::CleanupExpiredHandleReservations { .. }
| Self::GetPasswordResetInfo { .. }
| Self::ExpirePasswordResetCode { .. }
| Self::SaveDiscoverableChallenge { .. }
| Self::LoadDiscoverableChallenge { .. }
| Self::DeleteDiscoverableChallenge { .. } => Routing::Global,
}
}
}
pub enum InfraRequest {
EnqueueComms {
user_id: Option<Uuid>,
channel: CommsChannel,
comms_type: CommsType,
recipient: String,
subject: Option<String>,
body: String,
metadata: Option<serde_json::Value>,
tx: Tx<Uuid>,
},
FetchPendingComms {
now: DateTime<Utc>,
batch_size: i64,
tx: Tx<Vec<QueuedComms>>,
},
MarkCommsSent {
id: Uuid,
tx: Tx<()>,
},
MarkCommsFailed {
id: Uuid,
error: String,
tx: Tx<()>,
},
MarkCommsFailedPermanent {
id: Uuid,
error: String,
tx: Tx<()>,
},
CreateInviteCode {
code: InviteCode,
use_count: i32,
for_account: Did,
tx: Tx<bool>,
},
CreateInviteCodesBatch {
codes: Vec<InviteCode>,
use_count: i32,
created_by_user: Uuid,
for_account: Did,
tx: Tx<()>,
},
GetInviteCodeAvailableUses {
code: InviteCode,
tx: Tx<Option<i32>>,
},
ValidateInviteCode {
code: InviteCode,
tx: oneshot::Sender<Result<(), InviteCodeError>>,
},
GetInviteCodesForAccount {
for_account: Did,
tx: Tx<Vec<InviteCodeInfo>>,
},
GetInviteCodeUses {
code: String,
tx: Tx<Vec<InviteCodeUse>>,
},
DisableInviteCodesByCode {
codes: Vec<String>,
tx: Tx<()>,
},
DisableInviteCodesByAccount {
accounts: Vec<Did>,
tx: Tx<()>,
},
ListInviteCodes {
cursor: Option<String>,
limit: i64,
sort: InviteCodeSortOrder,
tx: Tx<Vec<InviteCodeRow>>,
},
GetUserDidsByIds {
user_ids: Vec<Uuid>,
tx: Tx<Vec<(Uuid, Did)>>,
},
GetInviteCodeUsesBatch {
codes: Vec<String>,
tx: Tx<Vec<InviteCodeUse>>,
},
GetInvitesCreatedByUser {
user_id: Uuid,
tx: Tx<Vec<InviteCodeInfo>>,
},
GetInviteCodeInfo {
code: String,
tx: Tx<Option<InviteCodeInfo>>,
},
GetInviteCodesByUsers {
user_ids: Vec<Uuid>,
tx: Tx<Vec<(Uuid, InviteCodeInfo)>>,
},
GetInviteCodeUsedByUser {
user_id: Uuid,
tx: Tx<Option<String>>,
},
DeleteInviteCodeUsesByUser {
user_id: Uuid,
tx: Tx<()>,
},
DeleteInviteCodesByUser {
user_id: Uuid,
tx: Tx<()>,
},
ReserveSigningKey {
did: Option<Did>,
public_key_did_key: String,
private_key_bytes: Vec<u8>,
expires_at: DateTime<Utc>,
tx: Tx<Uuid>,
},
GetReservedSigningKey {
public_key_did_key: String,
tx: Tx<Option<ReservedSigningKey>>,
},
MarkSigningKeyUsed {
key_id: Uuid,
tx: Tx<()>,
},
CreateDeletionRequest {
token: String,
did: Did,
expires_at: DateTime<Utc>,
tx: Tx<()>,
},
GetDeletionRequest {
token: String,
tx: Tx<Option<DeletionRequest>>,
},
DeleteDeletionRequest {
token: String,
tx: Tx<()>,
},
DeleteDeletionRequestsByDid {
did: Did,
tx: Tx<()>,
},
UpsertAccountPreference {
user_id: Uuid,
name: String,
value_json: serde_json::Value,
tx: Tx<()>,
},
InsertAccountPreferenceIfNotExists {
user_id: Uuid,
name: String,
value_json: serde_json::Value,
tx: Tx<()>,
},
GetServerConfig {
key: String,
tx: Tx<Option<String>>,
},
InsertReport {
id: i64,
reason_type: String,
reason: Option<String>,
subject_json: serde_json::Value,
reported_by_did: Did,
created_at: DateTime<Utc>,
tx: Tx<()>,
},
DeletePlcTokensForUser {
user_id: Uuid,
tx: Tx<()>,
},
InsertPlcToken {
user_id: Uuid,
token: String,
expires_at: DateTime<Utc>,
tx: Tx<()>,
},
GetPlcTokenExpiry {
user_id: Uuid,
token: String,
tx: Tx<Option<DateTime<Utc>>>,
},
DeletePlcToken {
user_id: Uuid,
token: String,
tx: Tx<()>,
},
GetAccountPreferences {
user_id: Uuid,
tx: Tx<Vec<(String, serde_json::Value)>>,
},
ReplaceNamespacePreferences {
user_id: Uuid,
namespace: String,
preferences: Vec<(String, serde_json::Value)>,
tx: Tx<()>,
},
GetNotificationHistory {
user_id: Uuid,
limit: i64,
tx: Tx<Vec<NotificationHistoryRow>>,
},
GetServerConfigs {
keys: Vec<String>,
tx: Tx<Vec<(String, String)>>,
},
UpsertServerConfig {
key: String,
value: String,
tx: Tx<()>,
},
DeleteServerConfig {
key: String,
tx: Tx<()>,
},
GetBlobStorageKeyByCid {
cid: CidLink,
tx: Tx<Option<String>>,
},
DeleteBlobByCid {
cid: CidLink,
tx: Tx<()>,
},
GetAdminAccountInfoByDid {
did: Did,
tx: Tx<Option<AdminAccountInfo>>,
},
GetAdminAccountInfosByDids {
dids: Vec<Did>,
tx: Tx<Vec<AdminAccountInfo>>,
},
GetInviteCodeUsesByUsers {
user_ids: Vec<Uuid>,
tx: Tx<Vec<(Uuid, String)>>,
},
GetDeletionRequestByDid {
did: Did,
tx: Tx<Option<DeletionRequestWithToken>>,
},
GetLatestCommsForUser {
user_id: Uuid,
comms_type: CommsType,
limit: i64,
tx: Tx<Vec<QueuedComms>>,
},
CountCommsByType {
user_id: Uuid,
comms_type: CommsType,
tx: Tx<i64>,
},
DeleteCommsByTypeForUser {
user_id: Uuid,
comms_type: CommsType,
tx: Tx<u64>,
},
ExpireDeletionRequest {
token: String,
tx: Tx<()>,
},
GetReservedSigningKeyFull {
public_key_did_key: String,
tx: Tx<Option<ReservedSigningKeyFull>>,
},
GetPlcTokensByDid {
did: Did,
tx: Tx<Vec<PlcTokenInfo>>,
},
CountPlcTokensByDid {
did: Did,
tx: Tx<i64>,
},
}
impl InfraRequest {
fn routing(&self, user_hashes: &UserHashMap) -> Routing {
match self {
Self::UpsertAccountPreference { user_id, .. }
| Self::InsertAccountPreferenceIfNotExists { user_id, .. }
| Self::GetAccountPreferences { user_id, .. }
| Self::ReplaceNamespacePreferences { user_id, .. }
| Self::GetNotificationHistory { user_id, .. }
| Self::DeletePlcTokensForUser { user_id, .. }
| Self::InsertPlcToken { user_id, .. }
| Self::GetPlcTokenExpiry { user_id, .. }
| Self::DeletePlcToken { user_id, .. }
| Self::GetInvitesCreatedByUser { user_id, .. }
| Self::GetInviteCodeUsedByUser { user_id, .. }
| Self::DeleteInviteCodeUsesByUser { user_id, .. }
| Self::DeleteInviteCodesByUser { user_id, .. }
| Self::GetLatestCommsForUser { user_id, .. }
| Self::CountCommsByType { user_id, .. }
| Self::DeleteCommsByTypeForUser { user_id, .. } => {
uuid_to_routing(user_hashes, user_id)
}
Self::CreateInviteCodesBatch {
created_by_user, ..
} => uuid_to_routing(user_hashes, created_by_user),
Self::EnqueueComms {
user_id: Some(uid), ..
} => uuid_to_routing(user_hashes, uid),
Self::GetInviteCodesForAccount { for_account, .. } => {
did_to_routing(for_account.as_str())
}
Self::DeleteDeletionRequestsByDid { did, .. }
| Self::CreateDeletionRequest { did, .. }
| Self::GetAdminAccountInfoByDid { did, .. }
| Self::GetDeletionRequestByDid { did, .. }
| Self::GetPlcTokensByDid { did, .. }
| Self::CountPlcTokensByDid { did, .. } => did_to_routing(did),
Self::GetBlobStorageKeyByCid { cid, .. } | Self::DeleteBlobByCid { cid, .. } => {
cid_to_routing(cid)
}
_ => Routing::Global,
}
}
}
pub enum OAuthRequest {
CreateToken {
data: TokenData,
tx: Tx<TokenFamilyId>,
},
GetTokenById {
token_id: TokenId,
tx: Tx<Option<TokenData>>,
},
GetTokenByRefreshToken {
refresh_token: RefreshToken,
tx: Tx<Option<(TokenFamilyId, TokenData)>>,
},
GetTokenByPreviousRefreshToken {
refresh_token: RefreshToken,
tx: Tx<Option<(TokenFamilyId, TokenData)>>,
},
RotateToken {
old_db_id: TokenFamilyId,
new_refresh_token: RefreshToken,
new_expires_at: DateTime<Utc>,
tx: Tx<()>,
},
CheckRefreshTokenUsed {
refresh_token: RefreshToken,
tx: Tx<Option<TokenFamilyId>>,
},
DeleteToken {
token_id: TokenId,
tx: Tx<()>,
},
DeleteTokenFamily {
db_id: TokenFamilyId,
tx: Tx<()>,
},
ListTokensForUser {
did: Did,
tx: Tx<Vec<TokenData>>,
},
CountTokensForUser {
did: Did,
tx: Tx<i64>,
},
DeleteOldestTokensForUser {
did: Did,
keep_count: i64,
tx: Tx<u64>,
},
RevokeTokensForClient {
did: Did,
client_id: ClientId,
tx: Tx<u64>,
},
RevokeTokensForController {
delegated_did: Did,
controller_did: Did,
tx: Tx<u64>,
},
CreateAuthorizationRequest {
request_id: RequestId,
data: RequestData,
tx: Tx<()>,
},
GetAuthorizationRequest {
request_id: RequestId,
tx: Tx<Option<RequestData>>,
},
SetAuthorizationDid {
request_id: RequestId,
did: Did,
device_id: Option<DeviceId>,
tx: Tx<()>,
},
UpdateAuthorizationRequest {
request_id: RequestId,
did: Did,
device_id: Option<DeviceId>,
code: AuthorizationCode,
tx: Tx<()>,
},
ConsumeAuthorizationRequestByCode {
code: AuthorizationCode,
tx: Tx<Option<RequestData>>,
},
DeleteAuthorizationRequest {
request_id: RequestId,
tx: Tx<()>,
},
DeleteExpiredAuthorizationRequests {
tx: Tx<u64>,
},
ExtendAuthorizationRequestExpiry {
request_id: RequestId,
new_expires_at: DateTime<Utc>,
tx: Tx<bool>,
},
MarkRequestAuthenticated {
request_id: RequestId,
did: Did,
device_id: Option<DeviceId>,
tx: Tx<()>,
},
UpdateRequestScope {
request_id: RequestId,
scope: String,
tx: Tx<()>,
},
SetControllerDid {
request_id: RequestId,
controller_did: Did,
tx: Tx<()>,
},
SetRequestDid {
request_id: RequestId,
did: Did,
tx: Tx<()>,
},
CreateDevice {
device_id: DeviceId,
data: DeviceData,
tx: Tx<()>,
},
GetDevice {
device_id: DeviceId,
tx: Tx<Option<DeviceData>>,
},
UpdateDeviceLastSeen {
device_id: DeviceId,
tx: Tx<()>,
},
DeleteDevice {
device_id: DeviceId,
tx: Tx<()>,
},
UpsertAccountDevice {
did: Did,
device_id: DeviceId,
tx: Tx<()>,
},
GetDeviceAccounts {
device_id: DeviceId,
tx: Tx<Vec<tranquil_db_traits::DeviceAccountRow>>,
},
VerifyAccountOnDevice {
device_id: DeviceId,
did: Did,
tx: Tx<bool>,
},
CheckAndRecordDpopJti {
jti: DPoPProofId,
tx: Tx<bool>,
},
CleanupExpiredDpopJtis {
max_age_secs: i64,
tx: Tx<u64>,
},
Create2faChallenge {
did: Did,
request_uri: RequestId,
tx: Tx<tranquil_db_traits::TwoFactorChallenge>,
},
Get2faChallenge {
request_uri: RequestId,
tx: Tx<Option<tranquil_db_traits::TwoFactorChallenge>>,
},
Increment2faAttempts {
id: Uuid,
tx: Tx<i32>,
},
Delete2faChallenge {
id: Uuid,
tx: Tx<()>,
},
Delete2faChallengeByRequestUri {
request_uri: RequestId,
tx: Tx<()>,
},
CleanupExpired2faChallenges {
tx: Tx<u64>,
},
CheckUser2faEnabled {
did: Did,
tx: Tx<bool>,
},
GetScopePreferences {
did: Did,
client_id: ClientId,
tx: Tx<Vec<ScopePreference>>,
},
UpsertScopePreferences {
did: Did,
client_id: ClientId,
prefs: Vec<ScopePreference>,
tx: Tx<()>,
},
DeleteScopePreferences {
did: Did,
client_id: ClientId,
tx: Tx<()>,
},
UpsertAuthorizedClient {
did: Did,
client_id: ClientId,
data: AuthorizedClientData,
tx: Tx<()>,
},
GetAuthorizedClient {
did: Did,
client_id: ClientId,
tx: Tx<Option<AuthorizedClientData>>,
},
ListTrustedDevices {
did: Did,
tx: Tx<Vec<tranquil_db_traits::TrustedDeviceRow>>,
},
GetDeviceTrustInfo {
device_id: DeviceId,
did: Did,
tx: Tx<Option<tranquil_db_traits::DeviceTrustInfo>>,
},
DeviceBelongsToUser {
device_id: DeviceId,
did: Did,
tx: Tx<bool>,
},
RevokeDeviceTrust {
device_id: DeviceId,
did: Did,
tx: Tx<()>,
},
UpdateDeviceFriendlyName {
device_id: DeviceId,
did: Did,
friendly_name: Option<String>,
tx: Tx<()>,
},
TrustDevice {
device_id: DeviceId,
did: Did,
trusted_at: DateTime<Utc>,
trusted_until: DateTime<Utc>,
tx: Tx<()>,
},
ExtendDeviceTrust {
device_id: DeviceId,
did: Did,
trusted_until: DateTime<Utc>,
tx: Tx<()>,
},
ListSessionsByDid {
did: Did,
tx: Tx<Vec<tranquil_db_traits::OAuthSessionListItem>>,
},
DeleteSessionById {
session_id: TokenFamilyId,
did: Did,
tx: Tx<u64>,
},
DeleteSessionsByDid {
did: Did,
tx: Tx<u64>,
},
DeleteSessionsByDidExcept {
did: Did,
except_token_id: TokenId,
tx: Tx<u64>,
},
Get2faChallengeCode {
request_uri: RequestId,
tx: Tx<Option<String>>,
},
}
impl OAuthRequest {
fn routing(&self) -> Routing {
match self {
Self::ListTokensForUser { did, .. }
| Self::CountTokensForUser { did, .. }
| Self::DeleteOldestTokensForUser { did, .. }
| Self::RevokeTokensForClient { did, .. }
| Self::Create2faChallenge { did, .. }
| Self::CheckUser2faEnabled { did, .. }
| Self::GetScopePreferences { did, .. }
| Self::UpsertScopePreferences { did, .. }
| Self::DeleteScopePreferences { did, .. }
| Self::UpsertAuthorizedClient { did, .. }
| Self::GetAuthorizedClient { did, .. }
| Self::ListTrustedDevices { did, .. }
| Self::GetDeviceTrustInfo { did, .. }
| Self::DeviceBelongsToUser { did, .. }
| Self::UpsertAccountDevice { did, .. }
| Self::VerifyAccountOnDevice { did, .. }
| Self::ListSessionsByDid { did, .. }
| Self::DeleteSessionsByDid { did, .. }
| Self::DeleteSessionsByDidExcept { did, .. }
| Self::DeleteSessionById { did, .. } => did_to_routing(did),
Self::RevokeTokensForController { delegated_did, .. } => did_to_routing(delegated_did),
Self::SetAuthorizationDid { did, .. }
| Self::UpdateAuthorizationRequest { did, .. }
| Self::MarkRequestAuthenticated { did, .. }
| Self::SetRequestDid { did, .. } => did_to_routing(did),
Self::CreateToken { .. }
| Self::GetTokenById { .. }
| Self::GetTokenByRefreshToken { .. }
| Self::GetTokenByPreviousRefreshToken { .. }
| Self::RotateToken { .. }
| Self::CheckRefreshTokenUsed { .. }
| Self::DeleteToken { .. }
| Self::DeleteTokenFamily { .. }
| Self::CreateAuthorizationRequest { .. }
| Self::GetAuthorizationRequest { .. }
| Self::ConsumeAuthorizationRequestByCode { .. }
| Self::DeleteAuthorizationRequest { .. }
| Self::DeleteExpiredAuthorizationRequests { .. }
| Self::ExtendAuthorizationRequestExpiry { .. }
| Self::UpdateRequestScope { .. }
| Self::SetControllerDid { .. }
| Self::CreateDevice { .. }
| Self::GetDevice { .. }
| Self::UpdateDeviceLastSeen { .. }
| Self::DeleteDevice { .. }
| Self::GetDeviceAccounts { .. }
| Self::CheckAndRecordDpopJti { .. }
| Self::CleanupExpiredDpopJtis { .. }
| Self::Get2faChallenge { .. }
| Self::Increment2faAttempts { .. }
| Self::Delete2faChallenge { .. }
| Self::Delete2faChallengeByRequestUri { .. }
| Self::CleanupExpired2faChallenges { .. }
| Self::RevokeDeviceTrust { .. }
| Self::UpdateDeviceFriendlyName { .. }
| Self::TrustDevice { .. }
| Self::ExtendDeviceTrust { .. }
| Self::Get2faChallengeCode { .. } => Routing::Global,
}
}
}
fn convert_repo_info(
r: super::repo_ops::RepoInfo,
) -> Result<tranquil_db_traits::RepoInfo, DbError> {
Ok(tranquil_db_traits::RepoInfo {
user_id: r.user_id,
repo_root_cid: r.repo_root_cid,
repo_rev: r
.repo_rev
.map(|rev| {
Tid::new(rev).map_err(|_| DbError::CorruptData("corrupt repo_meta repo_rev"))
})
.transpose()?,
})
}
fn convert_repo_account(
r: super::repo_ops::RepoAccountEntry,
) -> tranquil_db_traits::RepoAccountInfo {
tranquil_db_traits::RepoAccountInfo {
user_id: r.user_id,
did: r.did,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
repo_root_cid: r.repo_root_cid,
}
}
fn convert_repo_list_entry(
r: super::repo_ops::RepoListEntry,
) -> Result<tranquil_db_traits::RepoListItem, DbError> {
let did = r
.did
.ok_or(DbError::CorruptData("repo_meta missing DID field"))?;
Ok(tranquil_db_traits::RepoListItem {
did: Did::new(did).map_err(|_| DbError::CorruptData("corrupt repo_meta did"))?,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
repo_root_cid: r.repo_root_cid,
repo_rev: r
.repo_rev
.map(|rev| {
Tid::new(rev).map_err(|_| DbError::CorruptData("corrupt repo_meta repo_rev"))
})
.transpose()?,
})
}
fn convert_record_info(r: super::record_ops::RecordInfo) -> tranquil_db_traits::RecordInfo {
tranquil_db_traits::RecordInfo {
rkey: r.rkey,
record_cid: r.record_cid,
}
}
fn convert_full_record_info(
r: super::record_ops::FullRecordInfo,
) -> tranquil_db_traits::FullRecordInfo {
tranquil_db_traits::FullRecordInfo {
collection: r.collection,
rkey: r.rkey,
record_cid: r.record_cid,
}
}
fn convert_record_with_takedown(
r: super::record_ops::RecordWithTakedown,
) -> tranquil_db_traits::RecordWithTakedown {
tranquil_db_traits::RecordWithTakedown {
id: r.id,
takedown_ref: r.takedown_ref,
}
}
fn convert_without_rev(
r: super::repo_ops::RepoWithoutRevEntry,
) -> tranquil_db_traits::RepoWithoutRev {
tranquil_db_traits::RepoWithoutRev {
user_id: r.user_id,
repo_root_cid: r.repo_root_cid,
}
}
struct HandlerState<S: StorageIO> {
metastore: Metastore,
event_ops: EventOps<S>,
commit_ops: CommitOps<S>,
}
fn dispatch_repo<S: StorageIO>(state: &HandlerState<S>, req: RepoRequest) {
match req {
RepoRequest::CreateRepoFull {
user_id,
did,
handle,
repo_root_cid,
repo_rev,
tx,
} => {
let result = state
.metastore
.repo_ops()
.create_repo(
state.metastore.database(),
user_id,
&did,
&handle,
&repo_root_cid,
&repo_rev,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::UpdateRepoRoot {
user_id,
repo_root_cid,
repo_rev,
tx,
} => {
let result = state
.metastore
.repo_ops()
.update_repo_root(
state.metastore.database(),
user_id,
&repo_root_cid,
&repo_rev,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::UpdateRepoRev {
user_id,
repo_rev,
tx,
} => {
let result = state
.metastore
.repo_ops()
.update_repo_rev(state.metastore.database(), user_id, &repo_rev)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::DeleteRepo { user_id, tx } => {
let result = state
.metastore
.repo_ops()
.delete_repo(state.metastore.database(), user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::GetRepoRootForUpdate { user_id, tx } => {
let result = state
.metastore
.repo_ops()
.get_repo_root_for_update(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::GetRepo { user_id, tx } => {
let result = state
.metastore
.repo_ops()
.get_repo(user_id)
.map_err(metastore_to_db)
.and_then(|opt| opt.map(convert_repo_info).transpose());
let _ = tx.send(result);
}
RepoRequest::GetRepoRootByDid { did, tx } => {
let result = state
.metastore
.repo_ops()
.get_repo_root_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::CountRepos { tx } => {
let result = state
.metastore
.repo_ops()
.count_repos()
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::GetReposWithoutRev { tx } => {
let result = state
.metastore
.repo_ops()
.get_repos_without_rev(MAX_REPOS_WITHOUT_REV)
.map(|v| v.into_iter().map(convert_without_rev).collect())
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::GetRepoRootCidByUserId { user_id, tx } => {
let result = state
.metastore
.repo_ops()
.get_repo_root_cid_by_user_id(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::GetAccountWithRepo { did, tx } => {
let result = state
.metastore
.repo_ops()
.get_account_with_repo(&did)
.map(|opt| opt.map(convert_repo_account))
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RepoRequest::ListReposPaginated {
cursor_user_hash,
limit,
tx,
} => {
let result = state
.metastore
.repo_ops()
.list_repos_paginated(cursor_user_hash, limit)
.map_err(metastore_to_db)
.and_then(|entries| entries.into_iter().map(convert_repo_list_entry).collect());
let _ = tx.send(result);
}
RepoRequest::UpdateRepoStatus {
did,
takedown,
takedown_ref,
deactivated,
tx,
} => {
let result = state
.metastore
.repo_ops()
.update_repo_status(
state.metastore.database(),
&did,
takedown,
takedown_ref.as_deref(),
deactivated,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
}
}
fn dispatch_record<S: StorageIO>(state: &HandlerState<S>, req: RecordRequest) {
match req {
RecordRequest::UpsertRecords {
repo_id,
collections,
rkeys,
record_cids,
repo_rev,
tx,
} => {
let result = (|| {
let (user_hash, mut meta) = state
.metastore
.repo_ops()
.get_repo_meta(repo_id)
.map_err(metastore_to_db)?
.ok_or(DbError::Query("unknown user_id".to_string()))?;
let writes: Vec<super::record_ops::RecordWrite<'_>> = collections
.iter()
.zip(rkeys.iter())
.zip(record_cids.iter())
.map(|((c, r), cid)| super::record_ops::RecordWrite {
collection: c,
rkey: r,
cid,
})
.collect();
let mut batch = state.metastore.database().batch();
state
.metastore
.record_ops()
.upsert_records(&mut batch, user_hash, &writes)
.map_err(metastore_to_db)?;
meta.repo_rev = repo_rev.as_str().to_owned();
state
.metastore
.repo_ops()
.write_repo_meta(&mut batch, user_hash, &meta);
batch.commit().map_err(|e| DbError::Query(e.to_string()))
})();
let _ = tx.send(result);
}
RecordRequest::DeleteRecords {
repo_id,
collections,
rkeys,
tx,
} => {
let result = (|| {
let user_hash = state
.metastore
.user_hashes()
.get(&repo_id)
.ok_or(DbError::Query("unknown user_id".to_string()))?;
let deletes: Vec<super::record_ops::RecordDelete<'_>> = collections
.iter()
.zip(rkeys.iter())
.map(|(c, r)| super::record_ops::RecordDelete {
collection: c,
rkey: r,
})
.collect();
let mut batch = state.metastore.database().batch();
state
.metastore
.record_ops()
.delete_records(&mut batch, user_hash, &deletes)
.map_err(metastore_to_db)?;
batch.commit().map_err(|e| DbError::Query(e.to_string()))
})();
let _ = tx.send(result);
}
RecordRequest::DeleteAllRecords { repo_id, tx } => {
let result = (|| {
let user_hash = state
.metastore
.user_hashes()
.get(&repo_id)
.ok_or(DbError::Query("unknown user_id".to_string()))?;
let mut batch = state.metastore.database().batch();
state
.metastore
.record_ops()
.delete_all_records(&mut batch, user_hash)
.map_err(metastore_to_db)?;
batch.commit().map_err(|e| DbError::Query(e.to_string()))
})();
let _ = tx.send(result);
}
RecordRequest::GetRecordCid {
repo_id,
collection,
rkey,
tx,
} => {
let result = state
.metastore
.record_ops()
.get_record_cid(repo_id, &collection, &rkey)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RecordRequest::ListRecords {
repo_id,
collection,
cursor,
limit,
reverse,
rkey_start,
rkey_end,
tx,
} => {
let query = ListRecordsQuery {
user_id: repo_id,
collection: &collection,
cursor: cursor.as_ref(),
limit: usize::try_from(limit).unwrap_or(0),
reverse,
rkey_start: rkey_start.as_ref(),
rkey_end: rkey_end.as_ref(),
};
let result = state
.metastore
.record_ops()
.list_records(&query)
.map(|v| v.into_iter().map(convert_record_info).collect())
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RecordRequest::GetAllRecords { repo_id, tx } => {
let result = state
.metastore
.record_ops()
.get_all_records(repo_id)
.map(|v| v.into_iter().map(convert_full_record_info).collect())
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RecordRequest::ListCollections { repo_id, tx } => {
let result = state
.metastore
.record_ops()
.list_collections(repo_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RecordRequest::CountRecords { repo_id, tx } => {
let result = state
.metastore
.record_ops()
.count_records(repo_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RecordRequest::CountAllRecords { tx } => {
let result = state
.metastore
.record_ops()
.count_all_records()
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RecordRequest::GetRecordByCid { cid, tx } => {
let result = state
.metastore
.record_ops()
.get_record_by_cid(&cid, None)
.map(|opt| opt.map(convert_record_with_takedown))
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RecordRequest::ReferencedRecordCids {
repo_id,
cids,
excluded_keys,
tx,
} => {
let excluded: Vec<(&Nsid, &Rkey)> = excluded_keys
.iter()
.map(|(collection, rkey)| (collection, rkey))
.collect();
let result = state
.metastore
.record_ops()
.referenced_record_cids(repo_id, &cids, &excluded)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
RecordRequest::SetRecordTakedown {
cid,
takedown_ref,
scope_user,
tx,
} => {
let result = state
.metastore
.record_ops()
.set_record_takedown(
state.metastore.database(),
&cid,
takedown_ref.as_deref(),
scope_user,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
}
}
fn dispatch_user_block<S: StorageIO>(state: &HandlerState<S>, req: UserBlockRequest) {
match req {
UserBlockRequest::InsertUserBlocks {
user_id,
block_cids,
repo_rev,
tx,
} => {
let result = (|| {
let user_hash = state
.metastore
.user_hashes()
.get(&user_id)
.ok_or(DbError::Query("unknown user_id".to_string()))?;
let mut batch = state.metastore.database().batch();
state
.metastore
.user_block_ops()
.insert_user_blocks(&mut batch, user_hash, &block_cids, &repo_rev)
.map_err(metastore_to_db)?;
batch.commit().map_err(|e| DbError::Query(e.to_string()))
})();
let _ = tx.send(result);
}
UserBlockRequest::DeleteUserBlocks {
user_id,
block_cids,
tx,
} => {
let result = (|| {
let user_hash = state
.metastore
.user_hashes()
.get(&user_id)
.ok_or(DbError::Query("unknown user_id".to_string()))?;
let mut batch = state.metastore.database().batch();
state
.metastore
.user_block_ops()
.delete_user_blocks_by_cid(&mut batch, user_hash, &block_cids)
.map_err(metastore_to_db)?;
batch.commit().map_err(|e| DbError::Query(e.to_string()))
})();
let _ = tx.send(result);
}
UserBlockRequest::GetUserBlockCidsSinceRev {
user_id,
since_rev,
tx,
} => {
let result = state
.metastore
.user_block_ops()
.get_user_block_cids_since_rev(user_id, since_rev.as_ref())
.map_err(metastore_to_db);
let _ = tx.send(result);
}
UserBlockRequest::CountUserBlocks { user_id, tx } => {
let result = state
.metastore
.user_block_ops()
.count_user_blocks(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
}
}
fn dispatch_event<S: StorageIO + 'static>(state: &HandlerState<S>, req: EventRequest) {
match req {
EventRequest::InsertCommitEvent { data, tx } => {
let result = state.event_ops.insert_commit_event(&data);
let _ = tx.send(result);
}
EventRequest::InsertIdentityEvent { did, handle, tx } => {
let result = state.event_ops.insert_identity_event(&did, handle.as_ref());
let _ = tx.send(result);
}
EventRequest::InsertAccountEvent { did, status, tx } => {
let result = state.event_ops.insert_account_event(&did, status);
let _ = tx.send(result);
}
EventRequest::InsertSyncEvent {
did,
commit_cid,
rev,
commit_bytes,
tx,
} => {
let result =
state
.event_ops
.insert_sync_event(&did, &commit_cid, rev.as_ref(), &commit_bytes);
let _ = tx.send(result);
}
EventRequest::InsertGenesisCommitEvent {
did,
commit_cid,
mst_root_cid,
rev,
commit_bytes,
mst_root_bytes,
tx,
} => {
let result = state.event_ops.insert_genesis_commit_event(
&did,
&commit_cid,
&mst_root_cid,
&rev,
&commit_bytes,
&mst_root_bytes,
);
let _ = tx.send(result);
}
EventRequest::PurgeDidEventsKeepingLatest { did, tx } => {
let result = state.event_ops.purge_did_events_keeping_latest(&did);
let _ = tx.send(result);
}
EventRequest::GetMaxSeq { tx } => {
let _ = tx.send(Ok(state.event_ops.get_max_seq()));
}
EventRequest::GetMinSeqSince { since, tx } => {
let _ = tx.send(state.event_ops.get_min_seq_since(since));
}
EventRequest::GetEventsSinceSeq {
since_seq,
limit,
tx,
} => {
let _ = tx.send(state.event_ops.get_events_since_seq(since_seq, limit));
}
EventRequest::GetEventsInSeqRange {
start_seq,
end_seq,
tx,
} => {
let _ = tx.send(state.event_ops.get_events_in_seq_range(start_seq, end_seq));
}
EventRequest::GetEventBySeq { seq, tx } => {
let _ = tx.send(state.event_ops.get_event_by_seq(seq));
}
EventRequest::GetEventsSinceCursor { cursor, limit, tx } => {
let _ = tx.send(state.event_ops.get_events_since_cursor(cursor, limit));
}
}
}
fn dispatch_commit<S: StorageIO + 'static>(state: &HandlerState<S>, req: CommitRequest) {
match req {
CommitRequest::ApplyCommit { input, tx } => {
let _ = tx.send(state.commit_ops.apply_commit(*input));
}
CommitRequest::ImportRepoData {
user_id,
blocks,
records,
expected_root_cid,
tx,
} => {
let _ = tx.send(state.commit_ops.import_repo_data(
user_id,
&blocks,
&records,
expected_root_cid.as_ref(),
));
}
CommitRequest::GetUsersWithoutBlocks { tx } => {
let _ = tx.send(
state
.commit_ops
.get_users_without_blocks()
.map_err(metastore_to_db),
);
}
CommitRequest::GetUsersNeedingRecordBlobsBackfill { limit, tx } => {
let _ = tx.send(
state
.commit_ops
.get_users_needing_record_blobs_backfill(limit)
.map_err(metastore_to_db),
);
}
CommitRequest::InsertRecordBlobs {
repo_id,
record_uris,
blob_cids,
tx,
} => {
let _ = tx.send(
state
.commit_ops
.insert_record_blobs(repo_id, &record_uris, &blob_cids)
.map_err(metastore_to_db),
);
}
}
}
fn dispatch_backlink<S: StorageIO>(state: &HandlerState<S>, req: BacklinkRequest) {
match req {
BacklinkRequest::GetBacklinkConflicts {
repo_id,
collection,
backlinks,
tx,
} => {
let result = state
.metastore
.backlink_ops()
.get_backlink_conflicts(repo_id, &collection, &backlinks)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BacklinkRequest::AddBacklinks {
repo_id,
backlinks,
tx,
} => {
let result = (|| {
let user_hash = state
.metastore
.user_hashes()
.get(&repo_id)
.ok_or(DbError::Query("unknown user_id".to_string()))?;
let mut batch = state.metastore.database().batch();
state
.metastore
.backlink_ops()
.add_backlinks(&mut batch, user_hash, &backlinks)
.map_err(metastore_to_db)?;
batch.commit().map_err(|e| DbError::Query(e.to_string()))
})();
let _ = tx.send(result);
}
BacklinkRequest::RemoveBacklinksByUri { uri, tx } => {
let result = (|| {
let did_str = uri
.did()
.ok_or(DbError::Query("backlink uri missing did".to_string()))?;
let user_hash = UserHash::from_did(did_str);
let mut batch = state.metastore.database().batch();
state
.metastore
.backlink_ops()
.remove_backlinks_by_uri(&mut batch, user_hash, &uri)
.map_err(metastore_to_db)?;
batch.commit().map_err(|e| DbError::Query(e.to_string()))
})();
let _ = tx.send(result);
}
BacklinkRequest::RemoveBacklinksByRepo { repo_id, tx } => {
let result = (|| {
let user_hash = state
.metastore
.user_hashes()
.get(&repo_id)
.ok_or(DbError::Query("unknown user_id".to_string()))?;
let mut batch = state.metastore.database().batch();
state
.metastore
.backlink_ops()
.remove_backlinks_by_repo(&mut batch, user_hash)
.map_err(metastore_to_db)?;
batch.commit().map_err(|e| DbError::Query(e.to_string()))
})();
let _ = tx.send(result);
}
}
}
fn dispatch_blob<S: StorageIO + 'static>(state: &HandlerState<S>, req: BlobRequest) {
match req {
BlobRequest::InsertBlob {
cid,
mime_type,
size_bytes,
created_by_user,
storage_key,
tx,
} => {
let result = state
.metastore
.blob_ops()
.insert_blob(&cid, &mime_type, size_bytes, created_by_user, &storage_key)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::GetBlobMetadata { cid, tx } => {
let result = state
.metastore
.blob_ops()
.get_blob_metadata(&cid)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::GetBlobWithTakedown { cid, tx } => {
let result = state
.metastore
.blob_ops()
.get_blob_with_takedown(&cid)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::GetBlobStorageKey { cid, tx } => {
let result = state
.metastore
.blob_ops()
.get_blob_storage_key(&cid)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::ListBlobsByUser {
user_id,
cursor,
limit,
tx,
} => {
let result = state
.metastore
.blob_ops()
.list_blobs_by_user(
user_id,
cursor.as_deref(),
usize::try_from(limit).unwrap_or(0),
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::ListBlobsSinceRev { did, since, tx } => {
let result = state.event_ops.get_blob_cids_since_rev(&did, &since);
let _ = tx.send(result);
}
BlobRequest::CountBlobsByUser { user_id, tx } => {
let result = state
.metastore
.blob_ops()
.count_blobs_by_user(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::SumBlobStorage { tx } => {
let result = state
.metastore
.blob_ops()
.sum_blob_storage()
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::UpdateBlobTakedown {
cid,
takedown_ref,
tx,
} => {
let result = state
.metastore
.blob_ops()
.update_blob_takedown(&cid, takedown_ref.as_deref())
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::DeleteBlobsByUser { user_id, tx } => {
let result = state
.metastore
.blob_ops()
.delete_blobs_by_user(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::GetBlobStorageKeysByUser { user_id, tx } => {
let result = state
.metastore
.blob_ops()
.get_blob_storage_keys_by_user(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::ListMissingBlobs {
repo_id,
cursor,
limit,
tx,
} => {
let result = state
.metastore
.blob_ops()
.list_missing_blobs(
repo_id,
cursor.as_deref(),
usize::try_from(limit).unwrap_or(0),
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::CountDistinctRecordBlobs { repo_id, tx } => {
let result = state
.metastore
.blob_ops()
.count_distinct_record_blobs(repo_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
BlobRequest::GetBlobsForExport { repo_id, tx } => {
let result = state
.metastore
.blob_ops()
.get_blobs_for_export(repo_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
}
}
fn dispatch_delegation<S: StorageIO>(state: &HandlerState<S>, req: DelegationRequest) {
match req {
DelegationRequest::IsDelegatedAccount { did, tx } => {
let result = state
.metastore
.delegation_ops()
.is_delegated_account(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::CreateDelegation {
delegated_did,
controller_did,
granted_scopes,
granted_by,
tx,
} => {
let result = state
.metastore
.delegation_ops()
.create_delegation(
&delegated_did,
&controller_did,
&granted_scopes,
&granted_by,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::RevokeDelegation {
delegated_did,
controller_did,
revoked_by,
tx,
} => {
let result = state
.metastore
.delegation_ops()
.revoke_delegation(&delegated_did, &controller_did, &revoked_by)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::UpdateDelegationScopes {
delegated_did,
controller_did,
new_scopes,
tx,
} => {
let result = state
.metastore
.delegation_ops()
.update_delegation_scopes(&delegated_did, &controller_did, &new_scopes)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::GetDelegation {
delegated_did,
controller_did,
tx,
} => {
let result = state
.metastore
.delegation_ops()
.get_delegation(&delegated_did, &controller_did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::GetDelegationsForAccount { delegated_did, tx } => {
let result = state
.metastore
.delegation_ops()
.get_delegations_for_account(&delegated_did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::GetAccountsControlledBy { controller_did, tx } => {
let result = state
.metastore
.delegation_ops()
.get_accounts_controlled_by(&controller_did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::CountActiveControllers { delegated_did, tx } => {
let result = state
.metastore
.delegation_ops()
.count_active_controllers(&delegated_did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::ControlsAnyAccounts { did, tx } => {
let result = state
.metastore
.delegation_ops()
.controls_any_accounts(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::LogDelegationAction {
delegated_did,
actor_did,
controller_did,
action_type,
action_details,
ip_address,
user_agent,
tx,
} => {
let result = state
.metastore
.delegation_ops()
.log_delegation_action(
&delegated_did,
&actor_did,
controller_did.as_ref(),
action_type,
action_details,
ip_address.as_deref(),
user_agent.as_deref(),
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::GetAuditLogForAccount {
delegated_did,
limit,
offset,
tx,
} => {
let result = state
.metastore
.delegation_ops()
.get_audit_log_for_account(&delegated_did, limit, offset)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
DelegationRequest::CountAuditLogEntries { delegated_did, tx } => {
let result = state
.metastore
.delegation_ops()
.count_audit_log_entries(&delegated_did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
}
}
fn dispatch_sso<S: StorageIO>(state: &HandlerState<S>, req: SsoRequest) {
match req {
SsoRequest::CreateExternalIdentity {
did,
provider,
provider_user_id,
provider_username,
provider_email,
tx,
} => {
let result = state
.metastore
.sso_ops()
.create_external_identity(
&did,
provider,
&provider_user_id,
provider_username.as_deref(),
provider_email.as_deref(),
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::GetExternalIdentityByProvider {
provider,
provider_user_id,
tx,
} => {
let result = state
.metastore
.sso_ops()
.get_external_identity_by_provider(provider, &provider_user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::GetExternalIdentitiesByDid { did, tx } => {
let result = state
.metastore
.sso_ops()
.get_external_identities_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::UpdateExternalIdentityLogin {
id,
provider_username,
provider_email,
tx,
} => {
let result = state
.metastore
.sso_ops()
.update_external_identity_login(
id,
provider_username.as_deref(),
provider_email.as_deref(),
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::DeleteExternalIdentity { id, did, tx } => {
let result = state
.metastore
.sso_ops()
.delete_external_identity(id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::CreateSsoAuthState {
state: sso_state,
request_uri,
provider,
action,
nonce,
code_verifier,
did,
tx,
} => {
let result = state
.metastore
.sso_ops()
.create_sso_auth_state(
&sso_state,
&request_uri,
provider,
action,
nonce.as_deref(),
code_verifier.as_deref(),
did.as_ref(),
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::ConsumeSsoAuthState {
state: sso_state,
tx,
} => {
let result = state
.metastore
.sso_ops()
.consume_sso_auth_state(&sso_state)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::CleanupExpiredSsoAuthStates { tx } => {
let result = state
.metastore
.sso_ops()
.cleanup_expired_sso_auth_states()
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::CreatePendingRegistration {
token,
request_uri,
provider,
provider_user_id,
provider_username,
provider_email,
provider_email_verified,
tx,
} => {
let result = state
.metastore
.sso_ops()
.create_pending_registration(
&token,
&request_uri,
provider,
&provider_user_id,
provider_username.as_deref(),
provider_email.as_deref(),
provider_email_verified,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::GetPendingRegistration { token, tx } => {
let result = state
.metastore
.sso_ops()
.get_pending_registration(&token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::ConsumePendingRegistration { token, tx } => {
let result = state
.metastore
.sso_ops()
.consume_pending_registration(&token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SsoRequest::CleanupExpiredPendingRegistrations { tx } => {
let result = state
.metastore
.sso_ops()
.cleanup_expired_pending_registrations()
.map_err(metastore_to_db);
let _ = tx.send(result);
}
}
}
fn dispatch_session<S: StorageIO>(state: &HandlerState<S>, req: SessionRequest) {
match req {
SessionRequest::CreateSession { data, tx } => {
let result = state
.metastore
.session_ops()
.create_session(&data)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetSessionByAccessJti { access_jti, tx } => {
let result = state
.metastore
.session_ops()
.get_session_by_access_jti(&access_jti)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetSessionForRefresh { refresh_jti, tx } => {
let result = state
.metastore
.session_ops()
.get_session_for_refresh(&refresh_jti)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::DeleteSessionByAccessJti {
access_jti,
did,
tx,
} => {
let result = state
.metastore
.session_ops()
.delete_session_by_access_jti(&access_jti, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::DeleteSessionById {
session_id,
did,
tx,
} => {
let result = state
.metastore
.session_ops()
.delete_session_by_id(session_id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::DeleteSessionsByDid { did, tx } => {
let result = state
.metastore
.session_ops()
.delete_sessions_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::DeleteSessionsByDidExceptJti {
did,
except_jti,
tx,
} => {
let result = state
.metastore
.session_ops()
.delete_sessions_by_did_except_jti(&did, &except_jti)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::ListSessionsByDid { did, tx } => {
let result = state
.metastore
.session_ops()
.list_sessions_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetSessionAccessJtiById {
session_id,
did,
tx,
} => {
let result = state
.metastore
.session_ops()
.get_session_access_jti_by_id(session_id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::DeleteSessionsByAppPassword {
did,
app_password_name,
tx,
} => {
let result = state
.metastore
.session_ops()
.delete_sessions_by_app_password(&did, &app_password_name)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetSessionJtisByAppPassword {
did,
app_password_name,
tx,
} => {
let result = state
.metastore
.session_ops()
.get_session_jtis_by_app_password(&did, &app_password_name)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::LookupRefreshGrace { refresh_jti, tx } => {
let result = state
.metastore
.session_ops()
.lookup_refresh_grace(&refresh_jti)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::ListAppPasswords { user_id, tx } => {
let result = state
.metastore
.session_ops()
.list_app_passwords(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetAppPasswordsForLogin { user_id, tx } => {
let result = state
.metastore
.session_ops()
.get_app_passwords_for_login(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetAppPasswordByName { user_id, name, tx } => {
let result = state
.metastore
.session_ops()
.get_app_password_by_name(user_id, &name)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::CreateAppPassword { data, tx } => {
let result = state
.metastore
.session_ops()
.create_app_password(&data)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::DeleteAppPassword { user_id, name, tx } => {
let result = state
.metastore
.session_ops()
.delete_app_password(user_id, &name)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::DeleteAppPasswordsByController {
did,
controller_did,
tx,
} => {
let result = state
.metastore
.session_ops()
.delete_app_passwords_by_controller(&did, &controller_did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetLastReauthAt { did, tx } => {
let result = state
.metastore
.session_ops()
.get_last_reauth_at(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::UpdateLastReauth { did, tx } => {
let result = state
.metastore
.session_ops()
.update_last_reauth(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetSessionMfaStatus { did, tx } => {
let result = state
.metastore
.session_ops()
.get_session_mfa_status(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::UpdateMfaVerified { did, tx } => {
let result = state
.metastore
.session_ops()
.update_mfa_verified(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::GetAppPasswordHashesByDid { did, tx } => {
let result = state
.metastore
.session_ops()
.get_app_password_hashes_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
SessionRequest::RefreshSessionAtomic { data, tx } => {
let result = state
.metastore
.session_ops()
.refresh_session_atomic(&data)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
}
}
fn dispatch_infra<S: StorageIO>(state: &HandlerState<S>, req: InfraRequest) {
match req {
InfraRequest::EnqueueComms {
user_id,
channel,
comms_type,
recipient,
subject,
body,
metadata,
tx,
} => {
let result = state
.metastore
.infra_ops()
.enqueue_comms(
user_id,
channel,
comms_type,
&recipient,
subject.as_deref(),
&body,
metadata,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::FetchPendingComms {
now,
batch_size,
tx,
} => {
let result = state
.metastore
.infra_ops()
.fetch_pending_comms(now, batch_size)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::MarkCommsSent { id, tx } => {
let result = state
.metastore
.infra_ops()
.mark_comms_sent(id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::MarkCommsFailed { id, error, tx } => {
let result = state
.metastore
.infra_ops()
.mark_comms_failed(id, &error)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::MarkCommsFailedPermanent { id, error, tx } => {
let result = state
.metastore
.infra_ops()
.mark_comms_failed_permanent(id, &error)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::CreateInviteCode {
code,
use_count,
for_account,
tx,
} => {
let result = state
.metastore
.infra_ops()
.create_invite_code(&code, use_count, &for_account)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::CreateInviteCodesBatch {
codes,
use_count,
created_by_user,
for_account,
tx,
} => {
let result = state
.metastore
.infra_ops()
.create_invite_codes_batch(&codes, use_count, created_by_user, &for_account)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetInviteCodeAvailableUses { code, tx } => {
let result = state
.metastore
.infra_ops()
.get_invite_code_available_uses(&code)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::ValidateInviteCode { code, tx } => {
let result = state
.metastore
.infra_ops()
.validate_invite_code(&code)
.map(|_| ());
let _ = tx.send(result);
}
InfraRequest::GetInviteCodesForAccount { for_account, tx } => {
let result = state
.metastore
.infra_ops()
.get_invite_codes_for_account(&for_account)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetInviteCodeUses { code, tx } => {
let result = state
.metastore
.infra_ops()
.get_invite_code_uses(&code)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DisableInviteCodesByCode { codes, tx } => {
let result = state
.metastore
.infra_ops()
.disable_invite_codes_by_code(&codes)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DisableInviteCodesByAccount { accounts, tx } => {
let result = state
.metastore
.infra_ops()
.disable_invite_codes_by_account(&accounts)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::ListInviteCodes {
cursor,
limit,
sort,
tx,
} => {
let result = state
.metastore
.infra_ops()
.list_invite_codes(cursor.as_deref(), limit, sort)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetUserDidsByIds { user_ids, tx } => {
let result = state
.metastore
.infra_ops()
.get_user_dids_by_ids(&user_ids)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetInviteCodeUsesBatch { codes, tx } => {
let result = state
.metastore
.infra_ops()
.get_invite_code_uses_batch(&codes)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetInvitesCreatedByUser { user_id, tx } => {
let result = state
.metastore
.infra_ops()
.get_invites_created_by_user(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetInviteCodeInfo { code, tx } => {
let result = state
.metastore
.infra_ops()
.get_invite_code_info(&code)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetInviteCodesByUsers { user_ids, tx } => {
let result = state
.metastore
.infra_ops()
.get_invite_codes_by_users(&user_ids)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetInviteCodeUsedByUser { user_id, tx } => {
let result = state
.metastore
.infra_ops()
.get_invite_code_used_by_user(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeleteInviteCodeUsesByUser { user_id, tx } => {
let result = state
.metastore
.infra_ops()
.delete_invite_code_uses_by_user(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeleteInviteCodesByUser { user_id, tx } => {
let result = state
.metastore
.infra_ops()
.delete_invite_codes_by_user(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::ReserveSigningKey {
did,
public_key_did_key,
private_key_bytes,
expires_at,
tx,
} => {
let result = state
.metastore
.infra_ops()
.reserve_signing_key(
did.as_ref(),
&public_key_did_key,
&private_key_bytes,
expires_at,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetReservedSigningKey {
public_key_did_key,
tx,
} => {
let result = state
.metastore
.infra_ops()
.get_reserved_signing_key(&public_key_did_key)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::MarkSigningKeyUsed { key_id, tx } => {
let result = state
.metastore
.infra_ops()
.mark_signing_key_used(key_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::CreateDeletionRequest {
token,
did,
expires_at,
tx,
} => {
let result = state
.metastore
.infra_ops()
.create_deletion_request(&token, &did, expires_at)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetDeletionRequest { token, tx } => {
let result = state
.metastore
.infra_ops()
.get_deletion_request(&token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeleteDeletionRequest { token, tx } => {
let result = state
.metastore
.infra_ops()
.delete_deletion_request(&token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeleteDeletionRequestsByDid { did, tx } => {
let result = state
.metastore
.infra_ops()
.delete_deletion_requests_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::UpsertAccountPreference {
user_id,
name,
value_json,
tx,
} => {
if name == "email_auth_factor" {
let enabled = value_json.as_bool().unwrap_or(false);
let _ = state
.metastore
.user_ops()
.set_email_2fa_enabled(user_id, enabled);
}
let result = state
.metastore
.infra_ops()
.upsert_account_preference(user_id, &name, value_json)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::InsertAccountPreferenceIfNotExists {
user_id,
name,
value_json,
tx,
} => {
let result = state
.metastore
.infra_ops()
.insert_account_preference_if_not_exists(user_id, &name, value_json)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetServerConfig { key, tx } => {
let result = state
.metastore
.infra_ops()
.get_server_config(&key)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::InsertReport {
id,
reason_type,
reason,
subject_json,
reported_by_did,
created_at,
tx,
} => {
let result = state
.metastore
.infra_ops()
.insert_report(
id,
&reason_type,
reason.as_deref(),
subject_json,
&reported_by_did,
created_at,
)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeletePlcTokensForUser { user_id, tx } => {
let result = state
.metastore
.infra_ops()
.delete_plc_tokens_for_user(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::InsertPlcToken {
user_id,
token,
expires_at,
tx,
} => {
let result = state
.metastore
.infra_ops()
.insert_plc_token(user_id, &token, expires_at)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetPlcTokenExpiry { user_id, token, tx } => {
let result = state
.metastore
.infra_ops()
.get_plc_token_expiry(user_id, &token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeletePlcToken { user_id, token, tx } => {
let result = state
.metastore
.infra_ops()
.delete_plc_token(user_id, &token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetAccountPreferences { user_id, tx } => {
let result = state
.metastore
.infra_ops()
.get_account_preferences(user_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::ReplaceNamespacePreferences {
user_id,
namespace,
preferences,
tx,
} => {
let result = state
.metastore
.infra_ops()
.replace_namespace_preferences(user_id, &namespace, preferences)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetNotificationHistory { user_id, limit, tx } => {
let result = state
.metastore
.infra_ops()
.get_notification_history(user_id, limit)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetServerConfigs { keys, tx } => {
let key_refs: Vec<&str> = keys.iter().map(String::as_str).collect();
let result = state
.metastore
.infra_ops()
.get_server_configs(&key_refs)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::UpsertServerConfig { key, value, tx } => {
let result = state
.metastore
.infra_ops()
.upsert_server_config(&key, &value)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeleteServerConfig { key, tx } => {
let result = state
.metastore
.infra_ops()
.delete_server_config(&key)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetBlobStorageKeyByCid { cid, tx } => {
let result = state
.metastore
.infra_ops()
.get_blob_storage_key_by_cid(&cid)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeleteBlobByCid { cid, tx } => {
let result = state
.metastore
.infra_ops()
.delete_blob_by_cid(&cid)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetAdminAccountInfoByDid { did, tx } => {
let result = state
.metastore
.infra_ops()
.get_admin_account_info_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetAdminAccountInfosByDids { dids, tx } => {
let result = state
.metastore
.infra_ops()
.get_admin_account_infos_by_dids(&dids)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetInviteCodeUsesByUsers { user_ids, tx } => {
let result = state
.metastore
.infra_ops()
.get_invite_code_uses_by_users(&user_ids)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetDeletionRequestByDid { did, tx } => {
let result = state
.metastore
.infra_ops()
.get_deletion_request_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetLatestCommsForUser {
user_id,
comms_type,
limit,
tx,
} => {
let result = state
.metastore
.infra_ops()
.get_latest_comms_for_user(user_id, comms_type, limit)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::CountCommsByType {
user_id,
comms_type,
tx,
} => {
let result = state
.metastore
.infra_ops()
.count_comms_by_type(user_id, comms_type)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::DeleteCommsByTypeForUser {
user_id,
comms_type,
tx,
} => {
let result = state
.metastore
.infra_ops()
.delete_comms_by_type_for_user(user_id, comms_type)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::ExpireDeletionRequest { token, tx } => {
let result = state
.metastore
.infra_ops()
.expire_deletion_request(&token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetReservedSigningKeyFull {
public_key_did_key,
tx,
} => {
let result = state
.metastore
.infra_ops()
.get_reserved_signing_key_full(&public_key_did_key)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
InfraRequest::GetPlcTokensByDid { did, tx } => {
let result = (|| {
let user_id = state
.metastore
.user_ops()
.get_id_by_did(&did)
.map_err(metastore_to_db)?;
match user_id {
Some(uid) => state
.metastore
.infra_ops()
.get_plc_tokens_for_user(uid)
.map_err(metastore_to_db),
None => Ok(Vec::new()),
}
})();
let _ = tx.send(result);
}
InfraRequest::CountPlcTokensByDid { did, tx } => {
let result = (|| {
let user_id = state
.metastore
.user_ops()
.get_id_by_did(&did)
.map_err(metastore_to_db)?;
match user_id {
Some(uid) => state
.metastore
.infra_ops()
.count_plc_tokens_for_user(uid)
.map_err(metastore_to_db),
None => Ok(0),
}
})();
let _ = tx.send(result);
}
}
}
fn dispatch_oauth<S: StorageIO>(state: &HandlerState<S>, req: OAuthRequest) {
match req {
OAuthRequest::CreateToken { data, tx } => {
let result = state
.metastore
.oauth_ops()
.create_token(&data)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetTokenById { token_id, tx } => {
let result = state
.metastore
.oauth_ops()
.get_token_by_id(&token_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetTokenByRefreshToken { refresh_token, tx } => {
let result = state
.metastore
.oauth_ops()
.get_token_by_refresh_token(&refresh_token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetTokenByPreviousRefreshToken { refresh_token, tx } => {
let result = state
.metastore
.oauth_ops()
.get_token_by_previous_refresh_token(&refresh_token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::RotateToken {
old_db_id,
new_refresh_token,
new_expires_at,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.rotate_token(old_db_id, &new_refresh_token, new_expires_at)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::CheckRefreshTokenUsed { refresh_token, tx } => {
let result = state
.metastore
.oauth_ops()
.check_refresh_token_used(&refresh_token)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteToken { token_id, tx } => {
let result = state
.metastore
.oauth_ops()
.delete_token(&token_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteTokenFamily { db_id, tx } => {
let result = state
.metastore
.oauth_ops()
.delete_token_family(db_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::ListTokensForUser { did, tx } => {
let result = state
.metastore
.oauth_ops()
.list_tokens_for_user(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::CountTokensForUser { did, tx } => {
let result = state
.metastore
.oauth_ops()
.count_tokens_for_user(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteOldestTokensForUser {
did,
keep_count,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.delete_oldest_tokens_for_user(&did, keep_count)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::RevokeTokensForClient { did, client_id, tx } => {
let result = state
.metastore
.oauth_ops()
.revoke_tokens_for_client(&did, &client_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::RevokeTokensForController {
delegated_did,
controller_did,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.revoke_tokens_for_controller(&delegated_did, &controller_did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::CreateAuthorizationRequest {
request_id,
data,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.create_authorization_request(&request_id, &data)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetAuthorizationRequest { request_id, tx } => {
let result = state
.metastore
.oauth_ops()
.get_authorization_request(&request_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::SetAuthorizationDid {
request_id,
did,
device_id,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.set_authorization_did(&request_id, &did, device_id.as_ref())
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::UpdateAuthorizationRequest {
request_id,
did,
device_id,
code,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.update_authorization_request(&request_id, &did, device_id.as_ref(), &code)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::ConsumeAuthorizationRequestByCode { code, tx } => {
let result = state
.metastore
.oauth_ops()
.consume_authorization_request_by_code(&code)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteAuthorizationRequest { request_id, tx } => {
let result = state
.metastore
.oauth_ops()
.delete_authorization_request(&request_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteExpiredAuthorizationRequests { tx } => {
let result = state
.metastore
.oauth_ops()
.delete_expired_authorization_requests()
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::ExtendAuthorizationRequestExpiry {
request_id,
new_expires_at,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.extend_authorization_request_expiry(&request_id, new_expires_at)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::MarkRequestAuthenticated {
request_id,
did,
device_id,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.mark_request_authenticated(&request_id, &did, device_id.as_ref())
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::UpdateRequestScope {
request_id,
scope,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.update_request_scope(&request_id, &scope)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::SetControllerDid {
request_id,
controller_did,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.set_controller_did(&request_id, &controller_did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::SetRequestDid {
request_id,
did,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.set_request_did(&request_id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::CreateDevice {
device_id,
data,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.create_device(&device_id, &data)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetDevice { device_id, tx } => {
let result = state
.metastore
.oauth_ops()
.get_device(&device_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::UpdateDeviceLastSeen { device_id, tx } => {
let result = state
.metastore
.oauth_ops()
.update_device_last_seen(&device_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteDevice { device_id, tx } => {
let result = state
.metastore
.oauth_ops()
.delete_device(&device_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::UpsertAccountDevice { did, device_id, tx } => {
let result = state
.metastore
.oauth_ops()
.upsert_account_device(&did, &device_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetDeviceAccounts { device_id, tx } => {
let result = state
.metastore
.oauth_ops()
.get_device_accounts(&device_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::VerifyAccountOnDevice { device_id, did, tx } => {
let result = state
.metastore
.oauth_ops()
.verify_account_on_device(&device_id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::CheckAndRecordDpopJti { jti, tx } => {
let result = state
.metastore
.oauth_ops()
.check_and_record_dpop_jti(&jti)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::CleanupExpiredDpopJtis { max_age_secs, tx } => {
let result = state
.metastore
.oauth_ops()
.cleanup_expired_dpop_jtis(max_age_secs)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::Create2faChallenge {
did,
request_uri,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.create_2fa_challenge(&did, &request_uri)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::Get2faChallenge { request_uri, tx } => {
let result = state
.metastore
.oauth_ops()
.get_2fa_challenge(&request_uri)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::Increment2faAttempts { id, tx } => {
let result = state
.metastore
.oauth_ops()
.increment_2fa_attempts(id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::Delete2faChallenge { id, tx } => {
let result = state
.metastore
.oauth_ops()
.delete_2fa_challenge(id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::Delete2faChallengeByRequestUri { request_uri, tx } => {
let result = state
.metastore
.oauth_ops()
.delete_2fa_challenge_by_request_uri(&request_uri)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::CleanupExpired2faChallenges { tx } => {
let result = state
.metastore
.oauth_ops()
.cleanup_expired_2fa_challenges()
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::CheckUser2faEnabled { did, tx } => {
let result = state
.metastore
.oauth_ops()
.check_user_2fa_enabled(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetScopePreferences { did, client_id, tx } => {
let result = state
.metastore
.oauth_ops()
.get_scope_preferences(&did, &client_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::UpsertScopePreferences {
did,
client_id,
prefs,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.upsert_scope_preferences(&did, &client_id, &prefs)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteScopePreferences { did, client_id, tx } => {
let result = state
.metastore
.oauth_ops()
.delete_scope_preferences(&did, &client_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::UpsertAuthorizedClient {
did,
client_id,
data,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.upsert_authorized_client(&did, &client_id, &data)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetAuthorizedClient { did, client_id, tx } => {
let result = state
.metastore
.oauth_ops()
.get_authorized_client(&did, &client_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::ListTrustedDevices { did, tx } => {
let result = state
.metastore
.oauth_ops()
.list_trusted_devices(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::GetDeviceTrustInfo { device_id, did, tx } => {
let result = state
.metastore
.oauth_ops()
.get_device_trust_info(&device_id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeviceBelongsToUser { device_id, did, tx } => {
let result = state
.metastore
.oauth_ops()
.device_belongs_to_user(&device_id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::RevokeDeviceTrust { device_id, did, tx } => {
let result = state
.metastore
.oauth_ops()
.revoke_device_trust(&device_id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::UpdateDeviceFriendlyName {
device_id,
did,
friendly_name,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.update_device_friendly_name(&device_id, &did, friendly_name.as_deref())
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::TrustDevice {
device_id,
did,
trusted_at,
trusted_until,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.trust_device(&device_id, &did, trusted_at, trusted_until)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::ExtendDeviceTrust {
device_id,
did,
trusted_until,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.extend_device_trust(&device_id, &did, trusted_until)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::ListSessionsByDid { did, tx } => {
let result = state
.metastore
.oauth_ops()
.list_sessions_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteSessionById {
session_id,
did,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.delete_session_by_id(session_id, &did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteSessionsByDid { did, tx } => {
let result = state
.metastore
.oauth_ops()
.delete_sessions_by_did(&did)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::DeleteSessionsByDidExcept {
did,
except_token_id,
tx,
} => {
let result = state
.metastore
.oauth_ops()
.delete_sessions_by_did_except(&did, &except_token_id)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
OAuthRequest::Get2faChallengeCode { request_uri, tx } => {
let result = state
.metastore
.oauth_ops()
.get_2fa_challenge_code(&request_uri)
.map_err(metastore_to_db);
let _ = tx.send(result);
}
}
}
fn dispatch<S: StorageIO + 'static>(state: &HandlerState<S>, request: MetastoreRequest) {
match request {
MetastoreRequest::Repo(r) => dispatch_repo(state, r),
MetastoreRequest::Record(r) => dispatch_record(state, r),
MetastoreRequest::UserBlock(r) => dispatch_user_block(state, r),
MetastoreRequest::Event(r) => dispatch_event(state, r),
MetastoreRequest::Commit(r) => dispatch_commit(state, *r),
MetastoreRequest::Backlink(r) => dispatch_backlink(state, r),
MetastoreRequest::Blob(r) => dispatch_blob(state, r),
MetastoreRequest::Delegation(r) => dispatch_delegation(state, r),
MetastoreRequest::Sso(r) => dispatch_sso(state, r),
MetastoreRequest::Session(r) => dispatch_session(state, r),
MetastoreRequest::Infra(r) => dispatch_infra(state, r),
MetastoreRequest::OAuth(r) => dispatch_oauth(state, r),
MetastoreRequest::User(r) => dispatch_user(state, r),
}
}
fn purge_repo_side_data<S: StorageIO + 'static>(
state: &HandlerState<S>,
user_id: Uuid,
did: &Did,
) -> Result<(), MetastoreError> {
let _ = state.metastore.blob_ops().delete_blobs_by_user(user_id)?;
let mut batch = state.metastore.database().batch();
state
.metastore
.backlink_ops()
.remove_backlinks_by_repo(&mut batch, UserHash::from_did(did.as_str()))?;
batch.commit().map_err(MetastoreError::Fjall)
}
fn dispatch_user<S: StorageIO + 'static>(state: &HandlerState<S>, req: UserRequest) {
let user = state.metastore.user_ops();
match req {
UserRequest::GetByDid { did, tx } => {
let _ = tx.send(user.get_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetByHandle { handle, tx } => {
let _ = tx.send(user.get_by_handle(&handle).map_err(metastore_to_db));
}
UserRequest::GetWithKeyByDid { did, tx } => {
let _ = tx.send(user.get_with_key_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetStatusByDid { did, tx } => {
let _ = tx.send(user.get_status_by_did(&did).map_err(metastore_to_db));
}
UserRequest::CountUsers { tx } => {
let _ = tx.send(user.count_users().map_err(metastore_to_db));
}
UserRequest::GetSessionAccessExpiry {
did,
access_jti,
tx,
} => {
let _ = tx.send(
user.get_session_access_expiry(&did, &access_jti)
.map_err(metastore_to_db),
);
}
UserRequest::GetOAuthTokenWithUser { token_id, tx } => {
let _ = tx.send(
user.get_oauth_token_with_user(&token_id)
.map_err(metastore_to_db),
);
}
UserRequest::GetUserInfoByDid { did, tx } => {
let _ = tx.send(user.get_user_info_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetAnyAdminUserId { tx } => {
let _ = tx.send(user.get_any_admin_user_id().map_err(metastore_to_db));
}
UserRequest::SetInvitesDisabled { did, disabled, tx } => {
let _ = tx.send(
user.set_invites_disabled(&did, disabled)
.map_err(metastore_to_db),
);
}
UserRequest::SearchAccounts {
cursor_did,
email_filter,
handle_filter,
limit,
tx,
} => {
let _ = tx.send(
user.search_accounts(
cursor_did.as_ref(),
email_filter.as_deref(),
handle_filter.as_deref(),
limit,
)
.map_err(metastore_to_db),
);
}
UserRequest::GetAuthInfoByDid { did, tx } => {
let _ = tx.send(user.get_auth_info_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetByEmail { email, tx } => {
let _ = tx.send(user.get_by_email(&email).map_err(metastore_to_db));
}
UserRequest::GetLoginCheckByIdentifier { identifier, tx } => {
let _ = tx.send(
user.get_login_check_by_identifier(&identifier)
.map_err(metastore_to_db),
);
}
UserRequest::GetLoginInfoByIdentifier { identifier, tx } => {
let _ = tx.send(
user.get_login_info_by_identifier(&identifier)
.map_err(metastore_to_db),
);
}
UserRequest::Get2faStatusByDid { did, tx } => {
let _ = tx.send(user.get_2fa_status_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetCommsPrefs { user_id, tx } => {
let _ = tx.send(user.get_comms_prefs(user_id).map_err(metastore_to_db));
}
UserRequest::GetIdByDid { did, tx } => {
let _ = tx.send(user.get_id_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetUserKeyById { user_id, tx } => {
let _ = tx.send(user.get_user_key_by_id(user_id).map_err(metastore_to_db));
}
UserRequest::GetIdAndHandleByDid { did, tx } => {
let _ = tx.send(user.get_id_and_handle_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetDidWebInfoByHandle { handle, tx } => {
let _ = tx.send(
user.get_did_web_info_by_handle(&handle)
.map_err(metastore_to_db),
);
}
UserRequest::GetDidWebOverrides { user_id, tx } => {
let _ = tx.send(user.get_did_web_overrides(user_id).map_err(metastore_to_db));
}
UserRequest::GetHandleByDid { did, tx } => {
let _ = tx.send(user.get_handle_by_did(&did).map_err(metastore_to_db));
}
UserRequest::IsAccountActiveByDid { did, tx } => {
let _ = tx.send(user.is_account_active_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetUserForDeletion { did, tx } => {
let _ = tx.send(user.get_user_for_deletion(&did).map_err(metastore_to_db));
}
UserRequest::CheckHandleExists {
handle,
exclude_user_id,
tx,
} => {
let _ = tx.send(
user.check_handle_exists(&handle, exclude_user_id)
.map_err(metastore_to_db),
);
}
UserRequest::UpdateHandle {
user_id,
handle,
tx,
} => {
let _ = tx.send(
user.update_handle(user_id, &handle)
.map_err(metastore_to_db),
);
}
UserRequest::GetUserWithKeyByDid { did, tx } => {
let _ = tx.send(user.get_user_with_key_by_did(&did).map_err(metastore_to_db));
}
UserRequest::IsAccountMigrated { did, tx } => {
let _ = tx.send(user.is_account_migrated(&did).map_err(metastore_to_db));
}
UserRequest::HasVerifiedCommsChannel { did, tx } => {
let _ = tx.send(
user.has_verified_comms_channel(&did)
.map_err(metastore_to_db),
);
}
UserRequest::GetIdByHandle { handle, tx } => {
let _ = tx.send(user.get_id_by_handle(&handle).map_err(metastore_to_db));
}
UserRequest::GetEmailInfoByDid { did, tx } => {
let _ = tx.send(user.get_email_info_by_did(&did).map_err(metastore_to_db));
}
UserRequest::CheckEmailExists {
email,
exclude_user_id,
tx,
} => {
let _ = tx.send(
user.check_email_exists(&email, exclude_user_id)
.map_err(metastore_to_db),
);
}
UserRequest::UpdateEmail { user_id, email, tx } => {
let _ = tx.send(user.update_email(user_id, &email).map_err(metastore_to_db));
}
UserRequest::SetEmailVerified {
user_id,
verified,
tx,
} => {
let _ = tx.send(
user.set_email_verified(user_id, verified)
.map_err(metastore_to_db),
);
}
UserRequest::CheckEmailVerifiedByIdentifier { identifier, tx } => {
let _ = tx.send(
user.check_email_verified_by_identifier(&identifier)
.map_err(metastore_to_db),
);
}
UserRequest::CheckChannelVerifiedByDid { did, channel, tx } => {
let _ = tx.send(
user.check_channel_verified_by_did(&did, channel)
.map_err(metastore_to_db),
);
}
UserRequest::AdminUpdateEmail { did, email, tx } => {
let _ = tx.send(
user.admin_update_email(&did, &email)
.map_err(metastore_to_db),
);
}
UserRequest::AdminUpdateHandle { did, handle, tx } => {
let _ = tx.send(
user.admin_update_handle(&did, &handle)
.map_err(metastore_to_db),
);
}
UserRequest::AdminUpdatePassword {
did,
password_hash,
tx,
} => {
let _ = tx.send(
user.admin_update_password(&did, &password_hash)
.map_err(metastore_to_db),
);
}
UserRequest::SetAdminStatus { did, is_admin, tx } => {
let _ = tx.send(
user.set_admin_status(&did, is_admin)
.map_err(metastore_to_db),
);
}
UserRequest::GetNotificationPrefs { did, tx } => {
let _ = tx.send(user.get_notification_prefs(&did).map_err(metastore_to_db));
}
UserRequest::GetIdHandleEmailByDid { did, tx } => {
let _ = tx.send(
user.get_id_handle_email_by_did(&did)
.map_err(metastore_to_db),
);
}
UserRequest::UpdatePreferredCommsChannel { did, channel, tx } => {
let _ = tx.send(
user.update_preferred_comms_channel(&did, channel)
.map_err(metastore_to_db),
);
}
UserRequest::ClearDiscord { user_id, tx } => {
let _ = tx.send(user.clear_discord(user_id).map_err(metastore_to_db));
}
UserRequest::ClearTelegram { user_id, tx } => {
let _ = tx.send(user.clear_telegram(user_id).map_err(metastore_to_db));
}
UserRequest::ClearSignal { user_id, tx } => {
let _ = tx.send(user.clear_signal(user_id).map_err(metastore_to_db));
}
UserRequest::SetUnverifiedSignal {
user_id,
signal_username,
tx,
} => {
let _ = tx.send(
user.set_unverified_signal(user_id, &signal_username)
.map_err(metastore_to_db),
);
}
UserRequest::SetUnverifiedTelegram {
user_id,
telegram_username,
tx,
} => {
let _ = tx.send(
user.set_unverified_telegram(user_id, &telegram_username)
.map_err(metastore_to_db),
);
}
UserRequest::StoreTelegramChatId {
telegram_username,
chat_id,
handle,
tx,
} => {
let _ = tx.send(
user.store_telegram_chat_id(&telegram_username, chat_id, handle.as_deref())
.map_err(metastore_to_db),
);
}
UserRequest::GetTelegramChatId { user_id, tx } => {
let _ = tx.send(user.get_telegram_chat_id(user_id).map_err(metastore_to_db));
}
UserRequest::SetUnverifiedDiscord {
user_id,
discord_username,
tx,
} => {
let _ = tx.send(
user.set_unverified_discord(user_id, &discord_username)
.map_err(metastore_to_db),
);
}
UserRequest::StoreDiscordUserId {
discord_username,
discord_id,
handle,
tx,
} => {
let _ = tx.send(
user.store_discord_user_id(&discord_username, &discord_id, handle.as_deref())
.map_err(metastore_to_db),
);
}
UserRequest::GetVerificationInfo { did, tx } => {
let _ = tx.send(user.get_verification_info(&did).map_err(metastore_to_db));
}
UserRequest::VerifyEmailChannel { user_id, email, tx } => {
let _ = tx.send(
user.verify_email_channel(user_id, &email)
.map_err(metastore_to_db),
);
}
UserRequest::VerifyDiscordChannel {
user_id,
discord_id,
tx,
} => {
let _ = tx.send(
user.verify_discord_channel(user_id, &discord_id)
.map_err(metastore_to_db),
);
}
UserRequest::VerifyTelegramChannel {
user_id,
telegram_username,
tx,
} => {
let _ = tx.send(
user.verify_telegram_channel(user_id, &telegram_username)
.map_err(metastore_to_db),
);
}
UserRequest::VerifySignalChannel {
user_id,
signal_username,
tx,
} => {
let _ = tx.send(
user.verify_signal_channel(user_id, &signal_username)
.map_err(metastore_to_db),
);
}
UserRequest::SetEmailVerifiedFlag { user_id, tx } => {
let _ = tx.send(
user.set_email_verified_flag(user_id)
.map_err(metastore_to_db),
);
}
UserRequest::SetDiscordVerifiedFlag { user_id, tx } => {
let _ = tx.send(
user.set_discord_verified_flag(user_id)
.map_err(metastore_to_db),
);
}
UserRequest::SetTelegramVerifiedFlag { user_id, tx } => {
let _ = tx.send(
user.set_telegram_verified_flag(user_id)
.map_err(metastore_to_db),
);
}
UserRequest::SetSignalVerifiedFlag { user_id, tx } => {
let _ = tx.send(
user.set_signal_verified_flag(user_id)
.map_err(metastore_to_db),
);
}
UserRequest::HasTotpEnabled { did, tx } => {
let _ = tx.send(user.has_totp_enabled(&did).map_err(metastore_to_db));
}
UserRequest::HasPasskeys { did, tx } => {
let _ = tx.send(user.has_passkeys(&did).map_err(metastore_to_db));
}
UserRequest::GetPasswordHashByDid { did, tx } => {
let _ = tx.send(user.get_password_hash_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetPasskeysForUser { did, tx } => {
let _ = tx.send(user.get_passkeys_for_user(&did).map_err(metastore_to_db));
}
UserRequest::GetPasskeyByCredentialId { credential_id, tx } => {
let _ = tx.send(
user.get_passkey_by_credential_id(&credential_id)
.map_err(metastore_to_db),
);
}
UserRequest::SavePasskey {
did,
credential_id,
public_key,
friendly_name,
tx,
} => {
let _ = tx.send(
user.save_passkey(&did, &credential_id, &public_key, friendly_name.as_deref())
.map_err(metastore_to_db),
);
}
UserRequest::UpdatePasskeyCounter {
credential_id,
new_counter,
tx,
} => {
let _ = tx.send(
user.update_passkey_counter(&credential_id, new_counter)
.map_err(metastore_to_db),
);
}
UserRequest::DeletePasskey { id, did, tx } => {
let _ = tx.send(user.delete_passkey(id, &did).map_err(metastore_to_db));
}
UserRequest::UpdatePasskeyName { id, did, name, tx } => {
let _ = tx.send(
user.update_passkey_name(id, &did, &name)
.map_err(metastore_to_db),
);
}
UserRequest::SaveWebauthnChallenge {
did,
challenge_type,
state_json,
tx,
} => {
let _ = tx.send(
user.save_webauthn_challenge(&did, challenge_type, &state_json)
.map_err(metastore_to_db),
);
}
UserRequest::LoadWebauthnChallenge {
did,
challenge_type,
tx,
} => {
let _ = tx.send(
user.load_webauthn_challenge(&did, challenge_type)
.map_err(metastore_to_db),
);
}
UserRequest::DeleteWebauthnChallenge {
did,
challenge_type,
tx,
} => {
let _ = tx.send(
user.delete_webauthn_challenge(&did, challenge_type)
.map_err(metastore_to_db),
);
}
UserRequest::SaveDiscoverableChallenge {
request_key,
state_json,
tx,
} => {
let _ = tx.send(
user.save_discoverable_challenge(&request_key, &state_json)
.map_err(metastore_to_db),
);
}
UserRequest::LoadDiscoverableChallenge { request_key, tx } => {
let _ = tx.send(
user.load_discoverable_challenge(&request_key)
.map_err(metastore_to_db),
);
}
UserRequest::DeleteDiscoverableChallenge { request_key, tx } => {
let _ = tx.send(
user.delete_discoverable_challenge(&request_key)
.map_err(metastore_to_db),
);
}
UserRequest::GetTotpRecord { did, tx } => {
let _ = tx.send(user.get_totp_record(&did).map_err(metastore_to_db));
}
UserRequest::GetTotpRecordState { did, tx } => {
let _ = tx.send(user.get_totp_record_state(&did).map_err(metastore_to_db));
}
UserRequest::UpsertTotpSecret {
did,
secret_encrypted,
encryption_version,
tx,
} => {
let _ = tx.send(
user.upsert_totp_secret(&did, &secret_encrypted, encryption_version)
.map_err(metastore_to_db),
);
}
UserRequest::SetTotpVerified { did, tx } => {
let _ = tx.send(user.set_totp_verified(&did).map_err(metastore_to_db));
}
UserRequest::UpdateTotpLastUsed { did, tx } => {
let _ = tx.send(user.update_totp_last_used(&did).map_err(metastore_to_db));
}
UserRequest::DeleteTotp { did, tx } => {
let _ = tx.send(user.delete_totp(&did).map_err(metastore_to_db));
}
UserRequest::GetUnusedBackupCodes { did, tx } => {
let _ = tx.send(user.get_unused_backup_codes(&did).map_err(metastore_to_db));
}
UserRequest::MarkBackupCodeUsed { code_id, tx } => {
let _ = tx.send(user.mark_backup_code_used(code_id).map_err(metastore_to_db));
}
UserRequest::CountUnusedBackupCodes { did, tx } => {
let _ = tx.send(
user.count_unused_backup_codes(&did)
.map_err(metastore_to_db),
);
}
UserRequest::DeleteBackupCodes { did, tx } => {
let _ = tx.send(user.delete_backup_codes(&did).map_err(metastore_to_db));
}
UserRequest::InsertBackupCodes {
did,
code_hashes,
tx,
} => {
let _ = tx.send(
user.insert_backup_codes(&did, &code_hashes)
.map_err(metastore_to_db),
);
}
UserRequest::EnableTotpWithBackupCodes {
did,
code_hashes,
tx,
} => {
let _ = tx.send(
user.enable_totp_with_backup_codes(&did, &code_hashes)
.map_err(metastore_to_db),
);
}
UserRequest::DeleteTotpAndBackupCodes { did, tx } => {
let _ = tx.send(
user.delete_totp_and_backup_codes(&did)
.map_err(metastore_to_db),
);
}
UserRequest::ReplaceBackupCodes {
did,
code_hashes,
tx,
} => {
let _ = tx.send(
user.replace_backup_codes(&did, &code_hashes)
.map_err(metastore_to_db),
);
}
UserRequest::GetSessionInfoByDid { did, tx } => {
let _ = tx.send(user.get_session_info_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetLegacyLoginPref { did, tx } => {
let _ = tx.send(user.get_legacy_login_pref(&did).map_err(metastore_to_db));
}
UserRequest::UpdateLegacyLogin { did, allow, tx } => {
let _ = tx.send(
user.update_legacy_login(&did, allow)
.map_err(metastore_to_db),
);
}
UserRequest::UpdateLocale { did, locale, tx } => {
let _ = tx.send(user.update_locale(&did, &locale).map_err(metastore_to_db));
}
UserRequest::GetLoginFullByIdentifier { identifier, tx } => {
let _ = tx.send(
user.get_login_full_by_identifier(&identifier)
.map_err(metastore_to_db),
);
}
UserRequest::GetConfirmSignupByDid { did, tx } => {
let _ = tx.send(
user.get_confirm_signup_by_did(&did)
.map_err(metastore_to_db),
);
}
UserRequest::GetResendVerificationByDid { did, tx } => {
let _ = tx.send(
user.get_resend_verification_by_did(&did)
.map_err(metastore_to_db),
);
}
UserRequest::SetChannelVerified { did, channel, tx } => {
let _ = tx.send(
user.set_channel_verified(&did, channel)
.map_err(metastore_to_db),
);
}
UserRequest::GetIdByEmailOrHandle { email, handle, tx } => {
let _ = tx.send(
user.get_id_by_email_or_handle(&email, &handle)
.map_err(metastore_to_db),
);
}
UserRequest::CountAccountsByEmail { email, tx } => {
let _ = tx.send(
user.count_accounts_by_email(&email)
.map_err(metastore_to_db),
);
}
UserRequest::GetHandlesByEmail { email, tx } => {
let _ = tx.send(user.get_handles_by_email(&email).map_err(metastore_to_db));
}
UserRequest::SetPasswordResetCode {
user_id,
code,
expires_at,
tx,
} => {
let _ = tx.send(
user.set_password_reset_code(user_id, &code, expires_at)
.map_err(metastore_to_db),
);
}
UserRequest::GetUserByResetCode { code, tx } => {
let _ = tx.send(user.get_user_by_reset_code(&code).map_err(metastore_to_db));
}
UserRequest::ClearPasswordResetCode { user_id, tx } => {
let _ = tx.send(
user.clear_password_reset_code(user_id)
.map_err(metastore_to_db),
);
}
UserRequest::GetIdAndPasswordHashByDid { did, tx } => {
let _ = tx.send(
user.get_id_and_password_hash_by_did(&did)
.map_err(metastore_to_db),
);
}
UserRequest::UpdatePasswordHash {
user_id,
password_hash,
tx,
} => {
let _ = tx.send(
user.update_password_hash(user_id, &password_hash)
.map_err(metastore_to_db),
);
}
UserRequest::ResetPasswordWithSessions {
user_id,
password_hash,
tx,
} => {
let _ = tx.send(
user.reset_password_with_sessions(user_id, &password_hash)
.map_err(metastore_to_db),
);
}
UserRequest::ActivateAccount { did, tx } => {
let _ = tx.send(user.activate_account(&did).map_err(metastore_to_db));
}
UserRequest::DeactivateAccount {
did,
delete_after,
tx,
} => {
let _ = tx.send(
user.deactivate_account(&did, delete_after)
.map_err(metastore_to_db),
);
}
UserRequest::HasPasswordByDid { did, tx } => {
let _ = tx.send(user.has_password_by_did(&did).map_err(metastore_to_db));
}
UserRequest::GetPasswordInfoByDid { did, tx } => {
let _ = tx.send(user.get_password_info_by_did(&did).map_err(metastore_to_db));
}
UserRequest::RemoveUserPassword { user_id, tx } => {
let _ = tx.send(user.remove_user_password(user_id).map_err(metastore_to_db));
}
UserRequest::SetNewUserPassword {
user_id,
password_hash,
tx,
} => {
let _ = tx.send(
user.set_new_user_password(user_id, &password_hash)
.map_err(metastore_to_db),
);
}
UserRequest::GetUserKeyByDid { did, tx } => {
let _ = tx.send(user.get_user_key_by_did(&did).map_err(metastore_to_db));
}
UserRequest::DeleteAccountComplete { user_id, did, tx } => {
let result = purge_repo_side_data(state, user_id, &did)
.and_then(|()| user.delete_account_complete(user_id, &did))
.map_err(metastore_to_db);
let _ = tx.send(result);
}
UserRequest::SetUserTakedown {
did,
takedown_ref,
tx,
} => {
let _ = tx.send(
user.set_user_takedown(&did, takedown_ref.as_deref())
.map_err(metastore_to_db),
);
}
UserRequest::AdminDeleteAccountComplete { user_id, did, tx } => {
let result = purge_repo_side_data(state, user_id, &did)
.and_then(|()| user.admin_delete_account_complete(user_id, &did))
.map_err(metastore_to_db);
let _ = tx.send(result);
}
UserRequest::GetUserForDidDoc { did, tx } => {
let _ = tx.send(user.get_user_for_did_doc(&did).map_err(metastore_to_db));
}
UserRequest::GetUserForDidDocBuild { did, tx } => {
let _ = tx.send(
user.get_user_for_did_doc_build(&did)
.map_err(metastore_to_db),
);
}
UserRequest::UpsertDidWebOverrides {
user_id,
verification_methods,
also_known_as,
tx,
} => {
let _ = tx.send(
user.upsert_did_web_overrides(user_id, verification_methods, also_known_as)
.map_err(metastore_to_db),
);
}
UserRequest::UpdateMigratedToPds { did, endpoint, tx } => {
let _ = tx.send(
user.update_migrated_to_pds(&did, &endpoint)
.map_err(metastore_to_db),
);
}
UserRequest::GetUserForPasskeySetup { did, tx } => {
let _ = tx.send(
user.get_user_for_passkey_setup(&did)
.map_err(metastore_to_db),
);
}
UserRequest::GetUserForPasskeyRecovery {
identifier,
normalized_handle,
tx,
} => {
let _ = tx.send(
user.get_user_for_passkey_recovery(&identifier, &normalized_handle)
.map_err(metastore_to_db),
);
}
UserRequest::SetRecoveryToken {
did,
token_hash,
expires_at,
tx,
} => {
let _ = tx.send(
user.set_recovery_token(&did, &token_hash, expires_at)
.map_err(metastore_to_db),
);
}
UserRequest::GetUserForRecovery { did, tx } => {
let _ = tx.send(user.get_user_for_recovery(&did).map_err(metastore_to_db));
}
UserRequest::GetAccountsScheduledForDeletion { limit, tx } => {
let _ = tx.send(
user.get_accounts_scheduled_for_deletion(limit)
.map_err(metastore_to_db),
);
}
UserRequest::DeleteAccountWithFirehose { user_id, did, tx } => {
let result = user
.delete_account_complete(user_id, &did)
.map_err(metastore_to_db)
.and_then(|()| {
state
.event_ops
.insert_account_event(&did, AccountStatus::Deleted)
});
let _ = tx.send(result.map(|_| ()));
}
UserRequest::CreatePasswordAccount { input, tx } => {
let infra = state.metastore.infra_ops();
let code = input.invite_code.as_ref();
let result = reserve_invite(&infra, code).and_then(|()| {
finalize_account(
&infra,
code,
user.create_password_account(&input),
|result| {
if let Some(key_id) = input.reserved_key_id {
infra
.mark_signing_key_used(key_id)
.map_err(|e| CreateAccountError::Database(e.to_string()))?;
}
Ok(result.user_id)
},
)
});
let _ = tx.send(result);
}
UserRequest::CreateDelegatedAccount { input, tx } => {
let result = user.create_delegated_account(&input).and_then(|account| {
let scope =
tranquil_db_traits::DbScope::new(&input.controller_scopes).map_err(|e| {
tranquil_db_traits::CreateAccountError::Database(format!(
"invalid delegation scope: {e}"
))
})?;
state
.metastore
.delegation_ops()
.create_delegation(
&input.did,
&input.controller_did,
&scope,
&input.controller_did,
)
.map_err(|e| {
tranquil_db_traits::CreateAccountError::Database(format!(
"delegation grant creation failed: {e}"
))
})?;
Ok(account)
});
let _ = tx.send(result);
}
UserRequest::CreatePasskeyAccount { input, tx } => {
let infra = state.metastore.infra_ops();
let code = input.invite_code.as_ref();
let result = reserve_invite(&infra, code).and_then(|()| {
finalize_account(
&infra,
code,
user.create_passkey_account(&input),
|result| {
if let Some(key_id) = input.reserved_key_id {
infra
.mark_signing_key_used(key_id)
.map_err(|e| CreateAccountError::Database(e.to_string()))?;
}
Ok(result.user_id)
},
)
});
let _ = tx.send(result);
}
UserRequest::CreateSsoAccount { input, tx } => {
let sso_ops = state.metastore.sso_ops();
let infra = state.metastore.infra_ops();
let code = input.invite_code.as_ref();
let result = sso_ops
.consume_pending_registration(&input.pending_registration_token)
.map_err(|e| CreateAccountError::Database(e.to_string()))
.and_then(|consumed| match consumed {
Some(_) => reserve_invite(&infra, code).and_then(|()| {
finalize_account(&infra, code, user.create_sso_account(&input), |result| {
sso_ops
.create_external_identity(
&input.did,
input.sso_provider,
&input.sso_provider_user_id,
input.sso_provider_username.as_deref(),
input.sso_provider_email.as_deref(),
)
.map_err(|e| CreateAccountError::Database(e.to_string()))?;
Ok(result.user_id)
})
}),
None => Err(CreateAccountError::InvalidToken),
});
let _ = tx.send(result);
}
UserRequest::ReactivateMigrationAccount { input, tx } => {
let _ = tx.send(user.reactivate_migration_account(&input));
}
UserRequest::CheckHandleAvailableForNewAccount { handle, tx } => {
let _ = tx.send(
user.check_handle_available_for_new_account(&handle)
.map_err(metastore_to_db),
);
}
UserRequest::ReserveHandle {
handle,
reserved_by,
tx,
} => {
let _ = tx.send(
user.reserve_handle(&handle, &reserved_by)
.map_err(metastore_to_db),
);
}
UserRequest::ReleaseHandleReservation { handle, tx } => {
let _ = tx.send(
user.release_handle_reservation(&handle)
.map_err(metastore_to_db),
);
}
UserRequest::CleanupExpiredHandleReservations { tx } => {
let _ = tx.send(
user.cleanup_expired_handle_reservations()
.map_err(metastore_to_db),
);
}
UserRequest::CompletePasskeySetup { input, tx } => {
let _ = tx.send(user.complete_passkey_setup(&input).map_err(metastore_to_db));
}
UserRequest::RecoverPasskeyAccount { input, tx } => {
let _ = tx.send(
user.recover_passkey_account(&input)
.map_err(metastore_to_db),
);
}
UserRequest::GetPasswordResetInfo { email, tx } => {
let _ = tx.send(
user.get_password_reset_info(&email)
.map_err(metastore_to_db),
);
}
UserRequest::EnableTotpVerified {
did,
encrypted_secret,
tx,
} => {
let _ = tx.send(
user.enable_totp_verified(&did, &encrypted_secret)
.map_err(metastore_to_db),
);
}
UserRequest::SetTwoFactorEnabled { did, enabled, tx } => {
let _ = tx.send(
user.set_two_factor_enabled(&did, enabled)
.map_err(metastore_to_db),
);
}
UserRequest::ExpirePasswordResetCode { email, tx } => {
let _ = tx.send(
user.expire_password_reset_code(&email)
.map_err(metastore_to_db),
);
}
}
}
fn handler_loop<S: StorageIO + 'static>(
metastore: Metastore,
bridge: Arc<EventLogBridge<S>>,
blockstore: Option<TranquilBlockStore<RealIO, SystemClock>>,
rx: flume::Receiver<MetastoreRequest>,
thread_index: usize,
) {
let event_ops = metastore.event_ops(Arc::clone(&bridge));
let mut commit_ops = metastore.commit_ops(bridge);
if let Some(bs) = blockstore {
commit_ops = commit_ops.with_blockstore(bs);
}
let state = HandlerState {
metastore,
event_ops,
commit_ops,
};
tracing::info!(thread_index, "metastore handler thread started");
rx.iter().for_each(|req| {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| dispatch(&state, req))) {
Ok(()) => {}
Err(e) => {
let msg = match e.downcast_ref::<&str>() {
Some(s) => (*s).to_owned(),
None => match e.downcast_ref::<String>() {
Some(s) => s.clone(),
None => "unknown panic payload".to_owned(),
},
};
tracing::error!(thread_index, msg, "recovered metastore handler panic");
}
}
});
tracing::info!(thread_index, "metastore handler thread exiting");
}
const DEFAULT_CHANNEL_BOUND: usize = 256;
const MAX_REPOS_WITHOUT_REV: usize = 10_000;
pub struct HandlerPool {
senders: parking_lot::Mutex<Vec<flume::Sender<MetastoreRequest>>>,
handles: parking_lot::Mutex<Option<Vec<JoinHandle<()>>>>,
sender_count: usize,
user_hashes: Arc<UserHashMap>,
round_robin: AtomicUsize,
}
impl HandlerPool {
pub fn spawn<S: StorageIO + 'static>(
metastore: Metastore,
bridge: Arc<EventLogBridge<S>>,
blockstore: Option<TranquilBlockStore<RealIO, SystemClock>>,
thread_count: Option<usize>,
) -> Self {
let count = thread_count
.unwrap_or_else(|| {
std::thread::available_parallelism()
.map(|n| n.get().max(2) / 2)
.unwrap_or(1)
})
.max(1);
let user_hashes = Arc::clone(metastore.user_hashes());
let (senders, handles): (Vec<_>, Vec<_>) = (0..count)
.map(|i| {
let (tx, rx) = flume::bounded(DEFAULT_CHANNEL_BOUND);
let ms = metastore.clone();
let br = Arc::clone(&bridge);
let bs = blockstore.clone();
let handle = std::thread::Builder::new()
.name(format!("metastore-{i}"))
.spawn(move || handler_loop(ms, br, bs, rx, i))
.expect("failed to spawn metastore handler thread");
(tx, handle)
})
.unzip();
Self {
sender_count: senders.len(),
senders: parking_lot::Mutex::new(senders),
handles: parking_lot::Mutex::new(Some(handles)),
user_hashes,
round_robin: AtomicUsize::new(0),
}
}
pub fn send(&self, request: MetastoreRequest) -> Result<(), DbError> {
let senders = self.senders.lock();
if senders.is_empty() {
return Err(DbError::Connection(
"metastore handler pool shut down".to_string(),
));
}
let index = match request.routing(&self.user_hashes) {
Routing::Sharded(bits) => (bits as usize) % senders.len(),
Routing::Global => self.round_robin.fetch_add(1, Ordering::Relaxed) % senders.len(),
};
senders[index].try_send(request).map_err(|e| match e {
flume::TrySendError::Full(_) => {
DbError::Query("metastore handler backpressure".to_string())
}
flume::TrySendError::Disconnected(_) => {
DbError::Connection("metastore handler pool shut down".to_string())
}
})
}
pub fn thread_count(&self) -> usize {
self.sender_count
}
pub async fn close(&self) {
{
self.senders.lock().clear();
}
let handles = { self.handles.lock().take() };
if let Some(handles) = handles {
let join_fut = tokio::task::spawn_blocking(move || {
handles.into_iter().for_each(|h| {
if let Err(e) = h.join() {
tracing::error!("metastore handler thread panicked: {e:?}");
}
});
});
match tokio::time::timeout(std::time::Duration::from_secs(30), join_fut).await {
Ok(_) => tracing::info!("metastore handler threads shut down cleanly"),
Err(_) => tracing::error!("metastore handler thread shutdown timed out after 30s"),
}
}
}
}
impl Drop for HandlerPool {
fn drop(&mut self) {
self.senders.get_mut().clear();
if let Some(handles) = self.handles.get_mut().take() {
tracing::warn!(
"HandlerPool dropped without calling shutdown(); blocking on thread join"
);
handles.into_iter().for_each(|h| {
if let Err(e) = h.join() {
tracing::error!("metastore handler thread panicked: {e:?}");
}
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eventlog::{EventLog, EventLogConfig};
use crate::metastore::MetastoreConfig;
use tranquil_types::{Did, Handle, InviteCode, Jti};
struct TestHarness {
_metastore_dir: tempfile::TempDir,
_eventlog_dir: tempfile::TempDir,
pool: HandlerPool,
}
fn setup() -> TestHarness {
let metastore_dir = tempfile::TempDir::new().unwrap();
let eventlog_dir = tempfile::TempDir::new().unwrap();
let segments_dir = eventlog_dir.path().join("segments");
std::fs::create_dir_all(&segments_dir).unwrap();
let metastore = Metastore::open(
metastore_dir.path(),
MetastoreConfig {
cache_size_bytes: 64 * 1024 * 1024,
},
)
.unwrap();
let event_log = EventLog::open(
EventLogConfig {
segments_dir,
..EventLogConfig::default()
},
RealIO::new(),
)
.unwrap();
let bridge = Arc::new(EventLogBridge::new(Arc::new(event_log)));
let pool = HandlerPool::spawn::<RealIO>(metastore, bridge, None, Some(2));
TestHarness {
_metastore_dir: metastore_dir,
_eventlog_dir: eventlog_dir,
pool,
}
}
fn test_cid_link(seed: u8) -> CidLink {
let digest: [u8; 32] = std::array::from_fn(|i| seed.wrapping_add(i as u8));
let mh = multihash::Multihash::<64>::wrap(0x12, &digest).unwrap();
let c = cid::Cid::new_v1(0x71, mh);
CidLink::from_cid(&c)
}
fn test_rev(seq: u64) -> Tid {
const ALPHABET: &[u8] = b"234567abcdefghijklmnopqrstuvwxyz";
let s: String = (0..13)
.rev()
.map(|i| ALPHABET[((seq >> (i * 5)) & 0x1F) as usize] as char)
.collect();
Tid::new(s).expect("generated TID is valid")
}
#[tokio::test]
async fn create_and_get_roundtrip() {
let h = setup();
let user_id = Uuid::new_v4();
let did = Did::new("did:plc:whelk").expect("test DID is well-formed");
let handle = Handle::new("whelk.oyster.cafe").expect("test handle is valid");
let cid = test_cid_link(1);
let rev = test_rev(1);
let (tx, rx) = oneshot::channel();
h.pool
.send(MetastoreRequest::Repo(RepoRequest::CreateRepoFull {
user_id,
did,
handle,
repo_root_cid: cid.clone(),
repo_rev: rev.clone(),
tx,
}))
.unwrap();
rx.await.unwrap().unwrap();
let (tx, rx) = oneshot::channel();
h.pool
.send(MetastoreRequest::Repo(RepoRequest::GetRepo { user_id, tx }))
.unwrap();
let repo = rx.await.unwrap().unwrap().unwrap();
assert_eq!(repo.repo_root_cid, cid);
assert_eq!(repo.repo_rev.as_deref(), Some(rev.as_str()));
}
#[test]
fn reserve_invite_code_consumes_once_and_guards_exhaustion() {
let dir = tempfile::TempDir::new().unwrap();
let metastore = Metastore::open(
dir.path(),
MetastoreConfig {
cache_size_bytes: 64 * 1024 * 1024,
},
)
.unwrap();
let infra = metastore.infra_ops();
let squid = InviteCode::new("squid-invite");
let owner = Did::new("did:plc:whelk").expect("valid DID");
let whelk = InviteCode::new("whelk");
assert!(infra.create_invite_code(&squid, 1, &owner).unwrap());
infra.reserve_invite_code(&squid).unwrap();
assert_eq!(
infra.get_invite_code_available_uses(&squid).unwrap(),
Some(0)
);
assert!(matches!(
infra.reserve_invite_code(&squid),
Err(InviteCodeError::ExhaustedUses)
));
assert_eq!(
infra.get_invite_code_available_uses(&squid).unwrap(),
Some(0),
"a rejected reservation must not decrement below zero"
);
assert!(matches!(
infra.reserve_invite_code(&whelk),
Err(InviteCodeError::NotFound)
));
}
#[test]
fn refund_invite_code_restores_a_reserved_use() {
let dir = tempfile::TempDir::new().unwrap();
let metastore = Metastore::open(
dir.path(),
MetastoreConfig {
cache_size_bytes: 64 * 1024 * 1024,
},
)
.unwrap();
let infra = metastore.infra_ops();
let squid = InviteCode::new("squid-invite");
let owner = Did::new("did:plc:whelk").expect("valid DID");
let whelk = InviteCode::new("whelk");
assert!(infra.create_invite_code(&squid, 1, &owner).unwrap());
infra.reserve_invite_code(&squid).unwrap();
infra.refund_invite_code(&squid).unwrap();
assert_eq!(
infra.get_invite_code_available_uses(&squid).unwrap(),
Some(1),
"refund must return the reserved use so a failed signup does not burn it"
);
infra.reserve_invite_code(&squid).unwrap();
assert_eq!(
infra.get_invite_code_available_uses(&squid).unwrap(),
Some(0)
);
assert!(matches!(
infra.refund_invite_code(&whelk),
Err(InviteCodeError::NotFound)
));
}
#[test]
fn routing_determinism() {
let user_id = Uuid::from_u128(0x12345678);
let bits = user_id.as_u128() as u64;
let thread_count = 4usize;
let expected = (bits as usize) % thread_count;
(0..100).for_each(|_| {
assert_eq!((bits as usize) % thread_count, expected);
});
}
#[test]
fn global_round_robin_distributes() {
let counter = AtomicUsize::new(0);
let thread_count = 4usize;
let indices: Vec<usize> = (0..8)
.map(|_| counter.fetch_add(1, Ordering::Relaxed) % thread_count)
.collect();
assert_eq!(indices, vec![0, 1, 2, 3, 0, 1, 2, 3]);
}
#[test]
fn session_mutations_route_by_did() {
let dir = tempfile::TempDir::new().unwrap();
let ms = Metastore::open(
dir.path(),
MetastoreConfig {
cache_size_bytes: 1024 * 1024,
},
)
.unwrap();
let user_hashes = ms.user_hashes().as_ref();
let did = Did::new("did:plc:limpet").expect("test DID is well-formed");
let expected = did_to_routing(&did);
let sid = SessionId::new(7);
let (tx, _rx) = oneshot::channel();
let delete_by_id = SessionRequest::DeleteSessionById {
session_id: sid,
did: did.clone(),
tx,
};
let (tx, _rx) = oneshot::channel();
let delete_by_jti = SessionRequest::DeleteSessionByAccessJti {
access_jti: "a".to_string(),
did: did.clone(),
tx,
};
let (tx, _rx) = oneshot::channel();
let delete_by_did = SessionRequest::DeleteSessionsByDid {
did: did.clone(),
tx,
};
let (tx, _rx) = oneshot::channel();
let refresh = SessionRequest::RefreshSessionAtomic {
data: tranquil_db_traits::SessionRefreshData {
did: did.clone(),
old_refresh_jti: Jti::new("r0"),
session_id: sid,
new_access_jti: Jti::new("a1"),
new_refresh_jti: Jti::new("r1"),
new_access_expires_at: Utc::now(),
new_refresh_expires_at: Utc::now(),
},
tx,
};
assert_eq!(delete_by_id.routing(user_hashes), expected);
assert_eq!(delete_by_jti.routing(user_hashes), expected);
assert_eq!(delete_by_did.routing(user_hashes), expected);
assert_eq!(refresh.routing(user_hashes), expected);
}
#[tokio::test]
async fn shutdown_completes_inflight() {
let h = setup();
let user_id = Uuid::new_v4();
let did = Did::new("did:plc:scallop").expect("test DID is well-formed");
let handle = Handle::new("scallop.oyster.cafe").expect("test handle is valid");
let cid = test_cid_link(2);
let (tx, rx) = oneshot::channel();
h.pool
.send(MetastoreRequest::Repo(RepoRequest::CreateRepoFull {
user_id,
did,
handle,
repo_root_cid: cid,
repo_rev: test_rev(1),
tx,
}))
.unwrap();
rx.await.unwrap().unwrap();
h.pool.close().await;
}
}