haste_server/auth_n/tenant/routes/
branding.rs1use crate::{services::ServerState, static_assets::StaticFile, tenants::read_tenant};
2use axum::{
3 body::Body,
4 extract::{Path, State},
5 http::{HeaderValue, header::CONTENT_TYPE},
6 response::{IntoResponse, Response},
7};
8use haste_fhir_operation_error::OperationOutcomeError;
9use haste_fhir_search::SearchEngine;
10use haste_fhir_terminology::FHIRTerminology;
11use haste_jwt::TenantId;
12use haste_repository::Repository;
13use std::sync::Arc;
14
15pub async fn logo<
16 Repo: Repository + Send + Sync + 'static,
17 Search: SearchEngine + Send + Sync + 'static,
18 Terminology: FHIRTerminology + Send + Sync + 'static,
19>(
20 Path(tenant_id): Path<TenantId>,
21 State(state): State<Arc<ServerState<Repo, Search, Terminology>>>,
22) -> Result<Response, OperationOutcomeError> {
23 let tenant = read_tenant(&state, &tenant_id).await?;
24 let Some(data) = tenant.logo_data.filter(|data| !data.is_empty()) else {
25 return Ok(StaticFile("img/logo.svg".to_string()).into_response());
26 };
27
28 let content_type = tenant
29 .logo_content_type
30 .as_deref()
31 .filter(|content_type| content_type.starts_with("image/"))
32 .ok_or_else(|| {
33 OperationOutcomeError::error(
34 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
35 "Tenant logo has an invalid image content type.".to_string(),
36 )
37 })?;
38
39 let mut response = Response::new(Body::from(data));
40 response.headers_mut().insert(
41 CONTENT_TYPE,
42 HeaderValue::from_str(content_type).map_err(|_| {
43 OperationOutcomeError::error(
44 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
45 "Tenant logo has an invalid content type header.".to_string(),
46 )
47 })?,
48 );
49 Ok(response)
50}