use std::collections::BTreeMap; use clap::Parser; use exver::VersionRange; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::PackageId; use crate::context::CliContext; use crate::prelude::*; use crate::registry::admin::display_package_signers; use crate::registry::context::RegistryContext; use crate::registry::signer::SignerInfo; use crate::rpc_continuations::Guid; use crate::util::serde::HandlerExtSerde; pub fn signer_api() -> ParentHandler { ParentHandler::new() .subcommand( "add", from_fn_async(add_package_signer) .with_metadata("admin", Value::Bool(true)) .no_display() .with_about("about.add-package-signer") .with_call_remote::(), ) .subcommand( "remove", from_fn_async(remove_package_signer) .with_metadata("admin", Value::Bool(true)) .no_display() .with_about("about.remove-package-signer") .with_call_remote::(), ) .subcommand( "list", from_fn_async(list_package_signers) .with_metadata("authenticated", Value::Bool(false)) .with_display_serializable() .with_custom_display_fn(|handle, result| { display_package_signers(handle.params, result) }) .with_about("about.list-package-signers") .with_call_remote::(), ) } #[derive(Debug, Deserialize, Serialize, Parser, TS)] #[command(rename_all = "kebab-case")] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct AddPackageSignerParams { #[arg(help = "help.arg.package-id")] pub id: PackageId, #[arg(help = "help.arg.signer-id")] pub signer: Guid, #[arg(long, help = "help.arg.version-range")] #[ts(type = "string | null")] pub versions: Option, } pub async fn add_package_signer( ctx: RegistryContext, AddPackageSignerParams { id, signer, versions, }: AddPackageSignerParams, ) -> Result<(), Error> { ctx.db .mutate(|db| { ensure_code!( db.as_index().as_signers().contains_key(&signer)?, ErrorKind::InvalidRequest, "unknown signer {signer}" ); db.as_index_mut() .as_package_mut() .as_packages_mut() .as_idx_mut(&id) .or_not_found(&id)? .as_authorized_mut() .insert(&signer, &versions.unwrap_or_default())?; Ok(()) }) .await .result } #[derive(Debug, Deserialize, Serialize, Parser, TS)] #[command(rename_all = "kebab-case")] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct RemovePackageSignerParams { #[arg(help = "help.arg.package-id")] pub id: PackageId, #[arg(help = "help.arg.signer-id")] pub signer: Guid, } pub async fn remove_package_signer( ctx: RegistryContext, RemovePackageSignerParams { id, signer }: RemovePackageSignerParams, ) -> Result<(), Error> { ctx.db .mutate(|db| { if db .as_index_mut() .as_package_mut() .as_packages_mut() .as_idx_mut(&id) .or_not_found(&id)? .as_authorized_mut() .remove(&signer)? .is_some() { return Err(Error::new( eyre!( "{}", t!( "registry.package.signer.not-authorized", signer = signer, id = id ) ), ErrorKind::NotFound, )); } Ok(()) }) .await .result } #[derive(Debug, Deserialize, Serialize, Parser, TS)] #[command(rename_all = "kebab-case")] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct ListPackageSignersParams { #[arg(help = "help.arg.package-id")] pub id: PackageId, } pub async fn list_package_signers( ctx: RegistryContext, ListPackageSignersParams { id }: ListPackageSignersParams, ) -> Result, Error> { let db = ctx.db.peek().await; db.as_index() .as_package() .as_packages() .as_idx(&id) .or_not_found(&id)? .as_authorized() .de()? .into_iter() .filter_map(|(guid, versions)| { db.as_index() .as_signers() .as_idx(&guid) .map(|s| s.de().map(|s| (guid, (s, versions)))) }) .collect() }