use axum::{async_trait, extract::FromRequestParts, http::request::Parts}; use diesel::{pg::PgConnection, prelude::*, sql_types::Text}; use uuid::Uuid; use crate::{ db::PgPool, error::{AppError, AppResult}, models::Tenant, schema::tenants::dsl, state::AppState, }; pub struct TenantRepository; impl TenantRepository { pub fn get_by_id(conn: &mut PgConnection, tenant_id: Uuid) -> AppResult { dsl::tenants.find(tenant_id).first(conn).map_err(Into::into) } pub fn get_by_slug(conn: &mut PgConnection, slug: &str) -> AppResult { dsl::tenants .filter(dsl::slug.eq(slug)) .first(conn) .map_err(Into::into) } } #[derive(Clone)] pub struct TenantService { pool: PgPool, } impl TenantService { pub fn new(pool: PgPool) -> Self { Self { pool } } pub fn get_by_id(&self, tenant_id: Uuid) -> AppResult { let tenant = self.load(|conn| TenantRepository::get_by_id(conn, tenant_id))?; Ok(tenant) } pub fn get_by_slug(&self, slug: &str) -> AppResult { let slug_owned = slug.to_owned(); let tenant = self.load(|conn| TenantRepository::get_by_slug(conn, &slug_owned))?; Ok(tenant) } pub fn tenant_id_for_slug(&self, slug: &str) -> AppResult { Ok(self.get_by_slug(slug)?.id) } fn load(&self, loader: F) -> AppResult where F: FnOnce(&mut PgConnection) -> AppResult, { let mut conn = self .pool .get() .map_err(|err| AppError::internal(format!("database pool error: {err}")))?; let tenant = loader(&mut conn)?; Ok(tenant) } } pub fn apply_tenant_guc(conn: &mut PgConnection, tenant_id: Uuid) -> AppResult<()> { diesel::sql_query("SELECT set_config('papercrate.tenant_id', $1, true)") .bind::(tenant_id.to_string()) .execute(conn) .map(|_| ()) .map_err(AppError::from) } pub struct TenantContext { pub tenant: Tenant, } #[async_trait] impl FromRequestParts for TenantContext { type Rejection = AppError; async fn from_request_parts( _parts: &mut Parts, state: &AppState, ) -> Result { let tenant = state .tenants .get_by_slug(&state.config.default_tenant_slug)?; Ok(Self { tenant }) } }