use crate::{
dtos::{MyceliumProfileData, TenantData},
rest::shared::PaginationParams,
};
use actix_web::{get, patch, post, web, HttpResponse, Responder};
use myc_core::{
domain::{
actors::SystemActor,
dtos::{account::VerboseStatus, account_type::AccountType},
},
use_cases::role_scoped::subscriptions_manager::account::{
create_role_associated_account, create_subscription_account,
get_account_details, list_accounts_by_type,
propagate_existing_subscription_account, update_account_name_and_flags,
},
};
use myc_diesel::repositories::SqlAppModule;
use myc_http_tools::{
utils::HttpJsonResponse,
wrappers::default_response_to_http_response::{
fetch_many_response_kind, fetch_response_kind, handle_mapped_error,
updating_response_kind,
},
Account,
};
use mycelium_base::{dtos::PaginatedRecord, entities::GetOrCreateResponseKind};
use serde::Deserialize;
use shaku::HasComponent;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;
pub fn configure(config: &mut web::ServiceConfig) {
config
.service(create_subscription_account_url)
.service(create_role_associated_account_url)
.service(update_account_name_and_flags_url)
.service(list_accounts_by_type_url)
.service(get_account_details_url)
.service(propagate_existing_subscription_account_url);
}
#[derive(Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateSubscriptionAccountBody {
name: String,
}
#[derive(Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateRoleAssociatedAccountBody {
account_name: String,
role_name: String,
role_description: String,
}
#[derive(Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSubscriptionAccountNameAndFlagsBody {
name: Option<String>,
is_active: Option<bool>,
is_checked: Option<bool>,
is_archived: Option<bool>,
is_system_account: Option<bool>,
}
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub(crate) enum APIAccountType {
Staff,
Manager,
User,
Subscription,
ActorAssociated,
TenantManager,
RoleAssociated,
}
impl APIAccountType {
fn into_account_type(
&self,
tenant_id: Option<Uuid>,
actor: Option<SystemActor>,
) -> Result<AccountType, HttpResponse> {
match self {
APIAccountType::Staff => Ok(AccountType::Staff),
APIAccountType::Manager => Ok(AccountType::Manager),
APIAccountType::User => Ok(AccountType::User),
APIAccountType::TenantManager => {
if let Some(tenant_id) = tenant_id {
Ok(AccountType::TenantManager { tenant_id })
} else {
Err(HttpResponse::BadRequest()
.json(HttpJsonResponse::new_message(
"Tenant ID is required for tenant manager accounts.",
)))
}
}
APIAccountType::Subscription => {
if let Some(tenant_id) = tenant_id {
Ok(AccountType::Subscription { tenant_id })
} else {
Err(HttpResponse::BadRequest().json(
HttpJsonResponse::new_message(
"Tenant ID is required for subscription accounts.",
),
))
}
}
APIAccountType::ActorAssociated => {
if actor.is_none() {
return Err(HttpResponse::BadRequest().json(
HttpJsonResponse::new_message("Actor is required."),
));
}
Ok(AccountType::ActorAssociated {
actor: actor.unwrap(),
})
}
APIAccountType::RoleAssociated => {
if let Some(tenant_id) = tenant_id {
Ok(AccountType::RoleAssociated {
tenant_id,
role_name: String::new(),
read_role_id: Uuid::nil(),
write_role_id: Uuid::nil(),
})
} else {
Err(HttpResponse::BadRequest().json(
HttpJsonResponse::new_message(
"Tenant ID is required for subscription accounts.",
),
))
}
}
}
}
}
#[derive(Deserialize, ToSchema, IntoParams)]
#[serde(rename_all = "camelCase")]
pub struct ListSubscriptionAccountParams {
term: Option<String>,
tag_value: Option<String>,
account_type: Option<APIAccountType>,
is_owner_active: Option<bool>,
status: Option<VerboseStatus>,
actor: Option<SystemActor>,
}
#[utoipa::path(
post,
operation_id = "create_subscription_account",
params(
(
"x-mycelium-tenant-id" = Uuid,
Header,
description = "The tenant unique id."
),
),
request_body = CreateSubscriptionAccountBody,
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 = "Account already exists.",
body = HttpJsonResponse,
),
(
status = 201,
description = "Account created.",
body = Account,
),
),
)]
#[post("")]
pub async fn create_subscription_account_url(
tenant: TenantData,
body: web::Json<CreateSubscriptionAccountBody>,
profile: MyceliumProfileData,
app_module: web::Data<SqlAppModule>,
) -> impl Responder {
match create_subscription_account(
profile.to_profile(),
tenant.tenant_id().to_owned(),
body.name.to_owned(),
Box::new(&*app_module.resolve_ref()),
Box::new(&*app_module.resolve_ref()),
)
.await
{
Err(err) => handle_mapped_error(err),
Ok(account) => HttpResponse::Created().json(account),
}
}
#[utoipa::path(
post,
operation_id = "create_role_associated_account",
params(
(
"x-mycelium-tenant-id" = Uuid,
Header,
description = "The tenant unique id."
),
),
request_body = CreateRoleAssociatedAccountBody,
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 = "Account already exists.",
body = HttpJsonResponse,
),
(
status = 201,
description = "Account created.",
body = Account,
),
),
)]
#[post("/role-associated")]
pub async fn create_role_associated_account_url(
tenant: TenantData,
body: web::Json<CreateRoleAssociatedAccountBody>,
profile: MyceliumProfileData,
app_module: web::Data<SqlAppModule>,
) -> impl Responder {
match create_role_associated_account(
profile.to_profile(),
tenant.tenant_id().to_owned(),
body.account_name.to_owned(),
body.role_name.to_owned(),
body.role_description.to_owned(),
Box::new(&*app_module.resolve_ref()),
Box::new(&*app_module.resolve_ref()),
)
.await
{
Err(err) => handle_mapped_error(err),
Ok(account) => match account {
GetOrCreateResponseKind::Created(account) => {
HttpResponse::Created().json(account)
}
GetOrCreateResponseKind::NotCreated(account, msg) => {
tracing::warn!("{}", msg);
HttpResponse::Ok().json(account)
}
},
}
}
#[utoipa::path(
get,
operation_id = "list_accounts_by_type",
params(
(
"x-mycelium-tenant-id" = Uuid,
Header,
description = "The tenant unique id.",
),
ListSubscriptionAccountParams,
PaginationParams,
),
responses(
(
status = 500,
description = "Unknown internal server error.",
body = HttpJsonResponse,
),
(
status = 204,
description = "Not found.",
),
(
status = 403,
description = "Forbidden.",
body = HttpJsonResponse,
),
(
status = 401,
description = "Unauthorized.",
body = HttpJsonResponse,
),
(
status = 200,
description = "Fetching success.",
body = PaginatedRecord<Account>,
),
),
)]
#[get("")]
pub async fn list_accounts_by_type_url(
tenant: Option<TenantData>,
query: web::Query<ListSubscriptionAccountParams>,
page: web::Query<PaginationParams>,
profile: MyceliumProfileData,
app_module: web::Data<SqlAppModule>,
) -> impl Responder {
let mut is_account_active: Option<bool> = None;
let mut is_account_checked: Option<bool> = None;
let mut is_account_archived: Option<bool> = None;
let mut is_account_deleted: Option<bool> = None;
match query.status.to_owned() {
Some(res) => {
let flags = match res.to_flags() {
Err(err) => {
return HttpResponse::InternalServerError()
.json(HttpJsonResponse::new_message(err.to_string()))
}
Ok(res) => res,
};
is_account_active = flags.is_active;
is_account_checked = flags.is_checked;
is_account_archived = flags.is_archived;
is_account_deleted = flags.is_deleted;
}
_ => (),
}
let tenant_id = match tenant {
Some(tenant) => Some(tenant.tenant_id().to_owned()),
None => None,
};
let account_type = match &query.account_type {
None => None,
Some(res) => {
match res.into_account_type(tenant_id, query.actor.to_owned()) {
Ok(res) => Some(res),
Err(err) => return err,
}
}
};
match list_accounts_by_type(
profile.to_profile(),
tenant_id.to_owned(),
query.term.to_owned(),
query.is_owner_active.to_owned(),
is_account_active,
is_account_checked,
is_account_archived,
is_account_deleted,
account_type,
query.tag_value.to_owned(),
page.page_size.to_owned(),
page.skip.to_owned(),
Box::new(&*app_module.resolve_ref()),
)
.await
{
Ok(res) => fetch_many_response_kind(res),
Err(err) => handle_mapped_error(err),
}
}
#[utoipa::path(
get,
operation_id = "get_account_details",
params(
(
"x-mycelium-tenant-id" = Uuid,
Header,
description = "The tenant unique id."
),
("account_id" = Uuid, Path, description = "The account primary key."),
),
responses(
(
status = 500,
description = "Unknown internal server error.",
body = HttpJsonResponse,
),
(
status = 204,
description = "Not found.",
),
(
status = 403,
description = "Forbidden.",
body = HttpJsonResponse,
),
(
status = 401,
description = "Unauthorized.",
body = HttpJsonResponse,
),
(
status = 200,
description = "Fetching success.",
body = Account,
),
),
)]
#[get("/{account_id}")]
pub async fn get_account_details_url(
tenant: Option<TenantData>,
path: web::Path<Uuid>,
profile: MyceliumProfileData,
app_module: web::Data<SqlAppModule>,
) -> impl Responder {
let account_id = path.into_inner();
match get_account_details(
profile.to_profile(),
tenant.map(|t| t.tenant_id().to_owned()),
account_id,
Box::new(&*app_module.resolve_ref()),
)
.await
{
Ok(res) => fetch_response_kind(res),
Err(err) => handle_mapped_error(err),
}
}
#[utoipa::path(
patch,
operation_id = "update_account_name_and_flags",
params(
(
"x-mycelium-tenant-id" = Uuid,
Header,
description = "The tenant unique id."
),
("account_id" = Uuid, Path, description = "The account primary key."),
),
request_body = UpdateSubscriptionAccountNameAndFlagsBody,
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 = "Account already exists.",
body = HttpJsonResponse,
),
(
status = 201,
description = "Account created.",
body = Account,
),
),
)]
#[patch("/{account_id}")]
pub async fn update_account_name_and_flags_url(
tenant: TenantData,
path: web::Path<Uuid>,
body: web::Json<UpdateSubscriptionAccountNameAndFlagsBody>,
profile: MyceliumProfileData,
app_module: web::Data<SqlAppModule>,
) -> impl Responder {
let account_id = path.into_inner();
match update_account_name_and_flags(
profile.to_profile(),
account_id,
tenant.tenant_id().to_owned(),
body.name.to_owned(),
body.is_active.to_owned(),
body.is_checked.to_owned(),
body.is_archived.to_owned(),
body.is_system_account.to_owned(),
Box::new(&*app_module.resolve_ref()),
Box::new(&*app_module.resolve_ref()),
Box::new(&*app_module.resolve_ref()),
)
.await
{
Ok(res) => updating_response_kind(res),
Err(err) => handle_mapped_error(err),
}
}
#[utoipa::path(
post,
operation_id = "propagate_existing_subscription_account",
params(
(
"x-mycelium-tenant-id" = Uuid,
Header,
description = "The tenant unique id."
),
("account_id" = Uuid, Path, description = "The account primary key."),
),
responses(
(
status = 500,
description = "Unknown internal server error.",
body = HttpJsonResponse,
),
(
status = 204,
description = "Not found.",
),
(
status = 403,
description = "Forbidden.",
body = HttpJsonResponse,
),
(
status = 401,
description = "Unauthorized.",
body = HttpJsonResponse,
),
(
status = 200,
description = "Propagating success.",
body = Account,
),
),
)]
#[post("/{account_id}/propagate")]
pub async fn propagate_existing_subscription_account_url(
tenant: TenantData,
path: web::Path<Uuid>,
profile: MyceliumProfileData,
app_module: web::Data<SqlAppModule>,
) -> impl Responder {
let account_id = path.into_inner();
match propagate_existing_subscription_account(
profile.to_profile(),
tenant.tenant_id().to_owned(),
account_id,
Box::new(&*app_module.resolve_ref()),
Box::new(&*app_module.resolve_ref()),
)
.await
{
Ok(res) => HttpResponse::Ok().json(res),
Err(err) => handle_mapped_error(err),
}
}