orign 0.2.3

A globally distributed container orchestrator
Documentation
use crate::models::{Adapters, V1UserProfile};
use crate::state::AppState;
use axum::{
    extract::Extension, extract::Json, extract::Path, extract::State, http::StatusCode,
    response::IntoResponse,
};
use serde_json::json;
use tracing::info;

#[axum::debug_handler]
pub async fn list_adapters(
    State(state): State<AppState>,
    Extension(user_profile): Extension<V1UserProfile>,
) -> impl IntoResponse {
    // Collect all namespaces the user has access to
    let mut owners = Vec::new();
    owners.push(user_profile.email.clone());

    // Add organization IDs
    let organization_ids: Vec<String> = if let Some(orgs) = &user_profile.organizations {
        orgs.keys().cloned().collect()
    } else {
        Vec::new()
    };
    owners.extend(organization_ids);

    // Initialize a vector to hold the adapter names
    let mut adapters = Vec::new();

    // Iterate over all namespaces and collect adapters
    for owner in owners {
        // Construct the path to the adapters directory for this namespace
        let adapters_path = format!("/orign/{}/adapters", owner);

        // Read the directories in the adapters path
        match tokio::fs::read_dir(&adapters_path).await {
            Ok(mut dir) => {
                while let Ok(Some(entry)) = dir.next_entry().await {
                    let file_type = match entry.file_type().await {
                        Ok(ft) => ft,
                        Err(err) => {
                            return Err((
                                StatusCode::INTERNAL_SERVER_ERROR,
                                format!("Failed to get file type: {}", err),
                            ));
                        }
                    };

                    if file_type.is_dir() {
                        if let Some(name) = entry.file_name().to_str() {
                            // Determine the display name based on whether "owner" is the user's email or part of orgs
                            let resolved_owner = if owner == user_profile.email {
                                user_profile
                                    .handle
                                    .clone()
                                    .unwrap_or(user_profile.email.clone())
                            } else {
                                user_profile
                                    .organizations
                                    .as_ref()
                                    .and_then(|orgs| orgs.get(&owner))
                                    .and_then(|org_data| org_data.get("org_name"))
                                    .cloned()
                                    .unwrap_or(owner.clone())
                            };

                            adapters.push(format!("{}/{}", resolved_owner, name));
                        }
                    }
                }
            }
            Err(_) => {
                // If the directory doesn't exist, skip this namespace
                continue;
            }
        }
    }

    // Return the list of adapters
    Ok(Json(Adapters { adapters }))
}

/// Utility function to resolve the actual owner directory based on the given `namespace`.
/// 1) If user's handle is set and matches `namespace`, return the user email.
/// 2) Else, if any org has an `org_name` that matches `namespace`, return its org id.
/// 3) Otherwise, return FORBIDDEN.
fn resolve_namespace_owner(
    namespace: &str,
    user_profile: &V1UserProfile,
) -> Result<String, (StatusCode, String)> {
    // 1) Check if user's handle matches the namespace
    if let Some(ref handle) = user_profile.handle {
        if handle == namespace {
            return Ok(user_profile.email.clone());
        }
    }

    // 2) Otherwise, search the user's orgs for matching org_name
    if let Some(ref orgs) = user_profile.organizations {
        let found = orgs.iter().find_map(|(org_id, org_data)| {
            if let Some(org_name) = org_data.get("org_name") {
                if org_name == namespace {
                    return Some(org_id.clone());
                }
            }
            None
        });

        if let Some(org_id) = found {
            return Ok(org_id);
        }
    }

    // 3) If no handle match or org_name match, user does not own this namespace
    Err((
        StatusCode::FORBIDDEN,
        format!(
            "You do not have permission over the namespace '{}'",
            namespace
        ),
    ))
}

#[axum::debug_handler]
pub async fn delete_adapter(
    // The path is something like: /:namespace/:adapter_name
    Path((namespace, adapter_name)): Path<(String, String)>,
    State(state): State<AppState>,
    Extension(user_profile): Extension<V1UserProfile>,
) -> impl IntoResponse {
    // Use the utility function to resolve the real owner directory
    let resolved_owner = match resolve_namespace_owner(&namespace, &user_profile) {
        Ok(owner) => owner,
        Err(err) => return Err(err),
    };

    // Construct the directory path for this adapter
    let adapter_dir = format!("/{}/adapters/{}", resolved_owner, adapter_name);
    info!("Deleting adapter directory: {:?}", &adapter_dir);

    match tokio::fs::remove_dir_all(&adapter_dir).await {
        Ok(_) => {
            info!("Successfully removed adapter directory: {}", adapter_dir);
            Ok(Json(json!({
                "status": "ok",
                "message": format!("Adapter '{}' successfully deleted from namespace '{}'", adapter_name, namespace),
            })))
        }
        Err(e) => {
            let msg = format!(
                "Failed to remove adapter directory '{}': {}",
                adapter_dir, e
            );
            info!("{}", msg);
            Err((StatusCode::INTERNAL_SERVER_ERROR, msg))
        }
    }
}