use crate::api::error::ApiError; use crate::api::state::AppState; /// Share-rule levels for a shared address book. Wire integers match the /// Flutter client's `ShareRule` enum at flutter/lib/common/hbbs/hbbs.dart:210. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Rule { Read = 1, ReadWrite = 2, Full = 3, } impl Rule { pub fn from_i64(v: i64) -> Option { match v { 1 => Some(Rule::Read), 2 => Some(Rule::ReadWrite), 3 => Some(Rule::Full), _ => None, } } } /// Enforce that `caller` has at least `needed` access on `ab_guid`. Used at /// the top of every AB handler. Resolution lives in /// `Database::ab_resolve_rule` and considers (a) AB ownership and (b) the /// largest matching rule across direct and device-group shares. pub async fn enforce( state: &AppState, caller_user_id: i64, ab_guid: &str, needed: Rule, ) -> Result<(), ApiError> { let resolved = state .db .ab_resolve_rule(caller_user_id, ab_guid) .await .map_err(|e| ApiError::Internal(e.to_string()))?; let Some(have) = resolved.and_then(Rule::from_i64) else { // Either the AB doesn't exist or the caller has no relationship with // it. Surface as "not allowed" so we don't leak existence. return Err(ApiError::Forbidden("not allowed".into())); }; if have >= needed { Ok(()) } else { Err(ApiError::Forbidden("read-only".into())) } }