use chrono::Utc;
use skilj_core::access_control::{self, AccessLevel, Role, RoleAccessMapping, RoleStatus};
use skilj_core::bootstrap::ContextCreator;
use skilj_core::db::{self, Pool};
use skilj_core::event_store::{BoundedContext, BoundedContextStatus};
use skilj_core::shared::{generate_token_id, generate_token_secret};
use skilj_core::Error;
pub struct DevAdmin {
pool: Pool,
role: Role,
mappings: Vec<RoleAccessMapping>,
}
pub async fn seed_admin(
database_url: &str,
name: &str,
bounded_contexts: &[&str],
) -> Result<DevAdmin, Error> {
let pool = db::connect_with(database_url, db::PgPoolOptions::new().max_connections(2)).await?;
db::migrate(&pool).await?;
for bounded_context in bounded_contexts {
if db::get_bounded_context(&pool, bounded_context)
.await?
.is_none()
{
db::insert_bounded_context(
&pool,
&BoundedContext {
name: (*bounded_context).to_string(),
status: BoundedContextStatus::Active,
created_at: Utc::now(),
created_by: ContextCreator::SystemCreator,
template: None,
},
)
.await?;
}
}
let id = generate_token_id();
let role = Role {
external_subject: format!("dev-admin-{id}"),
id,
name: name.to_string(),
superadmin: false,
status: RoleStatus::Active,
created_at: Utc::now(),
revoked_at: None,
};
db::insert_role(&pool, &role).await?;
let mut mappings = Vec::with_capacity(bounded_contexts.len());
for bounded_context in bounded_contexts {
let bounded_context = db::get_bounded_context(&pool, bounded_context)
.await?
.ok_or_else(Error::row_not_found)?;
let mapping = RoleAccessMapping {
role: role.clone(),
bounded_context,
level: AccessLevel::Admin,
can_read_sensitive: false,
scope: None,
capabilities: Default::default(),
delegable: Default::default(),
status: RoleStatus::Active,
created_at: Utc::now(),
revoked_at: None,
};
db::insert_role_access_mapping(&pool, &mapping).await?;
mappings.push(mapping);
}
Ok(DevAdmin {
pool,
role,
mappings,
})
}
impl DevAdmin {
pub fn external_subject(&self) -> &str {
&self.role.external_subject
}
pub fn role(&self) -> &Role {
&self.role
}
pub async fn create_command_token(
&self,
bounded_context: &str,
command_type: &str,
) -> Result<String, Error> {
let mapping = self
.mappings
.iter()
.find(|m| m.bounded_context.name == bounded_context)
.ok_or_else(Error::row_not_found)?;
let command_type = db::get_command_type(&self.pool, bounded_context, command_type)
.await?
.ok_or_else(Error::row_not_found)?;
let token = access_control::create_command_token(
mapping,
&command_type,
generate_token_id(),
generate_token_secret(),
None,
Utc::now(),
)?;
db::insert_command_token(&self.pool, &token).await?;
Ok(format!("{}.{}", token.id, token.secret))
}
pub async fn close(self) {
self.pool.close().await;
}
}