ockam_api 0.93.0

Ockam's request-response API
use ockam::identity::{Identifier, IdentitiesVerification};
use std::collections::HashMap;
use std::sync::Arc;

use ockam_core::errcode::{Kind, Origin};
use ockam_core::Error;
use ockam_vault::SoftwareVaultForVerifyingSignatures;

use crate::cli_state::{CliState, EnrollmentFilter, ProjectsRepository};
use crate::orchestrator::email_address::EmailAddress;
use crate::orchestrator::project::models::ProjectModel;
use crate::orchestrator::project::Project;
use crate::orchestrator::share::RoleInShare;

use super::Result;

pub struct Projects {
    projects_repository: Arc<dyn ProjectsRepository>,
    identities_verification: IdentitiesVerification,
}

impl Projects {
    pub fn new(
        projects_repository: Arc<dyn ProjectsRepository>,
        identities_verification: IdentitiesVerification,
    ) -> Self {
        Self {
            projects_repository,
            identities_verification,
        }
    }

    #[instrument(skip_all, fields(project_id = project_model.id))]
    pub async fn import_and_store_project(&self, project_model: ProjectModel) -> Result<Project> {
        let project = Project::import(project_model.clone()).await?;
        self.store_project(project).await
    }

    #[instrument(skip_all, fields(project_id = project.project_id()))]
    pub async fn store_project(&self, project: Project) -> Result<Project> {
        if let Some(project_identity) = project.project_identity() {
            self.identities_verification
                .update_identity_ignore_older(project_identity)
                .await?;
        }

        if let Some(authority_identity) = project.authority_identity() {
            self.identities_verification
                .update_identity_ignore_older(authority_identity)
                .await?;
        }

        self.store_project_model(project.model()).await?;
        Ok(project)
    }

    #[instrument(skip_all, fields(project_id = project.id))]
    pub async fn store_project_model(&self, project: &ProjectModel) -> Result<()> {
        self.projects_repository.store_project(project).await?;
        Ok(())
    }

    #[instrument(skip_all, fields(project_id = project_id))]
    pub async fn delete_project(&self, project_id: &str) -> Result<()> {
        self.projects_repository.delete_project(project_id).await?;
        Ok(())
    }

    #[instrument(skip_all, fields(project_id = project_id))]
    pub async fn set_default_project(&self, project_id: &str) -> Result<()> {
        self.projects_repository
            .set_default_project(project_id)
            .await?;
        Ok(())
    }

    #[instrument(skip_all)]
    pub async fn get_default_project(&self) -> Result<Project> {
        match self.projects_repository.get_default_project().await? {
            Some(project) => Ok(Project::import(project).await?),
            None => Err(Error::new(
                Origin::Api,
                Kind::NotFound,
                "there is no default project",
            ))?,
        }
    }

    #[instrument(skip_all, fields(name = name))]
    pub async fn get_project_by_name(&self, name: &str) -> Result<Project> {
        match self.projects_repository.get_project_by_name(name).await? {
            Some(project) => Ok(Project::import(project).await?),
            None => Err(Error::new(
                Origin::Api,
                Kind::NotFound,
                format!("there is no project named {name}"),
            ))?,
        }
    }

    #[instrument(skip_all, fields(project_id = project_id))]
    pub async fn get_project(&self, project_id: &str) -> Result<Project> {
        match self.projects_repository.get_project(project_id).await? {
            Some(project) => Ok(Project::import(project).await?),
            None => Err(Error::new(
                Origin::Api,
                Kind::NotFound,
                format!("there is no space project with id {project_id}"),
            ))?,
        }
    }

    #[instrument(skip_all, fields(project_name = project_name.clone()))]
    pub async fn get_project_by_name_or_default(
        &self,
        project_name: &Option<String>,
    ) -> Result<Project> {
        match project_name {
            Some(project_name) => self.get_project_by_name(project_name.as_str()).await,
            None => self.get_default_project().await,
        }
    }

    #[instrument(skip_all)]
    pub async fn get_projects(&self) -> Result<Vec<Project>> {
        let project_models = self.projects_repository.get_projects().await?;

        let mut projects = Vec::with_capacity(project_models.len());
        for project_model in project_models {
            let project = Project::import(project_model).await?;
            projects.push(project);
        }

        Ok(projects)
    }

    #[instrument(skip_all)]
    pub async fn get_projects_grouped_by_name(&self) -> Result<HashMap<String, Project>> {
        let mut projects = HashMap::new();
        for project in self.get_projects().await? {
            projects.insert(project.name().to_string(), project);
        }
        Ok(projects)
    }
}

impl CliState {
    pub async fn is_project_admin(
        &self,
        caller_identifier: &Identifier,
        project: &Project,
    ) -> Result<bool> {
        let enrolled = self
            .get_identity_enrollments(EnrollmentFilter::Enrolled)
            .await?;

        let emails: Vec<EmailAddress> = enrolled
            .iter()
            .flat_map(|x| {
                if x.identifier() == caller_identifier {
                    x.status().email().cloned()
                } else {
                    None
                }
            })
            .collect();

        let is_project_admin = project
            .model()
            .user_roles
            .iter()
            .any(|u| u.role == RoleInShare::Admin && emails.contains(&u.email));

        Ok(is_project_admin)
    }

    pub fn projects(&self) -> Projects {
        let identities_verification = IdentitiesVerification::new(
            self.change_history_repository(),
            SoftwareVaultForVerifyingSignatures::create(),
        );

        Projects::new(self.projects_repository(), identities_verification)
    }
}