use crate::dtos::MyceliumProfileData;
use crate::models::active_backend_modules::SqlAppModule;
use actix_web::{get, web, HttpResponse, Responder};
use myc_core::{
domain::{
dtos::{
account_type::AccountType,
related_accounts::RelatedAccounts,
resource_audit_log::{ResourceAuditLog, ResourceAuditResourceType},
},
entities::AccountFetching,
},
use_cases::shared::audit::fetch_resource_audit_trail,
};
use myc_http_tools::{
utils::HttpJsonResponse,
wrappers::default_response_to_http_response::{
fetch_many_response_kind, handle_mapped_error,
},
};
use mycelium_base::entities::FetchResponseKind;
use serde::Deserialize;
use shaku::HasComponent;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;
pub fn configure(config: &mut web::ServiceConfig) {
config.service(fetch_resource_audit_trail_url);
}
#[derive(Deserialize, ToSchema, IntoParams)]
#[serde(rename_all = "camelCase")]
pub struct FetchResourceAuditTrailParams {
resource_type: ResourceAuditResourceType,
resource_id: Uuid,
}
#[utoipa::path(
get,
operation_id = "fetch_resource_audit_trail",
params(FetchResourceAuditTrailParams),
responses(
(
status = 500,
description = "Unknown internal server error.",
body = HttpJsonResponse,
),
(
status = 403,
description = "Forbidden.",
body = HttpJsonResponse,
),
(
status = 401,
description = "Unauthorized.",
body = HttpJsonResponse,
),
(
status = 400,
description = "Invalid resource reference.",
body = HttpJsonResponse,
),
(
status = 204,
description = "No audit rows for this resource.",
),
(
status = 200,
description = "Audit trail fetched, newest first.",
body = [ResourceAuditLog],
),
),
)]
#[get("")]
pub async fn fetch_resource_audit_trail_url(
query: web::Query<FetchResourceAuditTrailParams>,
profile: MyceliumProfileData,
app_module: web::Data<SqlAppModule>,
) -> impl Responder {
let (tenant_id, resource_owner_account_id) = match resolve_audit_context(
&query.resource_type,
query.resource_id,
&app_module,
)
.await
{
Ok(context) => context,
Err(response) => return response,
};
match fetch_resource_audit_trail(
profile.to_profile(),
query.resource_type.to_owned(),
query.resource_id,
tenant_id,
resource_owner_account_id,
Box::new(&*app_module.resolve_ref()),
)
.await
{
Ok(res) => fetch_many_response_kind(res),
Err(err) => handle_mapped_error(err),
}
}
async fn resolve_audit_context(
resource_type: &ResourceAuditResourceType,
resource_id: Uuid,
app_module: &web::Data<SqlAppModule>,
) -> Result<(Option<Uuid>, Option<Uuid>), HttpResponse> {
match resource_type {
ResourceAuditResourceType::Tenant => Ok((Some(resource_id), None)),
ResourceAuditResourceType::Account => {
resolve_account_audit_context(resource_id, app_module).await
}
_ => Ok((None, None)),
}
}
async fn resolve_account_audit_context(
account_id: Uuid,
app_module: &web::Data<SqlAppModule>,
) -> Result<(Option<Uuid>, Option<Uuid>), HttpResponse> {
let account_fetching_repo: &dyn AccountFetching = app_module.resolve_ref();
let account = match account_fetching_repo
.get(account_id, RelatedAccounts::HasStaffPrivileges)
.await
{
Ok(FetchResponseKind::Found(account)) => account,
Ok(FetchResponseKind::NotFound(_)) => {
return Err(HttpResponse::BadRequest()
.json(HttpJsonResponse::new_message("Invalid account ID")))
}
Err(err) => return Err(handle_mapped_error(err)),
};
Ok((account_tenant_id(&account.account_type), Some(account_id)))
}
fn account_tenant_id(account_type: &AccountType) -> Option<Uuid> {
match account_type {
AccountType::Subscription { tenant_id } => Some(*tenant_id),
AccountType::RoleAssociated { tenant_id, .. } => Some(*tenant_id),
AccountType::TenantManager { tenant_id } => Some(*tenant_id),
_ => None,
}
}