From e9454ae1c29983b05bcefa6542461b88cef627be Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 12:33:46 -0500 Subject: [PATCH 01/10] domains/repo.rs: take typed TenantId --- server/src/api/domains/routes.rs | 4 +-- server/src/api/links/routes.rs | 4 +-- .../billing/repos/resource_counts_adapter.rs | 2 +- server/src/services/domains/repo.rs | 35 ++++++++++--------- server/src/services/domains/service.rs | 13 ++----- server/src/services/links/service.rs | 4 +-- server/src/services/links/service_tests.rs | 17 ++++++--- server/tests/api/resolve.rs | 2 +- server/tests/api/sdk_keys.rs | 2 +- server/tests/common/mocks/domains.rs | 29 +++++++-------- server/tests/common/mod.rs | 2 +- 11 files changed, 55 insertions(+), 59 deletions(-) diff --git a/server/src/api/domains/routes.rs b/server/src/api/domains/routes.rs index f9032c91..0923bfc4 100644 --- a/server/src/api/domains/routes.rs +++ b/server/src/api/domains/routes.rs @@ -167,7 +167,7 @@ pub async fn list_domains( .into_response(); }; - match repo.list_by_tenant(&tenant.to_object_id()).await { + match repo.list_by_tenant(&tenant).await { Ok(domains) => { let details: Vec = domains .iter() @@ -221,7 +221,7 @@ pub async fn delete_domain( .into_response(); }; - match repo.delete_domain(&tenant.to_object_id(), &domain).await { + match repo.delete_domain(&tenant, &domain).await { Ok(true) => { // Remove TLS certificate from Fly.io (best-effort). // DB is authoritative — orphaned certs are harmless and can be cleaned up later. diff --git a/server/src/api/links/routes.rs b/server/src/api/links/routes.rs index 245823f0..2c9dcef5 100644 --- a/server/src/api/links/routes.rs +++ b/server/src/api/links/routes.rs @@ -686,7 +686,7 @@ async fn do_resolve( // Look up alternate domain for the "Open in App" button. let alternate_domain = if let Some(domains_repo) = &state.domains_repo { domains_repo - .find_alternate_by_tenant(link.tenant_id.as_object_id()) + .find_alternate_by_tenant(&link.tenant_id) .await .ok() .flatten() @@ -884,7 +884,7 @@ async fn lookup_tenant_domain( return (None, false); }; let domains = repo - .list_by_tenant(tenant_id.as_object_id()) + .list_by_tenant(tenant_id) .await .ok() .unwrap_or_default(); diff --git a/server/src/services/billing/repos/resource_counts_adapter.rs b/server/src/services/billing/repos/resource_counts_adapter.rs index 01b7b151..7b23df18 100644 --- a/server/src/services/billing/repos/resource_counts_adapter.rs +++ b/server/src/services/billing/repos/resource_counts_adapter.rs @@ -28,7 +28,7 @@ impl ResourceCounts for RepoResourceCounts { let oid = tenant_id.as_object_id(); match resource { Resource::CreateLink => self.links.count_links_by_tenant(oid).await, - Resource::CreateDomain => self.domains.count_by_tenant(oid).await, + Resource::CreateDomain => self.domains.count_by_tenant(tenant_id).await, Resource::InviteTeamMember => self .users .count_verified_by_tenant(oid) diff --git a/server/src/services/domains/repo.rs b/server/src/services/domains/repo.rs index 20c1ae04..5ede977c 100644 --- a/server/src/services/domains/repo.rs +++ b/server/src/services/domains/repo.rs @@ -1,8 +1,9 @@ use async_trait::async_trait; -use mongodb::bson::{doc, oid::ObjectId, DateTime}; +use mongodb::bson::{doc, DateTime}; use mongodb::options::IndexOptions; use mongodb::{Collection, Database}; +use crate::core::public_id::{DomainId, TenantId}; use crate::ensure_index; use super::models::{Domain, DomainRole}; @@ -13,7 +14,7 @@ use super::models::{Domain, DomainRole}; pub trait DomainsRepository: Send + Sync { async fn create_domain( &self, - tenant_id: ObjectId, + tenant_id: TenantId, domain: String, verification_token: String, role: DomainRole, @@ -21,18 +22,18 @@ pub trait DomainsRepository: Send + Sync { async fn find_by_domain(&self, domain: &str) -> Result, String>; - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String>; + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String>; /// Total domains attached to this tenant — feeds the CreateDomain quota. - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result; + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result; - async fn delete_domain(&self, tenant_id: &ObjectId, domain: &str) -> Result; + async fn delete_domain(&self, tenant_id: &TenantId, domain: &str) -> Result; async fn mark_verified(&self, domain: &str) -> Result<(), String>; async fn find_alternate_by_tenant( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, ) -> Result, String>; } @@ -64,14 +65,14 @@ impl DomainsRepo { impl DomainsRepository for DomainsRepo { async fn create_domain( &self, - tenant_id: ObjectId, + tenant_id: TenantId, domain: String, verification_token: String, role: DomainRole, ) -> Result { let doc = Domain { - id: crate::core::public_id::DomainId::new(), - tenant_id: crate::core::public_id::TenantId::from_object_id(tenant_id), + id: DomainId::new(), + tenant_id, domain, verified: false, verification_token, @@ -92,17 +93,17 @@ impl DomainsRepository for DomainsRepo { .map_err(|e| e.to_string()) } - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { self.domains - .count_documents(doc! { "tenant_id": tenant_id }) + .count_documents(doc! { "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .domains - .find(doc! { "tenant_id": tenant_id }) + .find(doc! { "tenant_id": *tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -114,10 +115,10 @@ impl DomainsRepository for DomainsRepo { Ok(domains) } - async fn delete_domain(&self, tenant_id: &ObjectId, domain: &str) -> Result { + async fn delete_domain(&self, tenant_id: &TenantId, domain: &str) -> Result { let result = self .domains - .delete_one(doc! { "tenant_id": tenant_id, "domain": domain }) + .delete_one(doc! { "tenant_id": *tenant_id, "domain": domain }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) @@ -136,10 +137,10 @@ impl DomainsRepository for DomainsRepo { async fn find_alternate_by_tenant( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, ) -> Result, String> { self.domains - .find_one(doc! { "tenant_id": tenant_id, "role": "alternate", "verified": true }) + .find_one(doc! { "tenant_id": *tenant_id, "role": "alternate", "verified": true }) .await .map_err(|e| e.to_string()) } diff --git a/server/src/services/domains/service.rs b/server/src/services/domains/service.rs index 9eac2342..723de330 100644 --- a/server/src/services/domains/service.rs +++ b/server/src/services/domains/service.rs @@ -39,11 +39,7 @@ impl DomainsService { } if role == DomainRole::Alternate { - if let Ok(Some(_)) = self - .repo - .find_alternate_by_tenant(ctx.tenant_id.as_object_id()) - .await - { + if let Ok(Some(_)) = self.repo.find_alternate_by_tenant(&ctx.tenant_id).await { return Err(DomainError::AlternateLimit); } } @@ -61,12 +57,7 @@ impl DomainsService { match self .repo - .create_domain( - ctx.tenant_id.to_object_id(), - domain, - verification_token, - role, - ) + .create_domain(ctx.tenant_id, domain, verification_token, role) .await { Ok(d) => Ok(d), diff --git a/server/src/services/links/service.rs b/server/src/services/links/service.rs index 8c063511..b3fb9fc3 100644 --- a/server/src/services/links/service.rs +++ b/server/src/services/links/service.rs @@ -945,7 +945,7 @@ impl LinksService { let Some(ref repo) = self.domains_repo else { return false; }; - repo.list_by_tenant(tenant_id.as_object_id()) + repo.list_by_tenant(tenant_id) .await .ok() .map(|domains| domains.iter().any(|d| d.verified)) @@ -1055,7 +1055,7 @@ pub async fn resolve_verified_primary_domain( tenant_id: &TenantId, ) -> Option { let repo = domains_repo?; - repo.list_by_tenant(tenant_id.as_object_id()) + repo.list_by_tenant(tenant_id) .await .ok()? .into_iter() diff --git a/server/src/services/links/service_tests.rs b/server/src/services/links/service_tests.rs index a1647800..6f7b064d 100644 --- a/server/src/services/links/service_tests.rs +++ b/server/src/services/links/service_tests.rs @@ -323,7 +323,7 @@ struct MockDomainsRepo { impl DomainsRepository for MockDomainsRepo { async fn create_domain( &self, - _tenant_id: ObjectId, + _tenant_id: crate::core::public_id::TenantId, _domain: String, _verification_token: String, _role: crate::services::domains::models::DomainRole, @@ -340,7 +340,7 @@ impl DomainsRepository for MockDomainsRepo { async fn list_by_tenant( &self, - _tenant_id: &ObjectId, + _tenant_id: &crate::core::public_id::TenantId, ) -> Result, String> { if self.has_verified { Ok(vec![crate::services::domains::models::Domain { @@ -357,11 +357,18 @@ impl DomainsRepository for MockDomainsRepo { } } - async fn count_by_tenant(&self, _tenant_id: &ObjectId) -> Result { + async fn count_by_tenant( + &self, + _tenant_id: &crate::core::public_id::TenantId, + ) -> Result { Ok(if self.has_verified { 1 } else { 0 }) } - async fn delete_domain(&self, _tenant_id: &ObjectId, _domain: &str) -> Result { + async fn delete_domain( + &self, + _tenant_id: &crate::core::public_id::TenantId, + _domain: &str, + ) -> Result { Ok(true) } @@ -371,7 +378,7 @@ impl DomainsRepository for MockDomainsRepo { async fn find_alternate_by_tenant( &self, - _tenant_id: &ObjectId, + _tenant_id: &crate::core::public_id::TenantId, ) -> Result, String> { Ok(None) } diff --git a/server/tests/api/resolve.rs b/server/tests/api/resolve.rs index a8554946..f65efce9 100644 --- a/server/tests/api/resolve.rs +++ b/server/tests/api/resolve.rs @@ -224,7 +224,7 @@ async fn resolve_custom_domain_unverified_returns_404() { // Create an unverified domain (seed without marking verified). app.domains_repo .create_domain( - tenant_id, + rift::core::public_id::TenantId::from_object_id(tenant_id), "unverified.example.com".to_string(), "tok".to_string(), rift::services::domains::models::DomainRole::Primary, diff --git a/server/tests/api/sdk_keys.rs b/server/tests/api/sdk_keys.rs index 5667fefd..8e5cbe37 100644 --- a/server/tests/api/sdk_keys.rs +++ b/server/tests/api/sdk_keys.rs @@ -35,7 +35,7 @@ async fn create_sdk_key_rejects_unverified_domain() { // Create an unverified domain. app.domains_repo .create_domain( - tenant_id, + rift::core::public_id::TenantId::from_object_id(tenant_id), "unverified.example.com".to_string(), "tok".to_string(), rift::services::domains::models::DomainRole::Primary, diff --git a/server/tests/common/mocks/domains.rs b/server/tests/common/mocks/domains.rs index f3676d4b..fecb9c46 100644 --- a/server/tests/common/mocks/domains.rs +++ b/server/tests/common/mocks/domains.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; -use mongodb::bson::{oid::ObjectId, DateTime}; +use mongodb::bson::DateTime; use std::sync::Mutex; +use rift::core::public_id::{DomainId, TenantId}; use rift::services::domains::models::{Domain, DomainRole}; use rift::services::domains::repo::DomainsRepository; @@ -14,7 +15,7 @@ pub struct MockDomainsRepo { impl DomainsRepository for MockDomainsRepo { async fn create_domain( &self, - tenant_id: ObjectId, + tenant_id: TenantId, domain: String, verification_token: String, role: DomainRole, @@ -24,8 +25,8 @@ impl DomainsRepository for MockDomainsRepo { return Err("E11000 duplicate key".to_string()); } let doc = Domain { - id: rift::core::public_id::DomainId::new(), - tenant_id: rift::core::public_id::TenantId::from_object_id(tenant_id), + id: DomainId::new(), + tenant_id, domain, verified: false, verification_token, @@ -46,31 +47,31 @@ impl DomainsRepository for MockDomainsRepo { .cloned()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { Ok(self .domains .lock() .unwrap() .iter() - .filter(|d| d.tenant_id.to_object_id() == *tenant_id) + .filter(|d| d.tenant_id == *tenant_id) .cloned() .collect()) } - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { Ok(self .domains .lock() .unwrap() .iter() - .filter(|d| d.tenant_id.to_object_id() == *tenant_id) + .filter(|d| d.tenant_id == *tenant_id) .count() as u64) } - async fn delete_domain(&self, tenant_id: &ObjectId, domain: &str) -> Result { + async fn delete_domain(&self, tenant_id: &TenantId, domain: &str) -> Result { let mut domains = self.domains.lock().unwrap(); let len_before = domains.len(); - domains.retain(|d| !(d.tenant_id.to_object_id() == *tenant_id && d.domain == domain)); + domains.retain(|d| !(d.tenant_id == *tenant_id && d.domain == domain)); Ok(domains.len() < len_before) } @@ -84,18 +85,14 @@ impl DomainsRepository for MockDomainsRepo { async fn find_alternate_by_tenant( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, ) -> Result, String> { Ok(self .domains .lock() .unwrap() .iter() - .find(|d| { - d.tenant_id.to_object_id() == *tenant_id - && d.role == DomainRole::Alternate - && d.verified - }) + .find(|d| d.tenant_id == *tenant_id && d.role == DomainRole::Alternate && d.verified) .cloned()) } } diff --git a/server/tests/common/mod.rs b/server/tests/common/mod.rs index f3ed5c86..3ef75336 100644 --- a/server/tests/common/mod.rs +++ b/server/tests/common/mod.rs @@ -249,7 +249,7 @@ pub async fn spawn_app() -> TestApp { pub async fn seed_verified_domain(app: &TestApp, tenant_id: &ObjectId, domain: &str) { app.domains_repo .create_domain( - *tenant_id, + rift::core::public_id::TenantId::from_object_id(*tenant_id), domain.to_string(), "tok".to_string(), rift::services::domains::models::DomainRole::Primary, From b49a73fb6e37a09b69dc9371c798535c480ec33c Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 12:39:26 -0500 Subject: [PATCH 02/10] publishable_keys/tenants repos: take typed IDs --- .../src/api/auth/publishable_keys/routes.rs | 7 ++---- server/src/api/billing/routes.rs | 4 ++-- server/src/api/billing/stripe_webhook.rs | 8 +++---- .../services/auth/publishable_keys/repo.rs | 20 +++++++++++------ server/src/services/auth/tenants/repo.rs | 22 ++++++++++--------- server/src/services/billing/quota_tests.rs | 8 +++---- server/src/services/billing/service.rs | 4 ++-- server/src/services/billing/service_tests.rs | 8 +++---- server/tests/common/mocks/sdk_keys.rs | 14 +++++++----- server/tests/common/mocks/tenants.rs | 20 ++++++----------- 10 files changed, 58 insertions(+), 57 deletions(-) diff --git a/server/src/api/auth/publishable_keys/routes.rs b/server/src/api/auth/publishable_keys/routes.rs index 82335ae6..dbeca4c1 100644 --- a/server/src/api/auth/publishable_keys/routes.rs +++ b/server/src/api/auth/publishable_keys/routes.rs @@ -138,7 +138,7 @@ pub async fn list_sdk_keys( .into_response(); }; - match sdk_keys_repo.list_by_tenant(&tenant.to_object_id()).await { + match sdk_keys_repo.list_by_tenant(&tenant).await { Ok(docs) => { let keys: Vec = docs .iter() @@ -189,10 +189,7 @@ pub async fn revoke_sdk_key( .into_response(); }; - match sdk_keys_repo - .revoke(&tenant.to_object_id(), &key_id.to_object_id()) - .await - { + match sdk_keys_repo.revoke(&tenant, &key_id).await { Ok(true) => StatusCode::NO_CONTENT.into_response(), Ok(false) => ( StatusCode::NOT_FOUND, diff --git a/server/src/api/billing/routes.rs b/server/src/api/billing/routes.rs index e44f89b4..7da66cfb 100644 --- a/server/src/api/billing/routes.rs +++ b/server/src/api/billing/routes.rs @@ -170,7 +170,7 @@ pub async fn create_stripe_portal( .into_response(); }; - let tenant_doc = match tenants.find_by_id(&tenant.to_object_id()).await { + let tenant_doc = match tenants.find_by_id(&tenant).await { Ok(Some(t)) => t, Ok(None) => { return ( @@ -286,7 +286,7 @@ pub async fn cancel_subscription( ) .into_response(); }; - let tenant_doc = match tenants.find_by_id(&tenant.to_object_id()).await { + let tenant_doc = match tenants.find_by_id(&tenant).await { Ok(Some(t)) => t, Ok(None) => { return ( diff --git a/server/src/api/billing/stripe_webhook.rs b/server/src/api/billing/stripe_webhook.rs index b8e8ac1b..ba322e9e 100644 --- a/server/src/api/billing/stripe_webhook.rs +++ b/server/src/api/billing/stripe_webhook.rs @@ -265,7 +265,7 @@ async fn handle_subscription_upsert( }; tenants - .apply_subscription_update(&tenant_id.to_object_id(), update) + .apply_subscription_update(&tenant_id, update) .await?; Ok(()) @@ -283,9 +283,7 @@ async fn handle_subscription_deleted( tracing::warn!(customer = %sub.customer, "stripe_webhook_deleted_no_tenant"); return Ok(()); }; - tenants - .clear_subscription(&tenant_id.to_object_id()) - .await?; + tenants.clear_subscription(&tenant_id).await?; Ok(()) } @@ -324,7 +322,7 @@ async fn handle_invoice_status( ..SubscriptionUpdate::default() }; tenants - .apply_subscription_update(&tenant_id.to_object_id(), update) + .apply_subscription_update(&tenant_id, update) .await?; Ok(()) } diff --git a/server/src/services/auth/publishable_keys/repo.rs b/server/src/services/auth/publishable_keys/repo.rs index 77a8ceff..1da597d5 100644 --- a/server/src/services/auth/publishable_keys/repo.rs +++ b/server/src/services/auth/publishable_keys/repo.rs @@ -1,8 +1,9 @@ use async_trait::async_trait; -use mongodb::bson::{doc, oid::ObjectId}; +use mongodb::bson::doc; use mongodb::options::IndexOptions; use mongodb::{Collection, Database}; +use crate::core::public_id::{PublishableKeyId, TenantId}; use crate::ensure_index; use super::models::SdkKeyDoc; @@ -15,9 +16,10 @@ pub trait SdkKeysRepository: Send + Sync { async fn find_by_hash(&self, key_hash: &str) -> Result, String>; - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String>; + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String>; - async fn revoke(&self, tenant_id: &ObjectId, key_id: &ObjectId) -> Result; + async fn revoke(&self, tenant_id: &TenantId, key_id: &PublishableKeyId) + -> Result; } // ── Repository ── @@ -58,10 +60,10 @@ impl SdkKeysRepository for SdkKeysRepo { .map_err(|e| e.to_string()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .keys - .find(doc! { "tenant_id": tenant_id, "revoked": false }) + .find(doc! { "tenant_id": *tenant_id, "revoked": false }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -73,11 +75,15 @@ impl SdkKeysRepository for SdkKeysRepo { Ok(docs) } - async fn revoke(&self, tenant_id: &ObjectId, key_id: &ObjectId) -> Result { + async fn revoke( + &self, + tenant_id: &TenantId, + key_id: &PublishableKeyId, + ) -> Result { let result = self .keys .update_one( - doc! { "_id": key_id, "tenant_id": tenant_id }, + doc! { "_id": *key_id, "tenant_id": *tenant_id }, doc! { "$set": { "revoked": true } }, ) .await diff --git a/server/src/services/auth/tenants/repo.rs b/server/src/services/auth/tenants/repo.rs index e7bfea6d..c6798223 100644 --- a/server/src/services/auth/tenants/repo.rs +++ b/server/src/services/auth/tenants/repo.rs @@ -1,7 +1,9 @@ use async_trait::async_trait; -use mongodb::bson::{self, doc, oid::ObjectId}; +use mongodb::bson::{self, doc}; use mongodb::{Collection, Database}; +use crate::core::public_id::TenantId; + // Re-export models so existing call sites that import via // `tenants::repo::TenantDoc` keep compiling. Data types live in models.rs // per the strict pub-types-in-models rule. @@ -14,7 +16,7 @@ pub use super::models::{ #[async_trait] pub trait TenantsRepository: Send + Sync { async fn create(&self, doc: &TenantDoc) -> Result<(), String>; - async fn find_by_id(&self, id: &ObjectId) -> Result, String>; + async fn find_by_id(&self, id: &TenantId) -> Result, String>; /// Resolve a tenant by the Stripe customer id stored on prior subscription /// events. Used by webhook handlers that receive `customer.subscription.*` @@ -28,13 +30,13 @@ pub trait TenantsRepository: Send + Sync { /// `None` fields are left untouched. async fn apply_subscription_update( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, update: SubscriptionUpdate, ) -> Result; /// End-of-subscription path for `customer.subscription.deleted`. Drops /// the tenant back to Free and clears Stripe identifiers. - async fn clear_subscription(&self, tenant_id: &ObjectId) -> Result; + async fn clear_subscription(&self, tenant_id: &TenantId) -> Result; } // ── Repository ── @@ -62,9 +64,9 @@ impl TenantsRepository for TenantsRepo { Ok(()) } - async fn find_by_id(&self, id: &ObjectId) -> Result, String> { + async fn find_by_id(&self, id: &TenantId) -> Result, String> { self.tenants - .find_one(doc! { "_id": id }) + .find_one(doc! { "_id": *id }) .await .map_err(|e| e.to_string()) } @@ -81,7 +83,7 @@ impl TenantsRepository for TenantsRepo { async fn apply_subscription_update( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, update: SubscriptionUpdate, ) -> Result { let mut set_doc = mongodb::bson::Document::new(); @@ -133,13 +135,13 @@ impl TenantsRepository for TenantsRepo { } let result = self .tenants - .update_one(doc! { "_id": tenant_id }, doc! { "$set": set_doc }) + .update_one(doc! { "_id": *tenant_id }, doc! { "$set": set_doc }) .await .map_err(|e| e.to_string())?; Ok(result.matched_count == 1) } - async fn clear_subscription(&self, tenant_id: &ObjectId) -> Result { + async fn clear_subscription(&self, tenant_id: &TenantId) -> Result { let update = doc! { "$set": { "plan_tier": bson::to_bson(&PlanTier::Free).map_err(|e| e.to_string())?, @@ -154,7 +156,7 @@ impl TenantsRepository for TenantsRepo { }; let result = self .tenants - .update_one(doc! { "_id": tenant_id }, update) + .update_one(doc! { "_id": *tenant_id }, update) .await .map_err(|e| e.to_string())?; Ok(result.matched_count == 1) diff --git a/server/src/services/billing/quota_tests.rs b/server/src/services/billing/quota_tests.rs index 8a22fce4..f1f3d76c 100644 --- a/server/src/services/billing/quota_tests.rs +++ b/server/src/services/billing/quota_tests.rs @@ -17,13 +17,13 @@ impl TenantsRepository for MockTenants { self.tenants.lock().unwrap().push(doc.clone()); Ok(()) } - async fn find_by_id(&self, id: &ObjectId) -> Result, String> { + async fn find_by_id(&self, id: &TenantId) -> Result, String> { Ok(self .tenants .lock() .unwrap() .iter() - .find(|t| t.id.map(|i| i.to_object_id()).as_ref() == Some(id)) + .find(|t| t.id.as_ref() == Some(id)) .cloned()) } async fn find_by_stripe_customer_id( @@ -34,12 +34,12 @@ impl TenantsRepository for MockTenants { } async fn apply_subscription_update( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _update: crate::services::auth::tenants::repo::SubscriptionUpdate, ) -> Result { Ok(true) } - async fn clear_subscription(&self, _tenant_id: &ObjectId) -> Result { + async fn clear_subscription(&self, _tenant_id: &TenantId) -> Result { Ok(true) } } diff --git a/server/src/services/billing/service.rs b/server/src/services/billing/service.rs index 72968f03..2e111ddf 100644 --- a/server/src/services/billing/service.rs +++ b/server/src/services/billing/service.rs @@ -44,7 +44,7 @@ impl BillingService { pub async fn status(&self, ctx: &AuthContext) -> Result { let tenant = self .tenants_repo - .find_by_id(ctx.tenant_id.as_object_id()) + .find_by_id(&ctx.tenant_id) .await .map_err(BillingError::Internal)? .ok_or(BillingError::TenantNotFound)?; @@ -70,7 +70,7 @@ impl TierResolver for BillingService { async fn effective_tier(&self, tenant_id: &TenantId) -> Result { let tenant = self .tenants_repo - .find_by_id(tenant_id.as_object_id()) + .find_by_id(tenant_id) .await .map_err(BillingError::Internal)? .ok_or(BillingError::TenantNotFound)?; diff --git a/server/src/services/billing/service_tests.rs b/server/src/services/billing/service_tests.rs index a2f869c1..5e14f5ef 100644 --- a/server/src/services/billing/service_tests.rs +++ b/server/src/services/billing/service_tests.rs @@ -25,13 +25,13 @@ impl TenantsRepository for MockRepo { Ok(()) } - async fn find_by_id(&self, id: &ObjectId) -> Result, String> { + async fn find_by_id(&self, id: &TenantId) -> Result, String> { Ok(self .tenants .lock() .unwrap() .iter() - .find(|t| t.id.map(|i| i.to_object_id()).as_ref() == Some(id)) + .find(|t| t.id.as_ref() == Some(id)) .cloned()) } @@ -44,13 +44,13 @@ impl TenantsRepository for MockRepo { async fn apply_subscription_update( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _update: crate::services::auth::tenants::repo::SubscriptionUpdate, ) -> Result { Ok(true) } - async fn clear_subscription(&self, _tenant_id: &ObjectId) -> Result { + async fn clear_subscription(&self, _tenant_id: &TenantId) -> Result { Ok(true) } } diff --git a/server/tests/common/mocks/sdk_keys.rs b/server/tests/common/mocks/sdk_keys.rs index 8b081f89..23410126 100644 --- a/server/tests/common/mocks/sdk_keys.rs +++ b/server/tests/common/mocks/sdk_keys.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; -use mongodb::bson::oid::ObjectId; use std::sync::Mutex; +use rift::core::public_id::{PublishableKeyId, TenantId}; use rift::services::auth::publishable_keys::models::SdkKeyDoc; use rift::services::auth::publishable_keys::repo::SdkKeysRepository; @@ -27,22 +27,26 @@ impl SdkKeysRepository for MockSdkKeysRepo { .cloned()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { Ok(self .keys .lock() .unwrap() .iter() - .filter(|k| k.tenant_id.to_object_id() == *tenant_id && !k.revoked) + .filter(|k| k.tenant_id == *tenant_id && !k.revoked) .cloned() .collect()) } - async fn revoke(&self, tenant_id: &ObjectId, key_id: &ObjectId) -> Result { + async fn revoke( + &self, + tenant_id: &TenantId, + key_id: &PublishableKeyId, + ) -> Result { let mut keys = self.keys.lock().unwrap(); if let Some(key) = keys .iter_mut() - .find(|k| k.id.to_object_id() == *key_id && k.tenant_id.to_object_id() == *tenant_id) + .find(|k| k.id == *key_id && k.tenant_id == *tenant_id) { key.revoked = true; Ok(true) diff --git a/server/tests/common/mocks/tenants.rs b/server/tests/common/mocks/tenants.rs index cb5f99f2..ae8f185c 100644 --- a/server/tests/common/mocks/tenants.rs +++ b/server/tests/common/mocks/tenants.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; -use mongodb::bson::oid::ObjectId; use std::sync::Mutex; +use rift::core::public_id::TenantId; use rift::services::auth::tenants::repo::{ BillingMethod, PlanTier, SubscriptionStatus, SubscriptionUpdate, TenantDoc, TenantsRepository, }; @@ -18,13 +18,13 @@ impl TenantsRepository for MockTenantsRepo { Ok(()) } - async fn find_by_id(&self, id: &ObjectId) -> Result, String> { + async fn find_by_id(&self, id: &TenantId) -> Result, String> { Ok(self .tenants .lock() .unwrap() .iter() - .find(|t| t.id.as_ref().map(|i| i.to_object_id()).as_ref() == Some(id)) + .find(|t| t.id.as_ref() == Some(id)) .cloned()) } @@ -43,14 +43,11 @@ impl TenantsRepository for MockTenantsRepo { async fn apply_subscription_update( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, update: SubscriptionUpdate, ) -> Result { let mut guard = self.tenants.lock().unwrap(); - if let Some(t) = guard - .iter_mut() - .find(|t| t.id.as_ref().map(|i| i.to_object_id()).as_ref() == Some(tenant_id)) - { + if let Some(t) = guard.iter_mut().find(|t| t.id.as_ref() == Some(tenant_id)) { if let Some(x) = update.plan_tier { t.plan_tier = x; } @@ -78,12 +75,9 @@ impl TenantsRepository for MockTenantsRepo { } } - async fn clear_subscription(&self, tenant_id: &ObjectId) -> Result { + async fn clear_subscription(&self, tenant_id: &TenantId) -> Result { let mut guard = self.tenants.lock().unwrap(); - if let Some(t) = guard - .iter_mut() - .find(|t| t.id.as_ref().map(|i| i.to_object_id()).as_ref() == Some(tenant_id)) - { + if let Some(t) = guard.iter_mut().find(|t| t.id.as_ref() == Some(tenant_id)) { t.plan_tier = PlanTier::Free; t.billing_method = BillingMethod::Free; t.status = SubscriptionStatus::Canceled; From 958e08820349bca6022a36db6a46e1f9360a9b58 Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 12:42:22 -0500 Subject: [PATCH 03/10] users/repo.rs: take typed TenantId/UserId --- .../src/services/auth/secret_keys/service.rs | 2 +- server/src/services/auth/users/repo.rs | 27 ++++++++++--------- server/src/services/auth/users/service.rs | 8 +++--- .../billing/repos/resource_counts_adapter.rs | 2 +- server/tests/common/mocks/users.rs | 21 +++++++-------- 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/server/src/services/auth/secret_keys/service.rs b/server/src/services/auth/secret_keys/service.rs index 5879c924..c09043b4 100644 --- a/server/src/services/auth/secret_keys/service.rs +++ b/server/src/services/auth/secret_keys/service.rs @@ -100,7 +100,7 @@ impl SecretKeysService { // Permission check: target email must be a verified member of this tenant. let user = self .users_repo - .find_by_tenant_and_email(ctx.tenant_id.as_object_id(), email) + .find_by_tenant_and_email(&ctx.tenant_id, email) .await .map_err(SecretKeyError::Internal)? .ok_or(SecretKeyError::UserNotMember)?; diff --git a/server/src/services/auth/users/repo.rs b/server/src/services/auth/users/repo.rs index 8183a36e..adcc522f 100644 --- a/server/src/services/auth/users/repo.rs +++ b/server/src/services/auth/users/repo.rs @@ -1,9 +1,10 @@ use async_trait::async_trait; -use mongodb::bson::{doc, oid::ObjectId}; +use mongodb::bson::doc; use mongodb::options::IndexOptions; use mongodb::{Collection, Database}; use super::models::UserDoc; +use crate::core::public_id::{TenantId, UserId}; use crate::ensure_index; // ── Trait ── @@ -14,12 +15,12 @@ pub trait UsersRepository: Send + Sync { async fn find_by_email(&self, email: &str) -> Result, String>; async fn find_by_tenant_and_email( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, email: &str, ) -> Result, String>; - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String>; - async fn count_verified_by_tenant(&self, tenant_id: &ObjectId) -> Result; - async fn delete(&self, tenant_id: &ObjectId, user_id: &ObjectId) -> Result; + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String>; + async fn count_verified_by_tenant(&self, tenant_id: &TenantId) -> Result; + async fn delete(&self, tenant_id: &TenantId, user_id: &UserId) -> Result; /// Flip `verified: true` on the user with this email. Returns the updated /// doc on success, `None` if no such user exists. Idempotent — verifying /// an already-verified user is a no-op that returns the doc. @@ -69,19 +70,19 @@ impl UsersRepository for UsersRepo { async fn find_by_tenant_and_email( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, email: &str, ) -> Result, String> { self.users - .find_one(doc! { "tenant_id": tenant_id, "email": email }) + .find_one(doc! { "tenant_id": *tenant_id, "email": email }) .await .map_err(|e| e.to_string()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .users - .find(doc! { "tenant_id": tenant_id }) + .find(doc! { "tenant_id": *tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -93,18 +94,18 @@ impl UsersRepository for UsersRepo { Ok(docs) } - async fn count_verified_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_verified_by_tenant(&self, tenant_id: &TenantId) -> Result { self.users - .count_documents(doc! { "tenant_id": tenant_id, "verified": true }) + .count_documents(doc! { "tenant_id": *tenant_id, "verified": true }) .await .map(|c| c as i64) .map_err(|e| e.to_string()) } - async fn delete(&self, tenant_id: &ObjectId, user_id: &ObjectId) -> Result { + async fn delete(&self, tenant_id: &TenantId, user_id: &UserId) -> Result { let result = self .users - .delete_one(doc! { "_id": user_id, "tenant_id": tenant_id }) + .delete_one(doc! { "_id": *user_id, "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) diff --git a/server/src/services/auth/users/service.rs b/server/src/services/auth/users/service.rs index 4b7db672..e1de0d69 100644 --- a/server/src/services/auth/users/service.rs +++ b/server/src/services/auth/users/service.rs @@ -136,7 +136,7 @@ impl UsersService { if self .users_repo - .find_by_tenant_and_email(ctx.tenant_id.as_object_id(), &email) + .find_by_tenant_and_email(&ctx.tenant_id, &email) .await .map_err(UserError::Internal)? .is_some() @@ -206,7 +206,7 @@ impl UsersService { pub async fn list(&self, ctx: &AuthContext) -> Result, UserError> { let docs = self .users_repo - .list_by_tenant(ctx.tenant_id.as_object_id()) + .list_by_tenant(&ctx.tenant_id) .await .map_err(UserError::Internal)?; @@ -231,7 +231,7 @@ impl UsersService { ) -> Result<(), UserError> { let count = self .users_repo - .count_verified_by_tenant(ctx.tenant_id.as_object_id()) + .count_verified_by_tenant(&ctx.tenant_id) .await .map_err(UserError::Internal)?; @@ -241,7 +241,7 @@ impl UsersService { let deleted = self .users_repo - .delete(ctx.tenant_id.as_object_id(), &user_id.to_object_id()) + .delete(&ctx.tenant_id, &user_id) .await .map_err(UserError::Internal)?; diff --git a/server/src/services/billing/repos/resource_counts_adapter.rs b/server/src/services/billing/repos/resource_counts_adapter.rs index 7b23df18..e9a2cd16 100644 --- a/server/src/services/billing/repos/resource_counts_adapter.rs +++ b/server/src/services/billing/repos/resource_counts_adapter.rs @@ -31,7 +31,7 @@ impl ResourceCounts for RepoResourceCounts { Resource::CreateDomain => self.domains.count_by_tenant(tenant_id).await, Resource::InviteTeamMember => self .users - .count_verified_by_tenant(oid) + .count_verified_by_tenant(tenant_id) .await .map(|n| n as u64), Resource::CreateWebhook => self.webhooks.count_by_tenant(oid).await, diff --git a/server/tests/common/mocks/users.rs b/server/tests/common/mocks/users.rs index 6d26db1f..a6e5f60b 100644 --- a/server/tests/common/mocks/users.rs +++ b/server/tests/common/mocks/users.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; -use mongodb::bson::oid::ObjectId; use std::sync::Mutex; +use rift::core::public_id::{TenantId, UserId}; use rift::services::auth::users::models::UserDoc; use rift::services::auth::users::repo::UsersRepository; @@ -29,7 +29,7 @@ impl UsersRepository for MockUsersRepo { async fn find_by_tenant_and_email( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, email: &str, ) -> Result, String> { Ok(self @@ -37,38 +37,35 @@ impl UsersRepository for MockUsersRepo { .lock() .unwrap() .iter() - .find(|u| u.tenant_id.to_object_id() == *tenant_id && u.email == email) + .find(|u| u.tenant_id == *tenant_id && u.email == email) .cloned()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { Ok(self .users .lock() .unwrap() .iter() - .filter(|u| u.tenant_id.to_object_id() == *tenant_id) + .filter(|u| u.tenant_id == *tenant_id) .cloned() .collect()) } - async fn count_verified_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_verified_by_tenant(&self, tenant_id: &TenantId) -> Result { Ok(self .users .lock() .unwrap() .iter() - .filter(|u| u.tenant_id.to_object_id() == *tenant_id && u.verified) + .filter(|u| u.tenant_id == *tenant_id && u.verified) .count() as i64) } - async fn delete(&self, tenant_id: &ObjectId, user_id: &ObjectId) -> Result { + async fn delete(&self, tenant_id: &TenantId, user_id: &UserId) -> Result { let mut users = self.users.lock().unwrap(); let len = users.len(); - users.retain(|u| { - !(u.id.as_ref().map(|i| i.to_object_id()).as_ref() == Some(user_id) - && u.tenant_id.to_object_id() == *tenant_id) - }); + users.retain(|u| !(u.id.as_ref() == Some(user_id) && u.tenant_id == *tenant_id)); Ok(users.len() < len) } From 0bb8a587e7cafd1ab0986906e97145f1acdf2c62 Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 12:46:01 -0500 Subject: [PATCH 04/10] secret_keys/repo.rs: take typed TenantId/SecretKeyId/AffiliateId --- server/src/services/affiliates/service.rs | 14 ++--- server/src/services/auth/secret_keys/repo.rs | 51 ++++++++++--------- .../src/services/auth/secret_keys/service.rs | 10 ++-- server/tests/common/mocks/secret_keys.rs | 36 +++++++------ 4 files changed, 51 insertions(+), 60 deletions(-) diff --git a/server/src/services/affiliates/service.rs b/server/src/services/affiliates/service.rs index 9be5a2fd..de06a0f8 100644 --- a/server/src/services/affiliates/service.rs +++ b/server/src/services/affiliates/service.rs @@ -168,10 +168,9 @@ impl AffiliatesService { // Per-affiliate cap. Counted at the repo level via the scope filter // so a compromised tenant key can't spam unbounded credentials. - let aff_oid = affiliate_id.to_object_id(); let existing = self .secret_keys_repo - .list_by_tenant_and_affiliate(ctx.tenant_id.as_object_id(), &aff_oid) + .list_by_tenant_and_affiliate(&ctx.tenant_id, &affiliate_id) .await .map_err(AffiliateError::Internal)?; if existing.len() >= MAX_CREDENTIALS_PER_AFFILIATE { @@ -210,10 +209,7 @@ impl AffiliatesService { .ok_or(AffiliateError::NotFound)?; self.secret_keys_repo - .list_by_tenant_and_affiliate( - ctx.tenant_id.as_object_id(), - &affiliate_id.to_object_id(), - ) + .list_by_tenant_and_affiliate(&ctx.tenant_id, &affiliate_id) .await .map_err(AffiliateError::Internal) } @@ -236,11 +232,7 @@ impl AffiliatesService { let deleted = self .secret_keys_repo - .delete_affiliate_credential( - ctx.tenant_id.as_object_id(), - &affiliate_id.to_object_id(), - &key_id.to_object_id(), - ) + .delete_affiliate_credential(&ctx.tenant_id, &affiliate_id, &key_id) .await .map_err(AffiliateError::Internal)?; diff --git a/server/src/services/auth/secret_keys/repo.rs b/server/src/services/auth/secret_keys/repo.rs index 9afcb9f8..219f8866 100644 --- a/server/src/services/auth/secret_keys/repo.rs +++ b/server/src/services/auth/secret_keys/repo.rs @@ -1,8 +1,9 @@ use async_trait::async_trait; -use mongodb::bson::{doc, oid::ObjectId}; +use mongodb::bson::doc; use mongodb::options::IndexOptions; use mongodb::{Collection, Database}; +use crate::core::public_id::{AffiliateId, SecretKeyId, TenantId}; use crate::ensure_index; // ── Document ── @@ -21,17 +22,17 @@ pub use super::models::{KeyScope, SecretKeyDoc}; pub trait SecretKeysRepository: Send + Sync { async fn create_key(&self, doc: &SecretKeyDoc) -> Result<(), String>; async fn find_by_hash(&self, key_hash: &str) -> Result, String>; - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String>; - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result; - async fn delete_key(&self, tenant_id: &ObjectId, key_id: &ObjectId) -> Result; + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String>; + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result; + async fn delete_key(&self, tenant_id: &TenantId, key_id: &SecretKeyId) -> Result; /// List all keys scoped to a specific affiliate within a tenant. /// Used by the affiliate-credentials API for both listing and the /// per-affiliate credential cap. async fn list_by_tenant_and_affiliate( &self, - tenant_id: &ObjectId, - affiliate_id: &ObjectId, + tenant_id: &TenantId, + affiliate_id: &AffiliateId, ) -> Result, String>; /// Delete a key only if it's scoped to (tenant, affiliate). Returns @@ -40,9 +41,9 @@ pub trait SecretKeysRepository: Send + Sync { /// avoids a TOCTOU between membership check and delete. async fn delete_affiliate_credential( &self, - tenant_id: &ObjectId, - affiliate_id: &ObjectId, - key_id: &ObjectId, + tenant_id: &TenantId, + affiliate_id: &AffiliateId, + key_id: &SecretKeyId, ) -> Result; } @@ -84,10 +85,10 @@ impl SecretKeysRepository for SecretKeysRepo { .map_err(|e| e.to_string()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .keys - .find(doc! { "tenant_id": tenant_id }) + .find(doc! { "tenant_id": *tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -99,18 +100,18 @@ impl SecretKeysRepository for SecretKeysRepo { Ok(docs) } - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { self.keys - .count_documents(doc! { "tenant_id": tenant_id }) + .count_documents(doc! { "tenant_id": *tenant_id }) .await .map(|c| c as i64) .map_err(|e| e.to_string()) } - async fn delete_key(&self, tenant_id: &ObjectId, key_id: &ObjectId) -> Result { + async fn delete_key(&self, tenant_id: &TenantId, key_id: &SecretKeyId) -> Result { let result = self .keys - .delete_one(doc! { "_id": key_id, "tenant_id": tenant_id }) + .delete_one(doc! { "_id": *key_id, "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) @@ -118,17 +119,17 @@ impl SecretKeysRepository for SecretKeysRepo { async fn list_by_tenant_and_affiliate( &self, - tenant_id: &ObjectId, - affiliate_id: &ObjectId, + tenant_id: &TenantId, + affiliate_id: &AffiliateId, ) -> Result, String> { // KeyScope serializes as { type: "affiliate", affiliate_id: } // — match on the nested fields rather than the enum directly. let mut cursor = self .keys .find(doc! { - "tenant_id": tenant_id, + "tenant_id": *tenant_id, "scope.type": "affiliate", - "scope.affiliate_id": affiliate_id, + "scope.affiliate_id": *affiliate_id, }) .sort(doc! { "created_at": -1 }) .await @@ -143,17 +144,17 @@ impl SecretKeysRepository for SecretKeysRepo { async fn delete_affiliate_credential( &self, - tenant_id: &ObjectId, - affiliate_id: &ObjectId, - key_id: &ObjectId, + tenant_id: &TenantId, + affiliate_id: &AffiliateId, + key_id: &SecretKeyId, ) -> Result { let result = self .keys .delete_one(doc! { - "_id": key_id, - "tenant_id": tenant_id, + "_id": *key_id, + "tenant_id": *tenant_id, "scope.type": "affiliate", - "scope.affiliate_id": affiliate_id, + "scope.affiliate_id": *affiliate_id, }) .await .map_err(|e| e.to_string())?; diff --git a/server/src/services/auth/secret_keys/service.rs b/server/src/services/auth/secret_keys/service.rs index c09043b4..40a2177d 100644 --- a/server/src/services/auth/secret_keys/service.rs +++ b/server/src/services/auth/secret_keys/service.rs @@ -114,7 +114,7 @@ impl SecretKeysService { // Key limit. let count = self .sk_repo - .count_by_tenant(ctx.tenant_id.as_object_id()) + .count_by_tenant(&ctx.tenant_id) .await .map_err(SecretKeyError::Internal)?; if count >= 5 { @@ -230,7 +230,7 @@ impl SecretKeysService { pub async fn list(&self, ctx: &AuthContext) -> Result, SecretKeyError> { let docs = self .sk_repo - .list_by_tenant(ctx.tenant_id.as_object_id()) + .list_by_tenant(&ctx.tenant_id) .await .map_err(SecretKeyError::Internal)?; @@ -269,7 +269,7 @@ impl SecretKeysService { let count = self .sk_repo - .count_by_tenant(ctx.tenant_id.as_object_id()) + .count_by_tenant(&ctx.tenant_id) .await .map_err(SecretKeyError::Internal)?; @@ -279,7 +279,7 @@ impl SecretKeysService { let deleted = self .sk_repo - .delete_key(ctx.tenant_id.as_object_id(), key_id.as_object_id()) + .delete_key(&ctx.tenant_id, &key_id) .await .map_err(SecretKeyError::Internal)?; @@ -307,7 +307,7 @@ impl SecretKeysService { let count = self .sk_repo - .count_by_tenant(ctx.tenant_id.as_object_id()) + .count_by_tenant(&ctx.tenant_id) .await .map_err(SecretKeyError::Internal)?; if count >= 5 { diff --git a/server/tests/common/mocks/secret_keys.rs b/server/tests/common/mocks/secret_keys.rs index ca105425..e27aeff4 100644 --- a/server/tests/common/mocks/secret_keys.rs +++ b/server/tests/common/mocks/secret_keys.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; -use mongodb::bson::oid::ObjectId; use std::sync::Mutex; +use rift::core::public_id::{AffiliateId, SecretKeyId, TenantId}; use rift::services::auth::secret_keys::repo::{KeyScope, SecretKeyDoc, SecretKeysRepository}; #[derive(Default)] @@ -26,40 +26,38 @@ impl SecretKeysRepository for MockSecretKeysRepo { .cloned()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { Ok(self .keys .lock() .unwrap() .iter() - .filter(|k| k.tenant_id.to_object_id() == *tenant_id) + .filter(|k| k.tenant_id == *tenant_id) .cloned() .collect()) } - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { Ok(self .keys .lock() .unwrap() .iter() - .filter(|k| k.tenant_id.to_object_id() == *tenant_id) + .filter(|k| k.tenant_id == *tenant_id) .count() as i64) } - async fn delete_key(&self, tenant_id: &ObjectId, key_id: &ObjectId) -> Result { + async fn delete_key(&self, tenant_id: &TenantId, key_id: &SecretKeyId) -> Result { let mut keys = self.keys.lock().unwrap(); let len = keys.len(); - keys.retain(|k| { - !(k.id.to_object_id() == *key_id && k.tenant_id.to_object_id() == *tenant_id) - }); + keys.retain(|k| !(k.id == *key_id && k.tenant_id == *tenant_id)); Ok(keys.len() < len) } async fn list_by_tenant_and_affiliate( &self, - tenant_id: &ObjectId, - affiliate_id: &ObjectId, + tenant_id: &TenantId, + affiliate_id: &AffiliateId, ) -> Result, String> { Ok(self .keys @@ -67,10 +65,10 @@ impl SecretKeysRepository for MockSecretKeysRepo { .unwrap() .iter() .filter(|k| { - k.tenant_id.to_object_id() == *tenant_id + k.tenant_id == *tenant_id && matches!( &k.scope, - Some(KeyScope::Affiliate { affiliate_id: a }) if a.to_object_id() == *affiliate_id + Some(KeyScope::Affiliate { affiliate_id: a }) if a == affiliate_id ) }) .cloned() @@ -79,18 +77,18 @@ impl SecretKeysRepository for MockSecretKeysRepo { async fn delete_affiliate_credential( &self, - tenant_id: &ObjectId, - affiliate_id: &ObjectId, - key_id: &ObjectId, + tenant_id: &TenantId, + affiliate_id: &AffiliateId, + key_id: &SecretKeyId, ) -> Result { let mut keys = self.keys.lock().unwrap(); let len = keys.len(); keys.retain(|k| { - !(k.id.to_object_id() == *key_id - && k.tenant_id.to_object_id() == *tenant_id + !(k.id == *key_id + && k.tenant_id == *tenant_id && matches!( &k.scope, - Some(KeyScope::Affiliate { affiliate_id: a }) if a.to_object_id() == *affiliate_id + Some(KeyScope::Affiliate { affiliate_id: a }) if a == affiliate_id )) }); Ok(keys.len() < len) From 641381fdb8ecd59e9ab3e27136fc7e27863550a0 Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 12:50:43 -0500 Subject: [PATCH 05/10] webhooks + apps repos: take typed IDs --- server/src/api/apps/routes.rs | 11 ++--- server/src/api/links/routes.rs | 4 +- server/src/api/webhooks/routes.rs | 24 +++-------- server/src/services/apps/repo.rs | 29 +++++++------ .../billing/repos/resource_counts_adapter.rs | 2 +- server/src/services/webhooks/dispatcher.rs | 3 +- server/src/services/webhooks/repo.rs | 43 ++++++++++--------- server/tests/common/mocks/apps.rs | 17 +++----- server/tests/common/mocks/webhooks.rs | 37 +++++++--------- 9 files changed, 73 insertions(+), 97 deletions(-) diff --git a/server/src/api/apps/routes.rs b/server/src/api/apps/routes.rs index 4feab05b..fb3fad44 100644 --- a/server/src/api/apps/routes.rs +++ b/server/src/api/apps/routes.rs @@ -146,7 +146,7 @@ pub async fn list_apps( .into_response(); }; - match repo.list_by_tenant(&tenant.to_object_id()).await { + match repo.list_by_tenant(&tenant).await { Ok(apps) => { let details: Vec = apps.iter().map(to_detail).collect(); Json(json!({ "apps": details })).into_response() @@ -189,10 +189,7 @@ pub async fn delete_app( .into_response(); }; - match repo - .delete_app(&tenant.to_object_id(), &app_id.to_object_id()) - .await - { + match repo.delete_app(&tenant, &app_id).await { Ok(true) => StatusCode::NO_CONTENT.into_response(), Ok(false) => ( StatusCode::NOT_FOUND, @@ -240,7 +237,7 @@ pub async fn serve_aasa(State(state): State>, headers: HeaderMap) }; let Some(ios_app) = repo - .find_by_tenant_platform(tenant_id.as_object_id(), "ios") + .find_by_tenant_platform(&tenant_id, "ios") .await .ok() .flatten() @@ -309,7 +306,7 @@ pub async fn serve_assetlinks(State(state): State>, headers: Heade }; let Some(android_app) = repo - .find_by_tenant_platform(tenant_id.as_object_id(), "android") + .find_by_tenant_platform(&tenant_id, "android") .await .ok() .flatten() diff --git a/server/src/api/links/routes.rs b/server/src/api/links/routes.rs index 2c9dcef5..2a75848f 100644 --- a/server/src/api/links/routes.rs +++ b/server/src/api/links/routes.rs @@ -665,12 +665,12 @@ async fn do_resolve( Platform::Other => "android", }; let app = apps_repo - .find_by_tenant_platform(link.tenant_id.as_object_id(), preferred) + .find_by_tenant_platform(&link.tenant_id, preferred) .await .ok() .flatten() .or(apps_repo - .find_by_tenant_platform(link.tenant_id.as_object_id(), fallback) + .find_by_tenant_platform(&link.tenant_id, fallback) .await .ok() .flatten()); diff --git a/server/src/api/webhooks/routes.rs b/server/src/api/webhooks/routes.rs index a045a703..54d0e3cd 100644 --- a/server/src/api/webhooks/routes.rs +++ b/server/src/api/webhooks/routes.rs @@ -112,7 +112,7 @@ pub async fn list_webhooks( .into_response(); }; - match repo.list_by_tenant(&tenant.to_object_id()).await { + match repo.list_by_tenant(&tenant).await { Ok(webhooks) => { let details: Vec = webhooks .into_iter() @@ -162,10 +162,7 @@ pub async fn delete_webhook( .into_response(); }; - match repo - .delete_webhook(&tenant.to_object_id(), &webhook_id.to_object_id()) - .await - { + match repo.delete_webhook(&tenant, &webhook_id).await { Ok(true) => StatusCode::NO_CONTENT.into_response(), Ok(false) => ( StatusCode::NOT_FOUND, @@ -210,8 +207,6 @@ pub async fn patch_webhook( .into_response(); }; - let oid = webhook_id.to_object_id(); - if req.active.is_none() && req.events.is_none() && req.url.is_none() { return ( StatusCode::BAD_REQUEST, @@ -245,22 +240,13 @@ pub async fn patch_webhook( } match repo - .update_webhook( - &tenant.to_object_id(), - &oid, - req.active, - req.events, - req.url, - ) + .update_webhook(&tenant, &webhook_id, req.active, req.events, req.url) .await { Ok(true) => { // Fetch updated webhook to return. - let webhooks = repo - .list_by_tenant(&tenant.to_object_id()) - .await - .unwrap_or_default(); - match webhooks.iter().find(|w| w.id.to_object_id() == oid) { + let webhooks = repo.list_by_tenant(&tenant).await.unwrap_or_default(); + match webhooks.iter().find(|w| w.id == webhook_id) { Some(w) => Json(json!({ "id": w.id, "url": w.url, diff --git a/server/src/services/apps/repo.rs b/server/src/services/apps/repo.rs index eadf12c4..224d4df5 100644 --- a/server/src/services/apps/repo.rs +++ b/server/src/services/apps/repo.rs @@ -1,8 +1,9 @@ use async_trait::async_trait; -use mongodb::bson::{doc, oid::ObjectId, DateTime}; +use mongodb::bson::{doc, DateTime}; use mongodb::options::IndexOptions; use mongodb::{Collection, Database}; +use crate::core::public_id::{AppId, TenantId}; use crate::ensure_index; use super::models::App; @@ -12,13 +13,13 @@ use super::models::App; #[async_trait] pub trait AppsRepository: Send + Sync { async fn create_or_update(&self, app: App) -> Result; - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String>; + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String>; async fn find_by_tenant_platform( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, platform: &str, ) -> Result, String>; - async fn delete_app(&self, tenant_id: &ObjectId, app_id: &ObjectId) -> Result; + async fn delete_app(&self, tenant_id: &TenantId, app_id: &AppId) -> Result; } // ── Repository ── @@ -49,7 +50,7 @@ impl AppsRepository for AppsRepo { async fn create_or_update(&self, app: App) -> Result { self.apps .update_one( - doc! { "tenant_id": &app.tenant_id, "platform": &app.platform }, + doc! { "tenant_id": app.tenant_id, "platform": &app.platform }, doc! { "$set": { "bundle_id": &app.bundle_id, @@ -61,8 +62,8 @@ impl AppsRepository for AppsRepo { "theme_color": &app.theme_color, }, "$setOnInsert": { - "_id": &app.id, - "tenant_id": &app.tenant_id, + "_id": app.id, + "tenant_id": app.tenant_id, "platform": &app.platform, "created_at": DateTime::now(), } @@ -73,15 +74,15 @@ impl AppsRepository for AppsRepo { .map_err(|e| e.to_string())?; // Re-fetch so we return the actual document (correct _id and created_at). - self.find_by_tenant_platform(app.tenant_id.as_object_id(), &app.platform) + self.find_by_tenant_platform(&app.tenant_id, &app.platform) .await? .ok_or_else(|| "App not found after upsert".to_string()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .apps - .find(doc! { "tenant_id": tenant_id }) + .find(doc! { "tenant_id": *tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -95,19 +96,19 @@ impl AppsRepository for AppsRepo { async fn find_by_tenant_platform( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, platform: &str, ) -> Result, String> { self.apps - .find_one(doc! { "tenant_id": tenant_id, "platform": platform }) + .find_one(doc! { "tenant_id": *tenant_id, "platform": platform }) .await .map_err(|e| e.to_string()) } - async fn delete_app(&self, tenant_id: &ObjectId, app_id: &ObjectId) -> Result { + async fn delete_app(&self, tenant_id: &TenantId, app_id: &AppId) -> Result { let result = self .apps - .delete_one(doc! { "_id": app_id, "tenant_id": tenant_id }) + .delete_one(doc! { "_id": *app_id, "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) diff --git a/server/src/services/billing/repos/resource_counts_adapter.rs b/server/src/services/billing/repos/resource_counts_adapter.rs index e9a2cd16..2a34df80 100644 --- a/server/src/services/billing/repos/resource_counts_adapter.rs +++ b/server/src/services/billing/repos/resource_counts_adapter.rs @@ -34,7 +34,7 @@ impl ResourceCounts for RepoResourceCounts { .count_verified_by_tenant(tenant_id) .await .map(|n| n as u64), - Resource::CreateWebhook => self.webhooks.count_by_tenant(oid).await, + Resource::CreateWebhook => self.webhooks.count_by_tenant(tenant_id).await, Resource::CreateAffiliate => self.affiliates.count_by_tenant(tenant_id).await, // TrackEvent uses the atomic counter path, not ResourceCounts. Resource::TrackEvent => Ok(0), diff --git a/server/src/services/webhooks/dispatcher.rs b/server/src/services/webhooks/dispatcher.rs index 097f2fd1..d69e4dd8 100644 --- a/server/src/services/webhooks/dispatcher.rs +++ b/server/src/services/webhooks/dispatcher.rs @@ -174,8 +174,7 @@ async fn cached_find_active_for_event( tenant_oid: crate::core::public_id::TenantId, event_type: WebhookEventType, ) -> Result, String> { - repo.find_active_for_event(&tenant_oid.to_object_id(), &event_type) - .await + repo.find_active_for_event(&tenant_oid, &event_type).await } async fn deliver_with_retry(http: &reqwest::Client, url: &str, body: &str, signature: &str) { diff --git a/server/src/services/webhooks/repo.rs b/server/src/services/webhooks/repo.rs index 59b2104f..9962a6d8 100644 --- a/server/src/services/webhooks/repo.rs +++ b/server/src/services/webhooks/repo.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; -use mongodb::bson::{doc, oid::ObjectId}; +use mongodb::bson::doc; use mongodb::{Collection, Database}; +use crate::core::public_id::{TenantId, WebhookId}; use crate::ensure_index; use super::models::{Webhook, WebhookEventType}; @@ -9,13 +10,13 @@ use super::models::{Webhook, WebhookEventType}; #[async_trait] pub trait WebhooksRepository: Send + Sync { async fn create_webhook(&self, webhook: Webhook) -> Result<(), String>; - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String>; + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String>; /// Total webhooks on this tenant — feeds the CreateWebhook quota. - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result; + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result; async fn delete_webhook( &self, - tenant_id: &ObjectId, - webhook_id: &ObjectId, + tenant_id: &TenantId, + webhook_id: &WebhookId, ) -> Result; /// Patch one or more mutable fields. `Some` overwrites; `None` leaves /// the field unchanged. Returns `true` when the webhook existed for @@ -23,15 +24,15 @@ pub trait WebhooksRepository: Send + Sync { /// "matched but unchanged" case is success for an idempotent PATCH). async fn update_webhook( &self, - tenant_id: &ObjectId, - webhook_id: &ObjectId, + tenant_id: &TenantId, + webhook_id: &WebhookId, active: Option, events: Option>, url: Option, ) -> Result; async fn find_active_for_event( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, event_type: &WebhookEventType, ) -> Result, String>; } @@ -67,10 +68,10 @@ impl WebhooksRepository for WebhooksRepo { Ok(()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .webhooks - .find(doc! { "tenant_id": tenant_id }) + .find(doc! { "tenant_id": *tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -82,21 +83,21 @@ impl WebhooksRepository for WebhooksRepo { Ok(webhooks) } - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { self.webhooks - .count_documents(doc! { "tenant_id": tenant_id }) + .count_documents(doc! { "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string()) } async fn delete_webhook( &self, - tenant_id: &ObjectId, - webhook_id: &ObjectId, + tenant_id: &TenantId, + webhook_id: &WebhookId, ) -> Result { let result = self .webhooks - .delete_one(doc! { "_id": webhook_id, "tenant_id": tenant_id }) + .delete_one(doc! { "_id": *webhook_id, "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) @@ -104,8 +105,8 @@ impl WebhooksRepository for WebhooksRepo { async fn update_webhook( &self, - tenant_id: &ObjectId, - webhook_id: &ObjectId, + tenant_id: &TenantId, + webhook_id: &WebhookId, active: Option, events: Option>, url: Option, @@ -127,7 +128,7 @@ impl WebhooksRepository for WebhooksRepo { if set_doc.is_empty() { let exists = self .webhooks - .find_one(doc! { "_id": webhook_id, "tenant_id": tenant_id }) + .find_one(doc! { "_id": *webhook_id, "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string())? .is_some(); @@ -136,7 +137,7 @@ impl WebhooksRepository for WebhooksRepo { let result = self .webhooks .update_one( - doc! { "_id": webhook_id, "tenant_id": tenant_id }, + doc! { "_id": *webhook_id, "tenant_id": *tenant_id }, doc! { "$set": set_doc }, ) .await @@ -146,7 +147,7 @@ impl WebhooksRepository for WebhooksRepo { async fn find_active_for_event( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, event_type: &WebhookEventType, ) -> Result, String> { let event_str = serde_json::to_value(event_type) @@ -158,7 +159,7 @@ impl WebhooksRepository for WebhooksRepo { let mut cursor = self .webhooks .find(doc! { - "tenant_id": tenant_id, + "tenant_id": *tenant_id, "active": true, "events": &event_str, }) diff --git a/server/tests/common/mocks/apps.rs b/server/tests/common/mocks/apps.rs index 7895daf6..ea859b4b 100644 --- a/server/tests/common/mocks/apps.rs +++ b/server/tests/common/mocks/apps.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; -use mongodb::bson::oid::ObjectId; use std::sync::Mutex; +use rift::core::public_id::{AppId, TenantId}; use rift::services::apps::models::App; use rift::services::apps::repo::AppsRepository; @@ -14,7 +14,6 @@ pub struct MockAppsRepo { impl AppsRepository for MockAppsRepo { async fn create_or_update(&self, app: App) -> Result { let mut apps = self.apps.lock().unwrap(); - // Upsert by tenant_id + platform. if let Some(existing) = apps .iter_mut() .find(|a| a.tenant_id == app.tenant_id && a.platform == app.platform) @@ -32,20 +31,20 @@ impl AppsRepository for MockAppsRepo { Ok(app) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { Ok(self .apps .lock() .unwrap() .iter() - .filter(|a| a.tenant_id.to_object_id() == *tenant_id) + .filter(|a| a.tenant_id == *tenant_id) .cloned() .collect()) } async fn find_by_tenant_platform( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, platform: &str, ) -> Result, String> { Ok(self @@ -53,16 +52,14 @@ impl AppsRepository for MockAppsRepo { .lock() .unwrap() .iter() - .find(|a| a.tenant_id.to_object_id() == *tenant_id && a.platform == platform) + .find(|a| a.tenant_id == *tenant_id && a.platform == platform) .cloned()) } - async fn delete_app(&self, tenant_id: &ObjectId, app_id: &ObjectId) -> Result { + async fn delete_app(&self, tenant_id: &TenantId, app_id: &AppId) -> Result { let mut apps = self.apps.lock().unwrap(); let len_before = apps.len(); - apps.retain(|a| { - !(a.tenant_id.to_object_id() == *tenant_id && a.id.to_object_id() == *app_id) - }); + apps.retain(|a| !(a.tenant_id == *tenant_id && a.id == *app_id)); Ok(apps.len() < len_before) } } diff --git a/server/tests/common/mocks/webhooks.rs b/server/tests/common/mocks/webhooks.rs index 1a70f197..265cff96 100644 --- a/server/tests/common/mocks/webhooks.rs +++ b/server/tests/common/mocks/webhooks.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; -use mongodb::bson::oid::ObjectId; use std::sync::Mutex; +use rift::core::public_id::{TenantId, WebhookId}; use rift::core::webhook_dispatcher::{ AttributeEventPayload, ClickEventPayload, ConversionEventPayload, IdentifyEventPayload, WebhookDispatcher, @@ -22,52 +22,51 @@ impl WebhooksRepository for MockWebhooksRepo { Ok(()) } - async fn list_by_tenant(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { Ok(self .webhooks .lock() .unwrap() .iter() - .filter(|w| w.tenant_id.to_object_id() == *tenant_id) + .filter(|w| w.tenant_id == *tenant_id) .cloned() .collect()) } - async fn count_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { Ok(self .webhooks .lock() .unwrap() .iter() - .filter(|w| w.tenant_id.to_object_id() == *tenant_id) + .filter(|w| w.tenant_id == *tenant_id) .count() as u64) } async fn delete_webhook( &self, - tenant_id: &ObjectId, - webhook_id: &ObjectId, + tenant_id: &TenantId, + webhook_id: &WebhookId, ) -> Result { let mut webhooks = self.webhooks.lock().unwrap(); let len_before = webhooks.len(); - webhooks.retain(|w| { - !(w.tenant_id.to_object_id() == *tenant_id && w.id.to_object_id() == *webhook_id) - }); + webhooks.retain(|w| !(w.tenant_id == *tenant_id && w.id == *webhook_id)); Ok(webhooks.len() < len_before) } async fn update_webhook( &self, - tenant_id: &ObjectId, - webhook_id: &ObjectId, + tenant_id: &TenantId, + webhook_id: &WebhookId, active: Option, events: Option>, url: Option, ) -> Result { let mut webhooks = self.webhooks.lock().unwrap(); - match webhooks.iter_mut().find(|w| { - w.tenant_id.to_object_id() == *tenant_id && w.id.to_object_id() == *webhook_id - }) { + match webhooks + .iter_mut() + .find(|w| w.tenant_id == *tenant_id && w.id == *webhook_id) + { Some(w) => { if let Some(a) = active { w.active = a; @@ -86,7 +85,7 @@ impl WebhooksRepository for MockWebhooksRepo { async fn find_active_for_event( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, event_type: &WebhookEventType, ) -> Result, String> { Ok(self @@ -94,11 +93,7 @@ impl WebhooksRepository for MockWebhooksRepo { .lock() .unwrap() .iter() - .filter(|w| { - w.tenant_id.to_object_id() == *tenant_id - && w.active - && w.events.contains(event_type) - }) + .filter(|w| w.tenant_id == *tenant_id && w.active && w.events.contains(event_type)) .cloned() .collect()) } From e534814c2ea21ea436b9764196af4b60ad29a54c Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 12:53:16 -0500 Subject: [PATCH 06/10] sessions + event_counters repos: take typed IDs --- server/src/services/auth/sessions/repo.rs | 15 ++++++++------- server/src/services/auth/sessions/service.rs | 7 ++----- server/src/services/billing/quota.rs | 3 +-- server/src/services/billing/quota_tests.rs | 2 +- .../src/services/billing/repos/event_counters.rs | 13 +++++++------ 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/server/src/services/auth/sessions/repo.rs b/server/src/services/auth/sessions/repo.rs index 9bf7f537..92c5272f 100644 --- a/server/src/services/auth/sessions/repo.rs +++ b/server/src/services/auth/sessions/repo.rs @@ -6,11 +6,12 @@ //! - TTL on `expires_at` so MongoDB sweeps expired sessions automatically use async_trait::async_trait; -use mongodb::bson::{self, doc, oid::ObjectId}; +use mongodb::bson::{self, doc}; use mongodb::options::IndexOptions; use mongodb::{Collection, Database}; use super::models::SessionDoc; +use crate::core::public_id::AuthSessionId; use crate::ensure_index; #[async_trait] @@ -24,11 +25,11 @@ pub trait SessionsRepository: Send + Sync { /// Mark `revoked_at = now` for a single session. Idempotent: revoking an /// already-revoked session is a no-op. - async fn revoke(&self, session_id: &ObjectId) -> Result; + async fn revoke(&self, session_id: &AuthSessionId) -> Result; /// Bump `last_seen_at = now`. Caller is expected to debounce (the middleware /// only calls this when `now - last_seen > 60s`). - async fn touch_last_seen(&self, session_id: &ObjectId) -> Result<(), String>; + async fn touch_last_seen(&self, session_id: &AuthSessionId) -> Result<(), String>; } // ── Mongo impl ── @@ -86,12 +87,12 @@ impl SessionsRepository for SessionsRepoMongo { .map_err(|e| e.to_string()) } - async fn revoke(&self, session_id: &ObjectId) -> Result { + async fn revoke(&self, session_id: &AuthSessionId) -> Result { let now = bson::DateTime::now(); let result = self .col .update_one( - doc! { "_id": session_id, "revoked_at": null }, + doc! { "_id": *session_id, "revoked_at": null }, doc! { "$set": { "revoked_at": now } }, ) .await @@ -99,11 +100,11 @@ impl SessionsRepository for SessionsRepoMongo { Ok(result.modified_count > 0) } - async fn touch_last_seen(&self, session_id: &ObjectId) -> Result<(), String> { + async fn touch_last_seen(&self, session_id: &AuthSessionId) -> Result<(), String> { let now = bson::DateTime::now(); self.col .update_one( - doc! { "_id": session_id }, + doc! { "_id": *session_id }, doc! { "$set": { "last_seen_at": now } }, ) .await diff --git a/server/src/services/auth/sessions/service.rs b/server/src/services/auth/sessions/service.rs index 0ae8ab5a..66133128 100644 --- a/server/src/services/auth/sessions/service.rs +++ b/server/src/services/auth/sessions/service.rs @@ -317,10 +317,7 @@ impl SessionsService { let staleness_secs = mongodb::bson::DateTime::now().timestamp_millis() / 1000 - session.last_seen_at.timestamp_millis() / 1000; if staleness_secs > Self::TOUCH_INTERVAL_SECS { - let _ = self - .sessions_repo - .touch_last_seen(&session.id.to_object_id()) - .await; + let _ = self.sessions_repo.touch_last_seen(&session.id).await; } Ok(Some(ResolvedSession { @@ -336,7 +333,7 @@ impl SessionsService { session_id: &crate::core::public_id::AuthSessionId, ) -> Result<(), SessionError> { self.sessions_repo - .revoke(&session_id.to_object_id()) + .revoke(session_id) .await .map(|_| ()) .map_err(SessionError::Internal) diff --git a/server/src/services/billing/quota.rs b/server/src/services/billing/quota.rs index 795686a1..0036f40c 100644 --- a/server/src/services/billing/quota.rs +++ b/server/src/services/billing/quota.rs @@ -88,7 +88,6 @@ impl QuotaChecker for QuotaService { /// - `Enforce`: returns `Err(QuotaError::Exceeded { ... })` when over /// limit. Caller renders as `402 Payment Required`. async fn check(&self, tenant_id: &TenantId, resource: Resource) -> Result<(), QuotaError> { - let oid = tenant_id.as_object_id(); let tier = self.billing.effective_tier(tenant_id).await?; let limits = limits_for(tier); let max = match limit_for_resource(&limits, resource) { @@ -101,7 +100,7 @@ impl QuotaChecker for QuotaService { let period = current_period(); let within = self .counters - .increment_if_below(oid, &period, Some(max)) + .increment_if_below(tenant_id, &period, Some(max)) .await .map_err(|e| QuotaError::Billing(BillingError::Internal(e)))?; if within { diff --git a/server/src/services/billing/quota_tests.rs b/server/src/services/billing/quota_tests.rs index f1f3d76c..e3cca586 100644 --- a/server/src/services/billing/quota_tests.rs +++ b/server/src/services/billing/quota_tests.rs @@ -76,7 +76,7 @@ struct MockCounters { impl EventCountersRepository for MockCounters { async fn increment_if_below( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _period: &str, max: Option, ) -> Result { diff --git a/server/src/services/billing/repos/event_counters.rs b/server/src/services/billing/repos/event_counters.rs index 1935eddd..f746974b 100644 --- a/server/src/services/billing/repos/event_counters.rs +++ b/server/src/services/billing/repos/event_counters.rs @@ -1,9 +1,10 @@ use async_trait::async_trait; -use mongodb::bson::{self, doc, oid::ObjectId}; +use mongodb::bson::{self, doc}; use mongodb::error::ErrorKind; use mongodb::options::{FindOneAndUpdateOptions, IndexOptions, ReturnDocument}; use mongodb::{Collection, Database}; +use crate::core::public_id::TenantId; use crate::ensure_index; pub use crate::services::billing::models::EventCounterDoc; @@ -20,7 +21,7 @@ pub use crate::services::billing::models::EventCounterDoc; pub trait EventCountersRepository: Send + Sync { async fn increment_if_below( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, period: &str, max: Option, ) -> Result; @@ -51,8 +52,8 @@ impl EventCountersRepo { } } -fn counter_id(tenant_id: &ObjectId, period: &str) -> String { - format!("{}:{}:events", tenant_id.to_hex(), period) +fn counter_id(tenant_id: &TenantId, period: &str) -> String { + format!("{}:{}:events", tenant_id.as_object_id().to_hex(), period) } fn is_duplicate_key(e: &mongodb::error::Error) -> bool { @@ -63,7 +64,7 @@ fn is_duplicate_key(e: &mongodb::error::Error) -> bool { impl EventCountersRepository for EventCountersRepo { async fn increment_if_below( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, period: &str, max: Option, ) -> Result { @@ -76,7 +77,7 @@ impl EventCountersRepository for EventCountersRepo { let update = doc! { "$inc": { "count": 1i64 }, "$setOnInsert": { - "tenant_id": tenant_id, + "tenant_id": *tenant_id, "period": period, "created_at": bson::DateTime::now(), }, From 37677162821bac4a93ed9d538e00e9be73f7372f Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 12:57:37 -0500 Subject: [PATCH 07/10] install_events + app_users repos: take typed TenantId --- server/src/services/analytics/service.rs | 1 - server/src/services/app_users/repo.rs | 30 +++++++----------- server/src/services/conversions/service.rs | 2 +- server/src/services/install_events/repo.rs | 37 +++++++++++----------- server/src/services/links/service.rs | 16 +++++----- server/tests/common/mocks/app_users.rs | 23 ++++++-------- 6 files changed, 48 insertions(+), 61 deletions(-) diff --git a/server/src/services/analytics/service.rs b/server/src/services/analytics/service.rs index eb9d9b30..4353a869 100644 --- a/server/src/services/analytics/service.rs +++ b/server/src/services/analytics/service.rs @@ -185,7 +185,6 @@ impl AnalyticsService { install_ids: &[String], params: &FunnelParams, ) -> Result { - let tenant_id = &tenant_id.to_object_id(); self.install_events_repo .count_events_by_type_for_installs( tenant_id, diff --git a/server/src/services/app_users/repo.rs b/server/src/services/app_users/repo.rs index a7a5acfb..37b00351 100644 --- a/server/src/services/app_users/repo.rs +++ b/server/src/services/app_users/repo.rs @@ -4,6 +4,7 @@ use mongodb::options::{IndexOptions, ReturnDocument}; use mongodb::{Collection, Database}; use super::models::{AppUserDoc, AppUserUpsert}; +use crate::core::public_id::TenantId; use crate::ensure_index; // ── Trait ── @@ -18,7 +19,7 @@ pub trait AppUsersRepository: Send + Sync { /// - `AlreadyPresent` — user existed with this install already bound. async fn upsert_with_install( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_id: &str, install_id: &str, ) -> Result; @@ -29,7 +30,7 @@ pub trait AppUsersRepository: Send + Sync { /// devices. async fn find_by_user_id( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_id: &str, ) -> Result, String>; @@ -39,7 +40,7 @@ pub trait AppUsersRepository: Send + Sync { /// read side and removing the need for a separate `installs` collection. async fn find_user_id_for_install( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, ) -> Result, String>; } @@ -56,9 +57,6 @@ impl AppUsersRepo { pub async fn new(database: &Database) -> Self { let app_users = database.collection::("app_users"); - // Primary lookup: tenant + user_id. Unique — one row per - // (tenant, customer-supplied user_id). Loser of concurrent inserts - // retries via the upsert path. ensure_index!( app_users, doc! { "tenant_id": 1, "user_id": 1 }, @@ -66,9 +64,6 @@ impl AppUsersRepo { "app_users_tenant_user_unique" ); - // Reverse lookup: tenant + install_id (multikey index over array). - // Powers "find the user who owns this install" — the write-time - // enrichment path for attribution events. ensure_index!( app_users, doc! { "tenant_id": 1, "install_ids": 1 }, @@ -83,22 +78,19 @@ impl AppUsersRepo { impl AppUsersRepository for AppUsersRepo { async fn upsert_with_install( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_id: &str, install_id: &str, ) -> Result { - // Single-roundtrip upsert with $addToSet for idempotent install - // accumulation. ReturnDocument::Before lets us classify the outcome - // without a second query. let now = DateTime::now(); let before = self .app_users .find_one_and_update( - doc! { "tenant_id": tenant_id, "user_id": user_id }, + doc! { "tenant_id": *tenant_id, "user_id": user_id }, doc! { "$setOnInsert": { "_id": ObjectId::new(), - "tenant_id": tenant_id, + "tenant_id": *tenant_id, "user_id": user_id, "identified_at": now, }, @@ -122,23 +114,23 @@ impl AppUsersRepository for AppUsersRepo { async fn find_by_user_id( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_id: &str, ) -> Result, String> { self.app_users - .find_one(doc! { "tenant_id": tenant_id, "user_id": user_id }) + .find_one(doc! { "tenant_id": *tenant_id, "user_id": user_id }) .await .map_err(|e| e.to_string()) } async fn find_user_id_for_install( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, ) -> Result, String> { let doc = self .app_users - .find_one(doc! { "tenant_id": tenant_id, "install_ids": install_id }) + .find_one(doc! { "tenant_id": *tenant_id, "install_ids": install_id }) .await .map_err(|e| e.to_string())?; Ok(doc.map(|d| d.user_id)) diff --git a/server/src/services/conversions/service.rs b/server/src/services/conversions/service.rs index f22eca9e..fca35912 100644 --- a/server/src/services/conversions/service.rs +++ b/server/src/services/conversions/service.rs @@ -125,7 +125,7 @@ impl ConversionsService { }; let user_known = match &self.app_users_repo { Some(repo) => repo - .find_by_user_id(&tenant_oid, user_id) + .find_by_user_id(&tenant_id, user_id) .await .ok() .flatten() diff --git a/server/src/services/install_events/repo.rs b/server/src/services/install_events/repo.rs index 4dc6f19a..b9656a9a 100644 --- a/server/src/services/install_events/repo.rs +++ b/server/src/services/install_events/repo.rs @@ -1,8 +1,9 @@ use async_trait::async_trait; -use mongodb::bson::{doc, oid::ObjectId, DateTime}; +use mongodb::bson::{doc, DateTime}; use mongodb::{Collection, Database}; use super::models::{InstallContext, InstallEvent, InstallEventType}; +use crate::core::public_id::{InstallEventId, TenantId}; use crate::ensure_index; // ── Trait ── @@ -14,7 +15,7 @@ pub trait InstallEventsRepository: Send + Sync { /// (e.g. webhook dispatcher) can decide whether to fire downstream. async fn record_attribute_lifecycle( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, context: &InstallContext, ) -> Result; @@ -30,7 +31,7 @@ pub trait InstallEventsRepository: Send + Sync { /// device_model data is missing. async fn record_identify_lifecycle( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, user_id: &str, prior_install_ids: &[String], @@ -43,7 +44,7 @@ pub trait InstallEventsRepository: Send + Sync { /// reinstall-vs-new-device input. async fn get_device_model( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, ) -> Result, String>; @@ -52,7 +53,7 @@ pub trait InstallEventsRepository: Send + Sync { /// Returns 0 immediately if `install_ids` is empty. async fn count_events_by_type_for_installs( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, event_type: InstallEventType, install_ids: &[String], from: DateTime, @@ -93,7 +94,7 @@ impl InstallEventsRepo { impl InstallEventsRepository for InstallEventsRepo { async fn record_attribute_lifecycle( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, context: &InstallContext, ) -> Result { @@ -102,7 +103,7 @@ impl InstallEventsRepository for InstallEventsRepo { let existing = self .events .find_one(doc! { - "tenant_id": tenant_id, + "tenant_id": *tenant_id, "install_id": install_id, "event_type": "created", }) @@ -116,8 +117,8 @@ impl InstallEventsRepository for InstallEventsRepo { }; let event = InstallEvent { - id: Some(crate::core::public_id::InstallEventId::new()), - tenant_id: crate::core::public_id::TenantId::from_object_id(*tenant_id), + id: Some(InstallEventId::new()), + tenant_id: *tenant_id, install_id: install_id.to_string(), event_type, timestamp: DateTime::now(), @@ -139,7 +140,7 @@ impl InstallEventsRepository for InstallEventsRepo { async fn record_identify_lifecycle( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, user_id: &str, prior_install_ids: &[String], @@ -151,8 +152,8 @@ impl InstallEventsRepository for InstallEventsRepo { // Always write install.identified. let identified = InstallEvent { - id: Some(crate::core::public_id::InstallEventId::new()), - tenant_id: crate::core::public_id::TenantId::from_object_id(*tenant_id), + id: Some(InstallEventId::new()), + tenant_id: *tenant_id, install_id: install_id.to_string(), event_type: InstallEventType::Identified, timestamp: now, @@ -186,8 +187,8 @@ impl InstallEventsRepository for InstallEventsRepo { }; let event = InstallEvent { - id: Some(crate::core::public_id::InstallEventId::new()), - tenant_id: crate::core::public_id::TenantId::from_object_id(*tenant_id), + id: Some(InstallEventId::new()), + tenant_id: *tenant_id, install_id: install_id.to_string(), event_type: classification, timestamp: now, @@ -209,13 +210,13 @@ impl InstallEventsRepository for InstallEventsRepo { async fn get_device_model( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, ) -> Result, String> { let event = self .events .find_one(doc! { - "tenant_id": tenant_id, + "tenant_id": *tenant_id, "install_id": install_id, "event_type": "created", }) @@ -226,7 +227,7 @@ impl InstallEventsRepository for InstallEventsRepo { async fn count_events_by_type_for_installs( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, event_type: InstallEventType, install_ids: &[String], from: DateTime, @@ -248,7 +249,7 @@ impl InstallEventsRepository for InstallEventsRepo { }; self.events .count_documents(doc! { - "tenant_id": tenant_id, + "tenant_id": *tenant_id, "event_type": type_str, "install_id": { "$in": bson_ids }, "timestamp": { "$gte": from, "$lte": to }, diff --git a/server/src/services/links/service.rs b/server/src/services/links/service.rs index b3fb9fc3..d3e5241d 100644 --- a/server/src/services/links/service.rs +++ b/server/src/services/links/service.rs @@ -95,7 +95,7 @@ impl LinksService { // silently would let a logged-out + re-logged-in flow on a // shared device leak attribution across users. if let Some(existing) = app_users - .find_user_id_for_install(tenant_oid, install_id) + .find_user_id_for_install(tenant_id, install_id) .await .map_err(LinkError::Internal)? { @@ -110,7 +110,7 @@ impl LinksService { // the current one — this is what feeds the reinstall vs // new_device classification in step 4. let prior_install_ids: Vec = - match app_users.find_by_user_id(tenant_oid, user_id).await { + match app_users.find_by_user_id(tenant_id, user_id).await { Ok(Some(existing)) => existing.install_ids, Ok(None) => Vec::new(), Err(e) => { @@ -124,7 +124,7 @@ impl LinksService { }; let upsert = app_users - .upsert_with_install(tenant_oid, user_id, install_id) + .upsert_with_install(tenant_id, user_id, install_id) .await .map_err(LinkError::Internal)?; @@ -167,14 +167,14 @@ impl LinksService { // reinstall vs new_device. if let Some(install_events) = &self.install_events_repo { let current_device_model = install_events - .get_device_model(tenant_oid, install_id) + .get_device_model(tenant_id, install_id) .await .ok() .flatten(); let mut prior_device_models = Vec::with_capacity(prior_install_ids.len()); for prior_id in &prior_install_ids { - if let Ok(Some(model)) = install_events.get_device_model(tenant_oid, prior_id).await + if let Ok(Some(model)) = install_events.get_device_model(tenant_id, prior_id).await { prior_device_models.push(model); } @@ -182,7 +182,7 @@ impl LinksService { if let Err(e) = install_events .record_identify_lifecycle( - tenant_oid, + tenant_id, install_id, user_id, &prior_install_ids, @@ -308,7 +308,7 @@ impl LinksService { // will backfill). let user_id = match &self.app_users_repo { Some(app_users) => app_users - .find_user_id_for_install(&tenant_oid, install_id) + .find_user_id_for_install(&tenant_id, install_id) .await .unwrap_or_else(|e| { tracing::warn!(error = %e, install_id, "app_users user_id lookup failed"); @@ -337,7 +337,7 @@ impl LinksService { ..InstallContext::default() }); if let Err(e) = install_events - .record_attribute_lifecycle(&tenant_oid, install_id, &ctx) + .record_attribute_lifecycle(&tenant_id, install_id, &ctx) .await { tracing::warn!( diff --git a/server/tests/common/mocks/app_users.rs b/server/tests/common/mocks/app_users.rs index 30063a67..e3e31036 100644 --- a/server/tests/common/mocks/app_users.rs +++ b/server/tests/common/mocks/app_users.rs @@ -1,12 +1,10 @@ use async_trait::async_trait; -use mongodb::bson::oid::ObjectId; use std::sync::Mutex; +use rift::core::public_id::{AppUserId, TenantId}; use rift::services::app_users::models::{AppUserDoc, AppUserUpsert}; use rift::services::app_users::repo::AppUsersRepository; -/// In-memory app_users mock. Same multi-install accumulation semantics as -/// the real repo (`$addToSet` on `install_ids`). #[derive(Default)] pub struct MockAppUsersRepo { rows: Mutex>, @@ -16,14 +14,14 @@ pub struct MockAppUsersRepo { impl AppUsersRepository for MockAppUsersRepo { async fn upsert_with_install( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_id: &str, install_id: &str, ) -> Result { let mut rows = self.rows.lock().unwrap(); if let Some(row) = rows .iter_mut() - .find(|r| r.tenant_id.to_object_id() == *tenant_id && r.user_id == user_id) + .find(|r| r.tenant_id == *tenant_id && r.user_id == user_id) { if row.install_ids.iter().any(|i| i == install_id) { Ok(AppUserUpsert::AlreadyPresent) @@ -33,8 +31,8 @@ impl AppUsersRepository for MockAppUsersRepo { } } else { rows.push(AppUserDoc { - id: Some(rift::core::public_id::AppUserId::new()), - tenant_id: rift::core::public_id::TenantId::from_object_id(*tenant_id), + id: Some(AppUserId::new()), + tenant_id: *tenant_id, user_id: user_id.to_string(), install_ids: vec![install_id.to_string()], identified_at: mongodb::bson::DateTime::now(), @@ -54,28 +52,25 @@ impl AppUsersRepository for MockAppUsersRepo { async fn find_by_user_id( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_id: &str, ) -> Result, String> { let rows = self.rows.lock().unwrap(); Ok(rows .iter() - .find(|r| r.tenant_id.to_object_id() == *tenant_id && r.user_id == user_id) + .find(|r| r.tenant_id == *tenant_id && r.user_id == user_id) .cloned()) } async fn find_user_id_for_install( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, ) -> Result, String> { let rows = self.rows.lock().unwrap(); Ok(rows .iter() - .find(|r| { - r.tenant_id.to_object_id() == *tenant_id - && r.install_ids.iter().any(|i| i == install_id) - }) + .find(|r| r.tenant_id == *tenant_id && r.install_ids.iter().any(|i| i == install_id)) .map(|r| r.user_id.clone())) } } From 5c10210aaf27f7c9946fed790209991381d7b6e0 Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 13:01:30 -0500 Subject: [PATCH 08/10] conversions/repo.rs: take typed TenantId/SourceId --- server/src/api/conversions/routes.rs | 22 ++------ server/src/mcp/server.rs | 4 +- server/src/services/analytics/service.rs | 2 +- server/src/services/conversions/repo.rs | 62 +++++++++++----------- server/src/services/conversions/service.rs | 2 +- server/tests/common/mocks/conversions.rs | 21 ++++---- 6 files changed, 51 insertions(+), 62 deletions(-) diff --git a/server/src/api/conversions/routes.rs b/server/src/api/conversions/routes.rs index 236f5ddd..45092b75 100644 --- a/server/src/api/conversions/routes.rs +++ b/server/src/api/conversions/routes.rs @@ -50,10 +50,7 @@ pub async fn create_source( .into_response(); } - match repo - .create_source(tenant.to_object_id(), name, req.source_type) - .await - { + match repo.create_source(tenant, name, req.source_type).await { Ok(source) => { let resp = CreateSourceResponse { id: source.id, @@ -113,7 +110,7 @@ pub async fn list_sources( .into_response(); }; - let mut sources = match repo.list_sources(&tenant.to_object_id()).await { + let mut sources = match repo.list_sources(&tenant).await { Ok(s) => s, Err(e) => { tracing::error!(error = %e, "Failed to list sources"); @@ -128,10 +125,7 @@ pub async fn list_sources( // Auto-provision a default custom source if the tenant has none. This is the // zero-ceremony dev flow: first GET returns a usable webhook URL immediately. if sources.is_empty() { - match repo - .get_or_create_default_custom_source(tenant.to_object_id()) - .await - { + match repo.get_or_create_default_custom_source(tenant).await { Ok(source) => sources.push(source), Err(e) => { tracing::error!(error = %e, "Failed to auto-provision default source"); @@ -175,10 +169,7 @@ pub async fn get_source( .into_response(); }; - match repo - .find_source_by_id(&tenant.to_object_id(), &id.to_object_id()) - .await - { + match repo.find_source_by_id(&tenant, &id).await { Ok(Some(source)) => Json(to_detail(&state, &source)).into_response(), Ok(None) => ( StatusCode::NOT_FOUND, @@ -223,10 +214,7 @@ pub async fn delete_source( .into_response(); }; - match repo - .delete_source(&tenant.to_object_id(), &id.to_object_id()) - .await - { + match repo.delete_source(&tenant, &id).await { Ok(true) => StatusCode::NO_CONTENT.into_response(), Ok(false) => ( StatusCode::NOT_FOUND, diff --git a/server/src/mcp/server.rs b/server/src/mcp/server.rs index c36bda7e..4a84a98d 100644 --- a/server/src/mcp/server.rs +++ b/server/src/mcp/server.rs @@ -289,7 +289,7 @@ impl RiftMcp { Parameters(input): Parameters, Extension(parts): Extension, ) -> Result, String> { - let tenant_id = self.auth_context(&parts).await?.tenant_id.to_object_id(); + let tenant_id = self.auth_context(&parts).await?.tenant_id; let repo = self .conversions_repo .as_ref() @@ -336,7 +336,7 @@ impl RiftMcp { Parameters(_input): Parameters, Extension(parts): Extension, ) -> Result, String> { - let tenant_id = self.auth_context(&parts).await?.tenant_id.to_object_id(); + let tenant_id = self.auth_context(&parts).await?.tenant_id; let repo = self .conversions_repo .as_ref() diff --git a/server/src/services/analytics/service.rs b/server/src/services/analytics/service.rs index 4353a869..e4778ab8 100644 --- a/server/src/services/analytics/service.rs +++ b/server/src/services/analytics/service.rs @@ -136,7 +136,7 @@ impl AnalyticsService { let conversions: BTreeMap = match &self.conversions_repo { Some(cr) => cr .count_conversions_by_type_credited_to_links( - tenant_id.as_object_id(), + tenant_id, ¶ms.link_ids, params.from, params.to, diff --git a/server/src/services/conversions/repo.rs b/server/src/services/conversions/repo.rs index 6c47ab90..c040053a 100644 --- a/server/src/services/conversions/repo.rs +++ b/server/src/services/conversions/repo.rs @@ -7,7 +7,7 @@ use rand::RngCore; use crate::ensure_index; use super::models::{ConversionDedup, ConversionEvent, Source, SourceType}; -use crate::core::public_id::SourceId; +use crate::core::public_id::{SourceId, TenantId}; use crate::services::links::models::CreditModel; /// Sentinel `source_id` for events that came in via the SDK direct endpoint @@ -26,7 +26,7 @@ pub trait ConversionsRepository: Send + Sync { async fn create_source( &self, - tenant_id: ObjectId, + tenant_id: TenantId, name: String, source_type: SourceType, ) -> Result; @@ -35,20 +35,20 @@ pub trait ConversionsRepository: Send + Sync { async fn find_source_by_id( &self, - tenant_id: &ObjectId, - id: &ObjectId, + tenant_id: &TenantId, + id: &SourceId, ) -> Result, String>; - async fn list_sources(&self, tenant_id: &ObjectId) -> Result, String>; + async fn list_sources(&self, tenant_id: &TenantId) -> Result, String>; - async fn delete_source(&self, tenant_id: &ObjectId, id: &ObjectId) -> Result; + async fn delete_source(&self, tenant_id: &TenantId, id: &SourceId) -> Result; /// Returns the tenant's default custom source, creating it (name: "default") /// on first access. Enables the zero-ceremony dev flow of `GET /v1/sources` /// returning a usable webhook URL immediately. async fn get_or_create_default_custom_source( &self, - tenant_id: ObjectId, + tenant_id: TenantId, ) -> Result; // ── Events ── @@ -64,7 +64,7 @@ pub trait ConversionsRepository: Send + Sync { /// - `Err(_)` — other DB error; caller logs and skips async fn check_and_insert_dedup( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, idempotency_key: &str, ) -> Result; @@ -76,7 +76,7 @@ pub trait ConversionsRepository: Send + Sync { #[allow(dead_code)] // kept for reference / future debug; funnel uses the credited variant async fn count_by_type_for_users( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_ids: &[String], from: DateTime, to: DateTime, @@ -100,7 +100,7 @@ pub trait ConversionsRepository: Send + Sync { /// Returns an empty vec for empty `link_ids`. async fn count_conversions_by_type_credited_to_links( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_ids: &[String], from: DateTime, to: DateTime, @@ -199,13 +199,13 @@ fn generate_url_token() -> String { impl ConversionsRepository for ConversionsRepo { async fn create_source( &self, - tenant_id: ObjectId, + tenant_id: TenantId, name: String, source_type: SourceType, ) -> Result { let doc = Source { - id: crate::core::public_id::SourceId::new(), - tenant_id: crate::core::public_id::TenantId::from_object_id(tenant_id), + id: SourceId::new(), + tenant_id, name, source_type, url_token: generate_url_token(), @@ -229,19 +229,19 @@ impl ConversionsRepository for ConversionsRepo { async fn find_source_by_id( &self, - tenant_id: &ObjectId, - id: &ObjectId, + tenant_id: &TenantId, + id: &SourceId, ) -> Result, String> { self.sources - .find_one(doc! { "_id": id, "tenant_id": tenant_id }) + .find_one(doc! { "_id": *id, "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string()) } - async fn list_sources(&self, tenant_id: &ObjectId) -> Result, String> { + async fn list_sources(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .sources - .find(doc! { "tenant_id": tenant_id }) + .find(doc! { "tenant_id": *tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -253,10 +253,10 @@ impl ConversionsRepository for ConversionsRepo { Ok(sources) } - async fn delete_source(&self, tenant_id: &ObjectId, id: &ObjectId) -> Result { + async fn delete_source(&self, tenant_id: &TenantId, id: &SourceId) -> Result { let result = self .sources - .delete_one(doc! { "_id": id, "tenant_id": tenant_id }) + .delete_one(doc! { "_id": *id, "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) @@ -264,11 +264,11 @@ impl ConversionsRepository for ConversionsRepo { async fn get_or_create_default_custom_source( &self, - tenant_id: ObjectId, + tenant_id: TenantId, ) -> Result { if let Some(existing) = self .sources - .find_one(doc! { "tenant_id": &tenant_id, "name": "default" }) + .find_one(doc! { "tenant_id": tenant_id, "name": "default" }) .await .map_err(|e| e.to_string())? { @@ -284,7 +284,7 @@ impl ConversionsRepository for ConversionsRepo { Ok(source) => Ok(source), Err(e) if e.contains("E11000") => self .sources - .find_one(doc! { "tenant_id": &tenant_id, "name": "default" }) + .find_one(doc! { "tenant_id": tenant_id, "name": "default" }) .await .map_err(|e| e.to_string())? .ok_or_else(|| "default source disappeared after conflict".to_string()), @@ -307,12 +307,12 @@ impl ConversionsRepository for ConversionsRepo { async fn check_and_insert_dedup( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, idempotency_key: &str, ) -> Result { let doc = ConversionDedup { id: crate::services::conversions::models::ConversionDedupId::new(), - tenant_id: crate::core::public_id::TenantId::from_object_id(*tenant_id), + tenant_id: *tenant_id, idempotency_key: idempotency_key.to_string(), created_at: DateTime::now(), }; @@ -325,7 +325,7 @@ impl ConversionsRepository for ConversionsRepo { async fn count_by_type_for_users( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_ids: &[String], from: DateTime, to: DateTime, @@ -340,7 +340,7 @@ impl ConversionsRepository for ConversionsRepo { let pipeline = vec![ doc! { "$match": { - "meta.tenant_id": tenant_id, + "meta.tenant_id": *tenant_id, "user_id": { "$in": bson_ids }, "occurred_at": { "$gte": from, "$lte": to }, } @@ -373,7 +373,7 @@ impl ConversionsRepository for ConversionsRepo { async fn count_conversions_by_type_credited_to_links( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_ids: &[String], from: DateTime, to: DateTime, @@ -406,7 +406,7 @@ impl ConversionsRepository for ConversionsRepo { "$match": { "$expr": { "$and": [ - { "$eq": ["$meta.tenant_id", tenant_id] }, + { "$eq": ["$meta.tenant_id", *tenant_id] }, { "$eq": ["$meta.user_id", "$$uid"] }, { "$lte": ["$timestamp", "$$convo_t"] }, { "$in": ["$link_id", bson_links.clone()] }, @@ -428,7 +428,7 @@ impl ConversionsRepository for ConversionsRepo { "$match": { "$expr": { "$and": [ - { "$eq": ["$meta.tenant_id", tenant_id] }, + { "$eq": ["$meta.tenant_id", *tenant_id] }, { "$eq": ["$meta.user_id", "$$uid"] }, { "$lte": ["$timestamp", "$$convo_t"] }, ] @@ -454,7 +454,7 @@ impl ConversionsRepository for ConversionsRepo { let pipeline = vec![ doc! { "$match": { - "meta.tenant_id": tenant_id, + "meta.tenant_id": *tenant_id, "occurred_at": { "$gte": from, "$lte": to }, "user_id": { "$ne": null }, } diff --git a/server/src/services/conversions/service.rs b/server/src/services/conversions/service.rs index fca35912..09f87168 100644 --- a/server/src/services/conversions/service.rs +++ b/server/src/services/conversions/service.rs @@ -89,7 +89,7 @@ impl ConversionsService { if let Some(key) = &event.idempotency_key { match self .conversions_repo - .check_and_insert_dedup(&tenant_oid, key) + .check_and_insert_dedup(&tenant_id, key) .await { Ok(false) => { diff --git a/server/tests/common/mocks/conversions.rs b/server/tests/common/mocks/conversions.rs index c40a9899..bb6c0107 100644 --- a/server/tests/common/mocks/conversions.rs +++ b/server/tests/common/mocks/conversions.rs @@ -3,6 +3,7 @@ use mongodb::bson::oid::ObjectId; use std::collections::HashSet; use std::sync::Mutex; +use rift::core::public_id::{SourceId, TenantId}; use rift::services::conversions::models::{ConversionEvent, Source, SourceType}; use rift::services::conversions::repo::ConversionsRepository; @@ -16,7 +17,7 @@ pub struct MockConversionsRepo { impl ConversionsRepository for MockConversionsRepo { async fn create_source( &self, - _tenant_id: ObjectId, + _tenant_id: TenantId, _name: String, _source_type: SourceType, ) -> Result { @@ -29,23 +30,23 @@ impl ConversionsRepository for MockConversionsRepo { async fn find_source_by_id( &self, - _tenant_id: &ObjectId, - _id: &ObjectId, + _tenant_id: &TenantId, + _id: &SourceId, ) -> Result, String> { Ok(None) } - async fn list_sources(&self, _tenant_id: &ObjectId) -> Result, String> { + async fn list_sources(&self, _tenant_id: &TenantId) -> Result, String> { Ok(Vec::new()) } - async fn delete_source(&self, _tenant_id: &ObjectId, _id: &ObjectId) -> Result { + async fn delete_source(&self, _tenant_id: &TenantId, _id: &SourceId) -> Result { Ok(false) } async fn get_or_create_default_custom_source( &self, - _tenant_id: ObjectId, + _tenant_id: TenantId, ) -> Result { unimplemented!("not needed for conversion tests") } @@ -57,10 +58,10 @@ impl ConversionsRepository for MockConversionsRepo { async fn check_and_insert_dedup( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, idempotency_key: &str, ) -> Result { - let key = (tenant_id.to_hex(), idempotency_key.to_string()); + let key = (tenant_id.to_string(), idempotency_key.to_string()); let mut keys = self.dedup_keys.lock().unwrap(); if keys.contains(&key) { Ok(false) @@ -72,7 +73,7 @@ impl ConversionsRepository for MockConversionsRepo { async fn count_by_type_for_users( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _user_ids: &[String], _from: mongodb::bson::DateTime, _to: mongodb::bson::DateTime, @@ -82,7 +83,7 @@ impl ConversionsRepository for MockConversionsRepo { async fn count_conversions_by_type_credited_to_links( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _link_ids: &[String], _from: mongodb::bson::DateTime, _to: mongodb::bson::DateTime, From fb9b81fb20776627d9d697e0cbc03c072d501a4c Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 13:14:27 -0500 Subject: [PATCH 09/10] =?UTF-8?q?links/repo.rs:=20take=20typed=20TenantId?= =?UTF-8?q?=20=E2=80=94=20drop=20~80=20boundary=20conversions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/api/lifecycle/routes.rs | 4 +- server/src/api/links/qr.rs | 2 +- server/src/api/links/routes.rs | 2 +- server/src/services/analytics/service.rs | 9 +-- server/src/services/billing/quota_tests.rs | 1 - .../billing/repos/resource_counts_adapter.rs | 3 +- server/src/services/billing/service_tests.rs | 1 - server/src/services/conversions/service.rs | 3 +- server/src/services/links/repo.rs | 78 ++++++++++--------- server/src/services/links/service.rs | 32 ++++---- server/src/services/links/service_tests.rs | 42 +++++----- server/tests/common/mocks/links.rs | 41 +++++----- 12 files changed, 102 insertions(+), 116 deletions(-) diff --git a/server/src/api/lifecycle/routes.rs b/server/src/api/lifecycle/routes.rs index ec2e964c..ff84b3f6 100644 --- a/server/src/api/lifecycle/routes.rs +++ b/server/src/api/lifecycle/routes.rs @@ -65,7 +65,7 @@ pub async fn lifecycle_click( }; let link = repo - .find_link_by_tenant_and_id(&tenant.to_object_id(), &req.link_id) + .find_link_by_tenant_and_id(&tenant, &req.link_id) .await .ok() .flatten(); @@ -176,7 +176,7 @@ pub async fn lifecycle_attribute( }; let link = repo - .find_link_by_tenant_and_id(&tenant.to_object_id(), &req.link_id) + .find_link_by_tenant_and_id(&tenant, &req.link_id) .await .ok() .flatten(); diff --git a/server/src/api/links/qr.rs b/server/src/api/links/qr.rs index 3f2f18e1..4a30d8f8 100644 --- a/server/src/api/links/qr.rs +++ b/server/src/api/links/qr.rs @@ -50,7 +50,7 @@ pub(crate) async fn render_link_qr( }; let Some(link) = repo - .find_link_by_tenant_and_id(&tenant.to_object_id(), &link_id) + .find_link_by_tenant_and_id(&tenant, &link_id) .await .ok() .flatten() diff --git a/server/src/api/links/routes.rs b/server/src/api/links/routes.rs index 2a75848f..8dafe8c9 100644 --- a/server/src/api/links/routes.rs +++ b/server/src/api/links/routes.rs @@ -445,7 +445,7 @@ pub async fn resolve_link_custom( } let Some(link) = repo - .find_link_by_tenant_and_id(domain.tenant_id.as_object_id(), &link_id) + .find_link_by_tenant_and_id(&domain.tenant_id, &link_id) .await .ok() .flatten() diff --git a/server/src/services/analytics/service.rs b/server/src/services/analytics/service.rs index e4778ab8..83071c47 100644 --- a/server/src/services/analytics/service.rs +++ b/server/src/services/analytics/service.rs @@ -59,12 +59,7 @@ impl AnalyticsService { // 2. Clicks — credit-independent. Direct count over click_events. let clicks = self .links_repo - .count_clicks_for_links( - tenant_id.as_object_id(), - ¶ms.link_ids, - params.from, - params.to, - ) + .count_clicks_for_links(tenant_id, ¶ms.link_ids, params.from, params.to) .await .map_err(AnalyticsError::Internal)?; @@ -75,7 +70,7 @@ impl AnalyticsService { let credited_installs = self .links_repo .distinct_install_ids_credited_to_links( - tenant_id.as_object_id(), + tenant_id, ¶ms.link_ids, params.from, params.to, diff --git a/server/src/services/billing/quota_tests.rs b/server/src/services/billing/quota_tests.rs index e3cca586..b1ea7811 100644 --- a/server/src/services/billing/quota_tests.rs +++ b/server/src/services/billing/quota_tests.rs @@ -3,7 +3,6 @@ use crate::core::public_id::TenantId; use crate::services::auth::tenants::repo::{PlanTier, TenantDoc, TenantsRepository}; use crate::services::billing::service::BillingService; use async_trait::async_trait; -use mongodb::bson::oid::ObjectId; use std::sync::Mutex; #[derive(Default)] diff --git a/server/src/services/billing/repos/resource_counts_adapter.rs b/server/src/services/billing/repos/resource_counts_adapter.rs index 2a34df80..f18427f4 100644 --- a/server/src/services/billing/repos/resource_counts_adapter.rs +++ b/server/src/services/billing/repos/resource_counts_adapter.rs @@ -25,9 +25,8 @@ pub struct RepoResourceCounts { #[async_trait] impl ResourceCounts for RepoResourceCounts { async fn count(&self, tenant_id: &TenantId, resource: Resource) -> Result { - let oid = tenant_id.as_object_id(); match resource { - Resource::CreateLink => self.links.count_links_by_tenant(oid).await, + Resource::CreateLink => self.links.count_links_by_tenant(tenant_id).await, Resource::CreateDomain => self.domains.count_by_tenant(tenant_id).await, Resource::InviteTeamMember => self .users diff --git a/server/src/services/billing/service_tests.rs b/server/src/services/billing/service_tests.rs index 5e14f5ef..7b5c0e15 100644 --- a/server/src/services/billing/service_tests.rs +++ b/server/src/services/billing/service_tests.rs @@ -3,7 +3,6 @@ use crate::core::public_id::{SecretKeyId, TenantId}; use crate::services::auth::permissions::AuthContext; use crate::services::auth::secret_keys::repo::KeyScope; use crate::services::auth::tenants::repo::{PlanTier, TenantDoc}; -use mongodb::bson::oid::ObjectId; use mongodb::bson::DateTime; use async_trait::async_trait; diff --git a/server/src/services/conversions/service.rs b/server/src/services/conversions/service.rs index 09f87168..40ffc5c9 100644 --- a/server/src/services/conversions/service.rs +++ b/server/src/services/conversions/service.rs @@ -80,7 +80,6 @@ impl ConversionsService { source_id: SourceId, parsed: Vec, ) -> IngestResult { - let tenant_oid = tenant_id.to_object_id(); let source_oid = source_id.to_object_id(); let mut result = IngestResult::default(); @@ -211,7 +210,7 @@ impl ConversionsService { Some(repo) => { let ids = repo .credited_links_for_user( - &tenant_oid, + &tenant_id, user_id, event.occurred_at.unwrap_or_else(DateTime::now), ) diff --git a/server/src/services/links/repo.rs b/server/src/services/links/repo.rs index e9ee20a3..da3671cd 100644 --- a/server/src/services/links/repo.rs +++ b/server/src/services/links/repo.rs @@ -5,6 +5,7 @@ use mongodb::bson::{self, doc, oid::ObjectId, DateTime}; use mongodb::options::{IndexOptions, TimeseriesGranularity, TimeseriesOptions}; use mongodb::{Collection, Database}; +use crate::core::public_id::TenantId; use crate::ensure_index; use mongodb::bson::Document; @@ -34,33 +35,33 @@ pub trait LinksRepository: Send + Sync { async fn find_link_by_tenant_and_id( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_id: &str, ) -> Result, String>; async fn update_link( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_id: &str, set: Document, unset: Document, ) -> Result; - async fn delete_link(&self, tenant_id: &ObjectId, link_id: &str) -> Result; + async fn delete_link(&self, tenant_id: &TenantId, link_id: &str) -> Result; /// Total active links owned by this tenant — feeds the CreateLink quota. - async fn count_links_by_tenant(&self, tenant_id: &ObjectId) -> Result; + async fn count_links_by_tenant(&self, tenant_id: &TenantId) -> Result; async fn list_links_by_tenant( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, limit: i64, cursor: Option, ) -> Result, String>; async fn record_click( &self, - tenant_id: ObjectId, + tenant_id: TenantId, link_id: &str, user_agent: Option, referer: Option, @@ -75,7 +76,7 @@ pub trait LinksRepository: Send + Sync { /// reads can prune buckets. async fn record_attribute_event( &self, - tenant_id: ObjectId, + tenant_id: TenantId, link_id: &str, install_id: &str, app_version: &str, @@ -95,7 +96,7 @@ pub trait LinksRepository: Send + Sync { /// Returns the number of rows updated. async fn backfill_user_id_on_attribution_events( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, user_id: &str, ) -> Result; @@ -110,7 +111,7 @@ pub trait LinksRepository: Send + Sync { /// Returns an empty set if `link_ids` is empty. async fn distinct_install_ids_credited_to_links( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_ids: &[String], from: DateTime, to: DateTime, @@ -120,7 +121,7 @@ pub trait LinksRepository: Send + Sync { /// Count click events for a set of links in the time range. async fn count_clicks_for_links( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_ids: &[String], from: DateTime, to: DateTime, @@ -139,7 +140,7 @@ pub trait LinksRepository: Send + Sync { /// hasn't done a `/lifecycle/attribute` yet, for example). async fn credited_links_for_user( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_id: &str, at_or_before: DateTime, ) -> Result; @@ -283,18 +284,18 @@ async fn cached_find_link_by_id(links: &Collection, link_id: &str) -> Resu )] async fn cached_find_link_by_tenant_and_id( links: &Collection, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_id: &str, ) -> Result { links - .find_one(doc! { "tenant_id": tenant_id, "link_id": link_id }) + .find_one(doc! { "tenant_id": *tenant_id, "link_id": link_id }) .await .map_err(|e| e.to_string())? .ok_or_else(|| NOT_FOUND.to_string()) } /// Evict a link from both caches after a write (create/update/delete). -async fn invalidate_link_cache(tenant_id: &ObjectId, link_id: &str) { +async fn invalidate_link_cache(tenant_id: &TenantId, link_id: &str) { CACHED_FIND_LINK_BY_ID .lock() .await @@ -313,7 +314,7 @@ impl LinksRepository for LinksRepo { .insert_one(&link) .await .map_err(|e| e.to_string())?; - invalidate_link_cache(link.tenant_id.as_object_id(), &link.link_id).await; + invalidate_link_cache(&link.tenant_id, &link.link_id).await; Ok(link) } @@ -347,7 +348,7 @@ impl LinksRepository for LinksRepo { .await .map_err(|e| BulkInsertError::Internal(e.to_string()))?; for d in &docs { - invalidate_link_cache(d.tenant_id.as_object_id(), &d.link_id).await; + invalidate_link_cache(&d.tenant_id, &d.link_id).await; } Ok(docs) } @@ -373,7 +374,7 @@ impl LinksRepository for LinksRepo { async fn find_link_by_tenant_and_id( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_id: &str, ) -> Result, String> { match cached_find_link_by_tenant_and_id(&self.links, tenant_id, link_id).await { @@ -385,7 +386,7 @@ impl LinksRepository for LinksRepo { async fn update_link( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_id: &str, set: Document, unset: Document, @@ -400,7 +401,7 @@ impl LinksRepository for LinksRepo { let result = self .links .update_one( - doc! { "tenant_id": tenant_id, "link_id": link_id }, + doc! { "tenant_id": *tenant_id, "link_id": link_id }, update_doc, ) .await @@ -411,10 +412,10 @@ impl LinksRepository for LinksRepo { Ok(result.matched_count > 0) } - async fn delete_link(&self, tenant_id: &ObjectId, link_id: &str) -> Result { + async fn delete_link(&self, tenant_id: &TenantId, link_id: &str) -> Result { let result = self .links - .delete_one(doc! { "tenant_id": tenant_id, "link_id": link_id }) + .delete_one(doc! { "tenant_id": *tenant_id, "link_id": link_id }) .await .map_err(|e| e.to_string())?; if result.deleted_count > 0 { @@ -423,20 +424,20 @@ impl LinksRepository for LinksRepo { Ok(result.deleted_count > 0) } - async fn count_links_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_links_by_tenant(&self, tenant_id: &TenantId) -> Result { self.links - .count_documents(doc! { "tenant_id": tenant_id }) + .count_documents(doc! { "tenant_id": *tenant_id }) .await .map_err(|e| e.to_string()) } async fn list_links_by_tenant( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, limit: i64, cursor: Option, ) -> Result, String> { - let mut filter = doc! { "tenant_id": tenant_id }; + let mut filter = doc! { "tenant_id": *tenant_id }; if let Some(cursor_id) = cursor { filter.insert("_id", doc! { "$lt": cursor_id }); } @@ -458,7 +459,7 @@ impl LinksRepository for LinksRepo { async fn record_click( &self, - tenant_id: ObjectId, + tenant_id: TenantId, link_id: &str, user_agent: Option, referer: Option, @@ -467,7 +468,7 @@ impl LinksRepository for LinksRepo { ) -> Result<(), String> { let event = ClickEvent { meta: ClickMeta { - tenant_id: crate::core::public_id::TenantId::from_object_id(tenant_id), + tenant_id, link_id: link_id.to_string(), retention_bucket, }, @@ -485,7 +486,7 @@ impl LinksRepository for LinksRepo { async fn record_attribute_event( &self, - tenant_id: ObjectId, + tenant_id: TenantId, link_id: &str, install_id: &str, app_version: &str, @@ -495,7 +496,7 @@ impl LinksRepository for LinksRepo { let event = AttributionEvent { timestamp: DateTime::now(), meta: AttributionEventMeta { - tenant_id: crate::core::public_id::TenantId::from_object_id(tenant_id), + tenant_id, install_id: install_id.to_string(), retention_bucket, user_id: user_id.map(|s| s.to_string()), @@ -512,7 +513,7 @@ impl LinksRepository for LinksRepo { async fn backfill_user_id_on_attribution_events( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, install_id: &str, user_id: &str, ) -> Result { @@ -527,7 +528,7 @@ impl LinksRepository for LinksRepo { .attribution_events .update_many( doc! { - "meta.tenant_id": tenant_id, + "meta.tenant_id": *tenant_id, "meta.install_id": install_id, }, doc! { "$set": { "meta.user_id": user_id } }, @@ -539,7 +540,7 @@ impl LinksRepository for LinksRepo { async fn distinct_install_ids_credited_to_links( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_ids: &[String], from: DateTime, to: DateTime, @@ -568,7 +569,7 @@ impl LinksRepository for LinksRepo { .distinct( "meta.install_id", doc! { - "meta.tenant_id": tenant_id, + "meta.tenant_id": *tenant_id, "link_id": { "$in": bson_ids }, "timestamp": { "$gte": from, "$lte": to }, }, @@ -593,7 +594,7 @@ impl LinksRepository for LinksRepo { let pipeline = vec![ doc! { "$match": { - "meta.tenant_id": tenant_id, + "meta.tenant_id": *tenant_id, "timestamp": { "$gte": from, "$lte": to }, } }, @@ -626,7 +627,7 @@ impl LinksRepository for LinksRepo { async fn count_clicks_for_links( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_ids: &[String], from: DateTime, to: DateTime, @@ -640,7 +641,7 @@ impl LinksRepository for LinksRepo { .collect(); self.click_events .count_documents(doc! { - "meta.tenant_id": tenant_id, + "meta.tenant_id": *tenant_id, "meta.link_id": { "$in": bson_ids }, "clicked_at": { "$gte": from, "$lte": to }, }) @@ -650,7 +651,7 @@ impl LinksRepository for LinksRepo { async fn credited_links_for_user( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, user_id: &str, at_or_before: DateTime, ) -> Result { @@ -660,7 +661,7 @@ impl LinksRepository for LinksRepo { // event in O(1) hops once the planner picks the right walk // direction. let match_stage = doc! { - "meta.tenant_id": tenant_id, + "meta.tenant_id": *tenant_id, "meta.user_id": user_id, "timestamp": { "$lte": at_or_before }, }; @@ -714,6 +715,7 @@ impl LinksRepository for LinksRepo { } } +#[allow(dead_code)] fn build_link(input: CreateLinkInput) -> Link { Link { id: crate::core::public_id::LinkInternalId::new(), diff --git a/server/src/services/links/service.rs b/server/src/services/links/service.rs index d3e5241d..096a48b8 100644 --- a/server/src/services/links/service.rs +++ b/server/src/services/links/service.rs @@ -87,8 +87,6 @@ impl LinksService { return Ok(IdentifyOutcome::AlreadyPresent); }; - let tenant_oid = tenant_id.as_object_id(); - // 1. Rebind guard. If the install is already bound to a different // user, refuse — option B from the cutover discussion. The // SDK's expected behavior is one install ↔ one user; rebinding @@ -140,7 +138,7 @@ impl LinksService { // doesn't fail the identify. match self .links_repo - .backfill_user_id_on_attribution_events(tenant_oid, install_id, user_id) + .backfill_user_id_on_attribution_events(tenant_id, install_id, user_id) .await { Ok(n) if n > 0 => { @@ -207,7 +205,7 @@ impl LinksService { // the webhook still fires with both fields absent. let credited_ids = self .links_repo - .credited_links_for_user(tenant_oid, user_id, mongodb::bson::DateTime::now()) + .credited_links_for_user(tenant_id, user_id, mongodb::bson::DateTime::now()) .await .unwrap_or_else(|e| { tracing::warn!( @@ -260,7 +258,7 @@ impl LinksService { if let Err(e) = self .links_repo .record_click( - tenant_id.to_object_id(), + tenant_id, link_id, user_agent, referer, @@ -300,8 +298,6 @@ impl LinksService { None => "30d".to_string(), }; - let tenant_oid = tenant_id.to_object_id(); - // Resolve user_id at write time so the row doesn't need to be // backfilled later for already-identified installs. Best-effort — // a lookup failure logs and falls back to None (the next identify @@ -319,7 +315,7 @@ impl LinksService { self.links_repo .record_attribute_event( - tenant_oid, + tenant_id, link_id, install_id, app_version, @@ -359,7 +355,6 @@ impl LinksService { req: CreateLinkRequest, ) -> Result { let tenant_id = ctx.tenant_id; - let tenant_oid = tenant_id.to_object_id(); // Quota enforcement lives here (service layer) so MCP tool invocations // and HTTP route handlers both hit the same choke point. CLAUDE.md // codifies this rule — see "Quota enforcement" section there. @@ -392,7 +387,7 @@ impl LinksService { if self .links_repo - .find_link_by_tenant_and_id(&tenant_oid, custom) + .find_link_by_tenant_and_id(&tenant_id, custom) .await .ok() .flatten() @@ -484,7 +479,6 @@ impl LinksService { req: BulkCreateLinksRequest, ) -> Result { let tenant_id = ctx.tenant_id; - let tenant_oid = tenant_id.to_object_id(); // 1. Mode — exactly one of custom_ids / count. let mode_ids = req.custom_ids.as_deref(); let mode_count = req.count; @@ -592,7 +586,7 @@ impl LinksService { } if self .links_repo - .find_link_by_tenant_and_id(&tenant_oid, id) + .find_link_by_tenant_and_id(&tenant_id, id) .await .map_err(LinkError::Internal)? .is_some() @@ -687,7 +681,7 @@ impl LinksService { ) -> Result { let link = self .links_repo - .find_link_by_tenant_and_id(ctx.tenant_id.as_object_id(), link_id) + .find_link_by_tenant_and_id(&ctx.tenant_id, link_id) .await .map_err(LinkError::Internal)? .ok_or(LinkError::NotFound)?; @@ -722,7 +716,7 @@ impl LinksService { // Fetch one extra to determine if there's a next page. let links = self .links_repo - .list_links_by_tenant(ctx.tenant_id.as_object_id(), limit + 1, cursor_id) + .list_links_by_tenant(&ctx.tenant_id, limit + 1, cursor_id) .await .map_err(|e| { tracing::error!("Failed to list links: {e}"); @@ -843,7 +837,7 @@ impl LinksService { let updated = self .links_repo - .update_link(ctx.tenant_id.as_object_id(), link_id, update, unset) + .update_link(&ctx.tenant_id, link_id, update, unset) .await .map_err(|e| { tracing::error!("Failed to update link: {e}"); @@ -858,7 +852,7 @@ impl LinksService { // the caller's update was already authorized at the macro layer. let link = self .links_repo - .find_link_by_tenant_and_id(ctx.tenant_id.as_object_id(), link_id) + .find_link_by_tenant_and_id(&ctx.tenant_id, link_id) .await .map_err(LinkError::Internal)? .ok_or(LinkError::NotFound)?; @@ -876,7 +870,7 @@ impl LinksService { ) -> Result { let link = self .links_repo - .find_link_by_tenant_and_id(tenant_id.as_object_id(), link_id) + .find_link_by_tenant_and_id(tenant_id, link_id) .await .map_err(LinkError::Internal)? .ok_or(LinkError::NotFound)?; @@ -901,7 +895,7 @@ impl LinksService { pub async fn delete_link(&self, ctx: &AuthContext, link_id: &str) -> Result<(), LinkError> { let deleted = self .links_repo - .delete_link(ctx.tenant_id.as_object_id(), link_id) + .delete_link(&ctx.tenant_id, link_id) .await .map_err(|e| { tracing::error!("Failed to delete link: {e}"); @@ -1011,7 +1005,7 @@ pub async fn enrich_credited_with_metadata( tenant_id: &TenantId, link_id: &str, ) -> Option { - repo.find_link_by_tenant_and_id(tenant_id.as_object_id(), link_id) + repo.find_link_by_tenant_and_id(tenant_id, link_id) .await .unwrap_or_else(|e| { tracing::warn!(error = %e, link_id, "credited link metadata lookup failed"); diff --git a/server/src/services/links/service_tests.rs b/server/src/services/links/service_tests.rs index 6f7b064d..db58af01 100644 --- a/server/src/services/links/service_tests.rs +++ b/server/src/services/links/service_tests.rs @@ -168,19 +168,19 @@ impl LinksRepository for MockLinksRepo { async fn find_link_by_tenant_and_id( &self, - tenant_id: &ObjectId, + tenant_id: &crate::core::public_id::TenantId, link_id: &str, ) -> Result, String> { let links = self.links.lock().unwrap(); Ok(links .iter() - .find(|l| l.tenant_id.to_object_id() == *tenant_id && l.link_id == link_id) + .find(|l| l.tenant_id == *tenant_id && l.link_id == link_id) .cloned()) } async fn update_link( &self, - tenant_id: &ObjectId, + tenant_id: &crate::core::public_id::TenantId, link_id: &str, update: Document, _unset: Document, @@ -188,7 +188,7 @@ impl LinksRepository for MockLinksRepo { let mut links = self.links.lock().unwrap(); let Some(link) = links .iter_mut() - .find(|l| l.tenant_id.to_object_id() == *tenant_id && l.link_id == link_id) + .find(|l| l.tenant_id == *tenant_id && l.link_id == link_id) else { return Ok(false); }; @@ -219,31 +219,35 @@ impl LinksRepository for MockLinksRepo { Ok(true) } - async fn delete_link(&self, tenant_id: &ObjectId, link_id: &str) -> Result { + async fn delete_link( + &self, + tenant_id: &crate::core::public_id::TenantId, + link_id: &str, + ) -> Result { let mut links = self.links.lock().unwrap(); let len_before = links.len(); - links.retain(|l| !(l.tenant_id.to_object_id() == *tenant_id && l.link_id == link_id)); + links.retain(|l| !(l.tenant_id == *tenant_id && l.link_id == link_id)); Ok(links.len() < len_before) } - async fn count_links_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_links_by_tenant( + &self, + tenant_id: &crate::core::public_id::TenantId, + ) -> Result { let links = self.links.lock().unwrap(); - Ok(links - .iter() - .filter(|l| l.tenant_id.to_object_id() == *tenant_id) - .count() as u64) + Ok(links.iter().filter(|l| l.tenant_id == *tenant_id).count() as u64) } async fn list_links_by_tenant( &self, - tenant_id: &ObjectId, + tenant_id: &crate::core::public_id::TenantId, limit: i64, _cursor: Option, ) -> Result, String> { let links = self.links.lock().unwrap(); Ok(links .iter() - .filter(|l| l.tenant_id.to_object_id() == *tenant_id) + .filter(|l| l.tenant_id == *tenant_id) .take(limit as usize) .cloned() .collect()) @@ -251,7 +255,7 @@ impl LinksRepository for MockLinksRepo { async fn record_click( &self, - _tenant_id: ObjectId, + _tenant_id: crate::core::public_id::TenantId, _link_id: &str, _user_agent: Option, _referer: Option, @@ -263,7 +267,7 @@ impl LinksRepository for MockLinksRepo { async fn record_attribute_event( &self, - _tenant_id: ObjectId, + _tenant_id: crate::core::public_id::TenantId, _link_id: &str, _install_id: &str, _app_version: &str, @@ -275,7 +279,7 @@ impl LinksRepository for MockLinksRepo { async fn backfill_user_id_on_attribution_events( &self, - _tenant_id: &ObjectId, + _tenant_id: &crate::core::public_id::TenantId, _install_id: &str, _user_id: &str, ) -> Result { @@ -284,7 +288,7 @@ impl LinksRepository for MockLinksRepo { async fn distinct_install_ids_credited_to_links( &self, - _tenant_id: &ObjectId, + _tenant_id: &crate::core::public_id::TenantId, _link_ids: &[String], _from: DateTime, _to: DateTime, @@ -295,7 +299,7 @@ impl LinksRepository for MockLinksRepo { async fn count_clicks_for_links( &self, - _tenant_id: &ObjectId, + _tenant_id: &crate::core::public_id::TenantId, _link_ids: &[String], _from: DateTime, _to: DateTime, @@ -305,7 +309,7 @@ impl LinksRepository for MockLinksRepo { async fn credited_links_for_user( &self, - _tenant_id: &ObjectId, + _tenant_id: &crate::core::public_id::TenantId, _user_id: &str, _at_or_before: DateTime, ) -> Result { diff --git a/server/tests/common/mocks/links.rs b/server/tests/common/mocks/links.rs index 875b025c..2cc766b0 100644 --- a/server/tests/common/mocks/links.rs +++ b/server/tests/common/mocks/links.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; use mongodb::bson::{oid::ObjectId, DateTime, Document}; +use rift::core::public_id::TenantId; use std::sync::Mutex; use rift::services::links::models::BulkInsertError; @@ -110,7 +111,7 @@ impl LinksRepository for MockLinksRepo { async fn find_link_by_tenant_and_id( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_id: &str, ) -> Result, String> { Ok(self @@ -118,13 +119,13 @@ impl LinksRepository for MockLinksRepo { .lock() .unwrap() .iter() - .find(|l| l.tenant_id.to_object_id() == *tenant_id && l.link_id == link_id) + .find(|l| l.tenant_id == *tenant_id && l.link_id == link_id) .cloned()) } async fn update_link( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, link_id: &str, set: Document, unset: Document, @@ -132,7 +133,7 @@ impl LinksRepository for MockLinksRepo { let mut links = self.links.lock().unwrap(); let Some(link) = links .iter_mut() - .find(|l| l.tenant_id.to_object_id() == *tenant_id && l.link_id == link_id) + .find(|l| l.tenant_id == *tenant_id && l.link_id == link_id) else { return Ok(false); }; @@ -172,34 +173,28 @@ impl LinksRepository for MockLinksRepo { Ok(true) } - async fn delete_link(&self, tenant_id: &ObjectId, link_id: &str) -> Result { + async fn delete_link(&self, tenant_id: &TenantId, link_id: &str) -> Result { let mut links = self.links.lock().unwrap(); let len_before = links.len(); - links.retain(|l| !(l.tenant_id.to_object_id() == *tenant_id && l.link_id == link_id)); + links.retain(|l| !(l.tenant_id == *tenant_id && l.link_id == link_id)); Ok(links.len() < len_before) } - async fn count_links_by_tenant(&self, tenant_id: &ObjectId) -> Result { + async fn count_links_by_tenant(&self, tenant_id: &TenantId) -> Result { let links = self.links.lock().unwrap(); - Ok(links - .iter() - .filter(|l| l.tenant_id.to_object_id() == *tenant_id) - .count() as u64) + Ok(links.iter().filter(|l| l.tenant_id == *tenant_id).count() as u64) } async fn list_links_by_tenant( &self, - tenant_id: &ObjectId, + tenant_id: &TenantId, limit: i64, cursor: Option, ) -> Result, String> { let links = self.links.lock().unwrap(); let mut filtered: Vec = links .iter() - .filter(|l| { - l.tenant_id.to_object_id() == *tenant_id - && cursor.is_none_or(|c| l.id.to_object_id() < c) - }) + .filter(|l| l.tenant_id == *tenant_id && cursor.is_none_or(|c| l.id.to_object_id() < c)) .cloned() .collect(); // Sort by _id descending (ObjectIds are monotonically increasing). @@ -210,7 +205,7 @@ impl LinksRepository for MockLinksRepo { async fn record_click( &self, - tenant_id: ObjectId, + tenant_id: TenantId, link_id: &str, user_agent: Option, referer: Option, @@ -219,7 +214,7 @@ impl LinksRepository for MockLinksRepo { ) -> Result<(), String> { self.clicks.lock().unwrap().push(ClickEvent { meta: ClickMeta { - tenant_id: rift::core::public_id::TenantId::from_object_id(tenant_id), + tenant_id, link_id: link_id.to_string(), retention_bucket, }, @@ -233,7 +228,7 @@ impl LinksRepository for MockLinksRepo { async fn record_attribute_event( &self, - _tenant_id: ObjectId, + _tenant_id: TenantId, _link_id: &str, _install_id: &str, _app_version: &str, @@ -245,7 +240,7 @@ impl LinksRepository for MockLinksRepo { async fn backfill_user_id_on_attribution_events( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _install_id: &str, _user_id: &str, ) -> Result { @@ -254,7 +249,7 @@ impl LinksRepository for MockLinksRepo { async fn distinct_install_ids_credited_to_links( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _link_ids: &[String], _from: DateTime, _to: DateTime, @@ -265,7 +260,7 @@ impl LinksRepository for MockLinksRepo { async fn count_clicks_for_links( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _link_ids: &[String], _from: DateTime, _to: DateTime, @@ -275,7 +270,7 @@ impl LinksRepository for MockLinksRepo { async fn credited_links_for_user( &self, - _tenant_id: &ObjectId, + _tenant_id: &TenantId, _user_id: &str, _at_or_before: DateTime, ) -> Result { From 818152eeac65098667bd8739a652b436cd378d42 Mon Sep 17 00:00:00 2001 From: drei Date: Fri, 29 May 2026 13:32:02 -0500 Subject: [PATCH 10/10] Drop redundant `*` derefs in doc! macros (bson handles &T via blanket impl) --- server/src/services/app_users/repo.rs | 8 ++++---- server/src/services/apps/repo.rs | 6 +++--- .../services/auth/publishable_keys/repo.rs | 4 ++-- server/src/services/auth/secret_keys/repo.rs | 16 +++++++-------- server/src/services/auth/sessions/repo.rs | 4 ++-- server/src/services/auth/tenants/repo.rs | 6 +++--- server/src/services/auth/users/repo.rs | 8 ++++---- server/src/services/conversions/repo.rs | 10 +++++----- server/src/services/domains/repo.rs | 8 ++++---- server/src/services/install_events/repo.rs | 6 +++--- server/src/services/links/repo.rs | 20 +++++++++---------- server/src/services/webhooks/repo.rs | 12 +++++------ 12 files changed, 54 insertions(+), 54 deletions(-) diff --git a/server/src/services/app_users/repo.rs b/server/src/services/app_users/repo.rs index 37b00351..324fa11c 100644 --- a/server/src/services/app_users/repo.rs +++ b/server/src/services/app_users/repo.rs @@ -86,11 +86,11 @@ impl AppUsersRepository for AppUsersRepo { let before = self .app_users .find_one_and_update( - doc! { "tenant_id": *tenant_id, "user_id": user_id }, + doc! { "tenant_id": tenant_id, "user_id": user_id }, doc! { "$setOnInsert": { "_id": ObjectId::new(), - "tenant_id": *tenant_id, + "tenant_id": tenant_id, "user_id": user_id, "identified_at": now, }, @@ -118,7 +118,7 @@ impl AppUsersRepository for AppUsersRepo { user_id: &str, ) -> Result, String> { self.app_users - .find_one(doc! { "tenant_id": *tenant_id, "user_id": user_id }) + .find_one(doc! { "tenant_id": tenant_id, "user_id": user_id }) .await .map_err(|e| e.to_string()) } @@ -130,7 +130,7 @@ impl AppUsersRepository for AppUsersRepo { ) -> Result, String> { let doc = self .app_users - .find_one(doc! { "tenant_id": *tenant_id, "install_ids": install_id }) + .find_one(doc! { "tenant_id": tenant_id, "install_ids": install_id }) .await .map_err(|e| e.to_string())?; Ok(doc.map(|d| d.user_id)) diff --git a/server/src/services/apps/repo.rs b/server/src/services/apps/repo.rs index 224d4df5..334e7e0b 100644 --- a/server/src/services/apps/repo.rs +++ b/server/src/services/apps/repo.rs @@ -82,7 +82,7 @@ impl AppsRepository for AppsRepo { async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .apps - .find(doc! { "tenant_id": *tenant_id }) + .find(doc! { "tenant_id": tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -100,7 +100,7 @@ impl AppsRepository for AppsRepo { platform: &str, ) -> Result, String> { self.apps - .find_one(doc! { "tenant_id": *tenant_id, "platform": platform }) + .find_one(doc! { "tenant_id": tenant_id, "platform": platform }) .await .map_err(|e| e.to_string()) } @@ -108,7 +108,7 @@ impl AppsRepository for AppsRepo { async fn delete_app(&self, tenant_id: &TenantId, app_id: &AppId) -> Result { let result = self .apps - .delete_one(doc! { "_id": *app_id, "tenant_id": *tenant_id }) + .delete_one(doc! { "_id": app_id, "tenant_id": tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) diff --git a/server/src/services/auth/publishable_keys/repo.rs b/server/src/services/auth/publishable_keys/repo.rs index 1da597d5..4a902019 100644 --- a/server/src/services/auth/publishable_keys/repo.rs +++ b/server/src/services/auth/publishable_keys/repo.rs @@ -63,7 +63,7 @@ impl SdkKeysRepository for SdkKeysRepo { async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .keys - .find(doc! { "tenant_id": *tenant_id, "revoked": false }) + .find(doc! { "tenant_id": tenant_id, "revoked": false }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -83,7 +83,7 @@ impl SdkKeysRepository for SdkKeysRepo { let result = self .keys .update_one( - doc! { "_id": *key_id, "tenant_id": *tenant_id }, + doc! { "_id": key_id, "tenant_id": tenant_id }, doc! { "$set": { "revoked": true } }, ) .await diff --git a/server/src/services/auth/secret_keys/repo.rs b/server/src/services/auth/secret_keys/repo.rs index 219f8866..42105419 100644 --- a/server/src/services/auth/secret_keys/repo.rs +++ b/server/src/services/auth/secret_keys/repo.rs @@ -88,7 +88,7 @@ impl SecretKeysRepository for SecretKeysRepo { async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .keys - .find(doc! { "tenant_id": *tenant_id }) + .find(doc! { "tenant_id": tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -102,7 +102,7 @@ impl SecretKeysRepository for SecretKeysRepo { async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { self.keys - .count_documents(doc! { "tenant_id": *tenant_id }) + .count_documents(doc! { "tenant_id": tenant_id }) .await .map(|c| c as i64) .map_err(|e| e.to_string()) @@ -111,7 +111,7 @@ impl SecretKeysRepository for SecretKeysRepo { async fn delete_key(&self, tenant_id: &TenantId, key_id: &SecretKeyId) -> Result { let result = self .keys - .delete_one(doc! { "_id": *key_id, "tenant_id": *tenant_id }) + .delete_one(doc! { "_id": key_id, "tenant_id": tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) @@ -127,9 +127,9 @@ impl SecretKeysRepository for SecretKeysRepo { let mut cursor = self .keys .find(doc! { - "tenant_id": *tenant_id, + "tenant_id": tenant_id, "scope.type": "affiliate", - "scope.affiliate_id": *affiliate_id, + "scope.affiliate_id": affiliate_id, }) .sort(doc! { "created_at": -1 }) .await @@ -151,10 +151,10 @@ impl SecretKeysRepository for SecretKeysRepo { let result = self .keys .delete_one(doc! { - "_id": *key_id, - "tenant_id": *tenant_id, + "_id": key_id, + "tenant_id": tenant_id, "scope.type": "affiliate", - "scope.affiliate_id": *affiliate_id, + "scope.affiliate_id": affiliate_id, }) .await .map_err(|e| e.to_string())?; diff --git a/server/src/services/auth/sessions/repo.rs b/server/src/services/auth/sessions/repo.rs index 92c5272f..2f4421d0 100644 --- a/server/src/services/auth/sessions/repo.rs +++ b/server/src/services/auth/sessions/repo.rs @@ -92,7 +92,7 @@ impl SessionsRepository for SessionsRepoMongo { let result = self .col .update_one( - doc! { "_id": *session_id, "revoked_at": null }, + doc! { "_id": session_id, "revoked_at": null }, doc! { "$set": { "revoked_at": now } }, ) .await @@ -104,7 +104,7 @@ impl SessionsRepository for SessionsRepoMongo { let now = bson::DateTime::now(); self.col .update_one( - doc! { "_id": *session_id }, + doc! { "_id": session_id }, doc! { "$set": { "last_seen_at": now } }, ) .await diff --git a/server/src/services/auth/tenants/repo.rs b/server/src/services/auth/tenants/repo.rs index c6798223..e1bd9eee 100644 --- a/server/src/services/auth/tenants/repo.rs +++ b/server/src/services/auth/tenants/repo.rs @@ -66,7 +66,7 @@ impl TenantsRepository for TenantsRepo { async fn find_by_id(&self, id: &TenantId) -> Result, String> { self.tenants - .find_one(doc! { "_id": *id }) + .find_one(doc! { "_id": id }) .await .map_err(|e| e.to_string()) } @@ -135,7 +135,7 @@ impl TenantsRepository for TenantsRepo { } let result = self .tenants - .update_one(doc! { "_id": *tenant_id }, doc! { "$set": set_doc }) + .update_one(doc! { "_id": tenant_id }, doc! { "$set": set_doc }) .await .map_err(|e| e.to_string())?; Ok(result.matched_count == 1) @@ -156,7 +156,7 @@ impl TenantsRepository for TenantsRepo { }; let result = self .tenants - .update_one(doc! { "_id": *tenant_id }, update) + .update_one(doc! { "_id": tenant_id }, update) .await .map_err(|e| e.to_string())?; Ok(result.matched_count == 1) diff --git a/server/src/services/auth/users/repo.rs b/server/src/services/auth/users/repo.rs index adcc522f..a1ee6243 100644 --- a/server/src/services/auth/users/repo.rs +++ b/server/src/services/auth/users/repo.rs @@ -74,7 +74,7 @@ impl UsersRepository for UsersRepo { email: &str, ) -> Result, String> { self.users - .find_one(doc! { "tenant_id": *tenant_id, "email": email }) + .find_one(doc! { "tenant_id": tenant_id, "email": email }) .await .map_err(|e| e.to_string()) } @@ -82,7 +82,7 @@ impl UsersRepository for UsersRepo { async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .users - .find(doc! { "tenant_id": *tenant_id }) + .find(doc! { "tenant_id": tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -96,7 +96,7 @@ impl UsersRepository for UsersRepo { async fn count_verified_by_tenant(&self, tenant_id: &TenantId) -> Result { self.users - .count_documents(doc! { "tenant_id": *tenant_id, "verified": true }) + .count_documents(doc! { "tenant_id": tenant_id, "verified": true }) .await .map(|c| c as i64) .map_err(|e| e.to_string()) @@ -105,7 +105,7 @@ impl UsersRepository for UsersRepo { async fn delete(&self, tenant_id: &TenantId, user_id: &UserId) -> Result { let result = self .users - .delete_one(doc! { "_id": *user_id, "tenant_id": *tenant_id }) + .delete_one(doc! { "_id": user_id, "tenant_id": tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) diff --git a/server/src/services/conversions/repo.rs b/server/src/services/conversions/repo.rs index c040053a..76b4b7e9 100644 --- a/server/src/services/conversions/repo.rs +++ b/server/src/services/conversions/repo.rs @@ -233,7 +233,7 @@ impl ConversionsRepository for ConversionsRepo { id: &SourceId, ) -> Result, String> { self.sources - .find_one(doc! { "_id": *id, "tenant_id": *tenant_id }) + .find_one(doc! { "_id": id, "tenant_id": tenant_id }) .await .map_err(|e| e.to_string()) } @@ -241,7 +241,7 @@ impl ConversionsRepository for ConversionsRepo { async fn list_sources(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .sources - .find(doc! { "tenant_id": *tenant_id }) + .find(doc! { "tenant_id": tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -256,7 +256,7 @@ impl ConversionsRepository for ConversionsRepo { async fn delete_source(&self, tenant_id: &TenantId, id: &SourceId) -> Result { let result = self .sources - .delete_one(doc! { "_id": *id, "tenant_id": *tenant_id }) + .delete_one(doc! { "_id": id, "tenant_id": tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) @@ -340,7 +340,7 @@ impl ConversionsRepository for ConversionsRepo { let pipeline = vec![ doc! { "$match": { - "meta.tenant_id": *tenant_id, + "meta.tenant_id": tenant_id, "user_id": { "$in": bson_ids }, "occurred_at": { "$gte": from, "$lte": to }, } @@ -454,7 +454,7 @@ impl ConversionsRepository for ConversionsRepo { let pipeline = vec![ doc! { "$match": { - "meta.tenant_id": *tenant_id, + "meta.tenant_id": tenant_id, "occurred_at": { "$gte": from, "$lte": to }, "user_id": { "$ne": null }, } diff --git a/server/src/services/domains/repo.rs b/server/src/services/domains/repo.rs index 5ede977c..cf9b76f5 100644 --- a/server/src/services/domains/repo.rs +++ b/server/src/services/domains/repo.rs @@ -95,7 +95,7 @@ impl DomainsRepository for DomainsRepo { async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { self.domains - .count_documents(doc! { "tenant_id": *tenant_id }) + .count_documents(doc! { "tenant_id": tenant_id }) .await .map_err(|e| e.to_string()) } @@ -103,7 +103,7 @@ impl DomainsRepository for DomainsRepo { async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .domains - .find(doc! { "tenant_id": *tenant_id }) + .find(doc! { "tenant_id": tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -118,7 +118,7 @@ impl DomainsRepository for DomainsRepo { async fn delete_domain(&self, tenant_id: &TenantId, domain: &str) -> Result { let result = self .domains - .delete_one(doc! { "tenant_id": *tenant_id, "domain": domain }) + .delete_one(doc! { "tenant_id": tenant_id, "domain": domain }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) @@ -140,7 +140,7 @@ impl DomainsRepository for DomainsRepo { tenant_id: &TenantId, ) -> Result, String> { self.domains - .find_one(doc! { "tenant_id": *tenant_id, "role": "alternate", "verified": true }) + .find_one(doc! { "tenant_id": tenant_id, "role": "alternate", "verified": true }) .await .map_err(|e| e.to_string()) } diff --git a/server/src/services/install_events/repo.rs b/server/src/services/install_events/repo.rs index b9656a9a..a23408c1 100644 --- a/server/src/services/install_events/repo.rs +++ b/server/src/services/install_events/repo.rs @@ -103,7 +103,7 @@ impl InstallEventsRepository for InstallEventsRepo { let existing = self .events .find_one(doc! { - "tenant_id": *tenant_id, + "tenant_id": tenant_id, "install_id": install_id, "event_type": "created", }) @@ -216,7 +216,7 @@ impl InstallEventsRepository for InstallEventsRepo { let event = self .events .find_one(doc! { - "tenant_id": *tenant_id, + "tenant_id": tenant_id, "install_id": install_id, "event_type": "created", }) @@ -249,7 +249,7 @@ impl InstallEventsRepository for InstallEventsRepo { }; self.events .count_documents(doc! { - "tenant_id": *tenant_id, + "tenant_id": tenant_id, "event_type": type_str, "install_id": { "$in": bson_ids }, "timestamp": { "$gte": from, "$lte": to }, diff --git a/server/src/services/links/repo.rs b/server/src/services/links/repo.rs index da3671cd..92a3cfca 100644 --- a/server/src/services/links/repo.rs +++ b/server/src/services/links/repo.rs @@ -288,7 +288,7 @@ async fn cached_find_link_by_tenant_and_id( link_id: &str, ) -> Result { links - .find_one(doc! { "tenant_id": *tenant_id, "link_id": link_id }) + .find_one(doc! { "tenant_id": tenant_id, "link_id": link_id }) .await .map_err(|e| e.to_string())? .ok_or_else(|| NOT_FOUND.to_string()) @@ -401,7 +401,7 @@ impl LinksRepository for LinksRepo { let result = self .links .update_one( - doc! { "tenant_id": *tenant_id, "link_id": link_id }, + doc! { "tenant_id": tenant_id, "link_id": link_id }, update_doc, ) .await @@ -415,7 +415,7 @@ impl LinksRepository for LinksRepo { async fn delete_link(&self, tenant_id: &TenantId, link_id: &str) -> Result { let result = self .links - .delete_one(doc! { "tenant_id": *tenant_id, "link_id": link_id }) + .delete_one(doc! { "tenant_id": tenant_id, "link_id": link_id }) .await .map_err(|e| e.to_string())?; if result.deleted_count > 0 { @@ -426,7 +426,7 @@ impl LinksRepository for LinksRepo { async fn count_links_by_tenant(&self, tenant_id: &TenantId) -> Result { self.links - .count_documents(doc! { "tenant_id": *tenant_id }) + .count_documents(doc! { "tenant_id": tenant_id }) .await .map_err(|e| e.to_string()) } @@ -437,7 +437,7 @@ impl LinksRepository for LinksRepo { limit: i64, cursor: Option, ) -> Result, String> { - let mut filter = doc! { "tenant_id": *tenant_id }; + let mut filter = doc! { "tenant_id": tenant_id }; if let Some(cursor_id) = cursor { filter.insert("_id", doc! { "$lt": cursor_id }); } @@ -528,7 +528,7 @@ impl LinksRepository for LinksRepo { .attribution_events .update_many( doc! { - "meta.tenant_id": *tenant_id, + "meta.tenant_id": tenant_id, "meta.install_id": install_id, }, doc! { "$set": { "meta.user_id": user_id } }, @@ -569,7 +569,7 @@ impl LinksRepository for LinksRepo { .distinct( "meta.install_id", doc! { - "meta.tenant_id": *tenant_id, + "meta.tenant_id": tenant_id, "link_id": { "$in": bson_ids }, "timestamp": { "$gte": from, "$lte": to }, }, @@ -594,7 +594,7 @@ impl LinksRepository for LinksRepo { let pipeline = vec![ doc! { "$match": { - "meta.tenant_id": *tenant_id, + "meta.tenant_id": tenant_id, "timestamp": { "$gte": from, "$lte": to }, } }, @@ -641,7 +641,7 @@ impl LinksRepository for LinksRepo { .collect(); self.click_events .count_documents(doc! { - "meta.tenant_id": *tenant_id, + "meta.tenant_id": tenant_id, "meta.link_id": { "$in": bson_ids }, "clicked_at": { "$gte": from, "$lte": to }, }) @@ -661,7 +661,7 @@ impl LinksRepository for LinksRepo { // event in O(1) hops once the planner picks the right walk // direction. let match_stage = doc! { - "meta.tenant_id": *tenant_id, + "meta.tenant_id": tenant_id, "meta.user_id": user_id, "timestamp": { "$lte": at_or_before }, }; diff --git a/server/src/services/webhooks/repo.rs b/server/src/services/webhooks/repo.rs index 9962a6d8..13de5a5f 100644 --- a/server/src/services/webhooks/repo.rs +++ b/server/src/services/webhooks/repo.rs @@ -71,7 +71,7 @@ impl WebhooksRepository for WebhooksRepo { async fn list_by_tenant(&self, tenant_id: &TenantId) -> Result, String> { let mut cursor = self .webhooks - .find(doc! { "tenant_id": *tenant_id }) + .find(doc! { "tenant_id": tenant_id }) .sort(doc! { "created_at": -1 }) .await .map_err(|e| e.to_string())?; @@ -85,7 +85,7 @@ impl WebhooksRepository for WebhooksRepo { async fn count_by_tenant(&self, tenant_id: &TenantId) -> Result { self.webhooks - .count_documents(doc! { "tenant_id": *tenant_id }) + .count_documents(doc! { "tenant_id": tenant_id }) .await .map_err(|e| e.to_string()) } @@ -97,7 +97,7 @@ impl WebhooksRepository for WebhooksRepo { ) -> Result { let result = self .webhooks - .delete_one(doc! { "_id": *webhook_id, "tenant_id": *tenant_id }) + .delete_one(doc! { "_id": webhook_id, "tenant_id": tenant_id }) .await .map_err(|e| e.to_string())?; Ok(result.deleted_count > 0) @@ -128,7 +128,7 @@ impl WebhooksRepository for WebhooksRepo { if set_doc.is_empty() { let exists = self .webhooks - .find_one(doc! { "_id": *webhook_id, "tenant_id": *tenant_id }) + .find_one(doc! { "_id": webhook_id, "tenant_id": tenant_id }) .await .map_err(|e| e.to_string())? .is_some(); @@ -137,7 +137,7 @@ impl WebhooksRepository for WebhooksRepo { let result = self .webhooks .update_one( - doc! { "_id": *webhook_id, "tenant_id": *tenant_id }, + doc! { "_id": webhook_id, "tenant_id": tenant_id }, doc! { "$set": set_doc }, ) .await @@ -159,7 +159,7 @@ impl WebhooksRepository for WebhooksRepo { let mut cursor = self .webhooks .find(doc! { - "tenant_id": *tenant_id, + "tenant_id": tenant_id, "active": true, "events": &event_str, })