mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
Feature/fe new registry (#2647)
* bugfixes * update fe types * implement new registry types in marketplace and ui * fix marketplace types to have default params * add alt implementation toggle * merge cleanup * more cleanup and notes * fix build * cleanup sync with next/minor * add exver JS parser * parse ValidExVer to string * update types to interface * add VersionRange and comparative functions * Parse ExtendedVersion from string * add conjunction, disjunction, and inversion logic * consider flavor in satisfiedBy fn * consider prerelease for ordering * add compare fn for sorting * rename fns for consistency * refactoring * update compare fn to return null if flavors don't match * begin simplifying dependencies * under construction * wip * add dependency metadata to CurrentDependencyInfo * ditch inheritance for recursive VersionRange constructor. Recursive 'satisfiedBy' fn wip * preprocess manifest * misc fixes * use sdk version as osVersion in manifest * chore: Change the type to just validate and not generate all solutions. * add publishedAt * fix pegjs exports * integrate exver into sdk * misc fixes * complete satisfiedBy fn * refactor - use greaterThanOrEqual and lessThanOrEqual fns * fix tests * update dependency details * update types * remove interim types * rename alt implementation to flavor * cleanup os update * format exver.ts * add s9pk parsing endpoints * fix build * update to exver * exver and bug fixes * update static endpoints + cleanup * cleanup * update static proxy verification * make mocks more robust; fix dep icon fallback; cleanup * refactor alert versions and update fixtures * registry bugfixes * misc fixes * cleanup unused * convert patchdb ui seed to camelCase * update otherVersions type * change otherVersions: null to 'none' * refactor and complete feature * improve static endpoints * fix install params * mask systemd-networkd-wait-online * fix static file fetching * include non-matching versions in otherVersions * convert release notes to modal and clean up displayExver * alert for no other versions * Fix ack-instructions casing * fix indeterminate loader on service install --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: Shadowy Super Coder <musashidisciple@proton.me> Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Co-authored-by: J H <dragondef@gmail.com> Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -46,7 +46,7 @@ fn signers_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand("add", from_fn_async(cli_add_signer).no_display())
|
||||
.subcommand("add", from_fn_async(cli_add_signer))
|
||||
}
|
||||
|
||||
impl Model<BTreeMap<Guid, SignerInfo>> {
|
||||
@@ -71,7 +71,7 @@ impl Model<BTreeMap<Guid, SignerInfo>> {
|
||||
.ok_or_else(|| Error::new(eyre!("unknown signer"), ErrorKind::Authorization))
|
||||
}
|
||||
|
||||
pub fn add_signer(&mut self, signer: &SignerInfo) -> Result<(), Error> {
|
||||
pub fn add_signer(&mut self, signer: &SignerInfo) -> Result<Guid, Error> {
|
||||
if let Some((guid, s)) = self
|
||||
.as_entries()?
|
||||
.into_iter()
|
||||
@@ -89,7 +89,9 @@ impl Model<BTreeMap<Guid, SignerInfo>> {
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
self.insert(&Guid::new(), signer)
|
||||
let id = Guid::new();
|
||||
self.insert(&id, signer)?;
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +124,7 @@ pub fn display_signers<T>(params: WithIoFormat<T>, signers: BTreeMap<Guid, Signe
|
||||
table.print_tty(false).unwrap();
|
||||
}
|
||||
|
||||
pub async fn add_signer(ctx: RegistryContext, signer: SignerInfo) -> Result<(), Error> {
|
||||
pub async fn add_signer(ctx: RegistryContext, signer: SignerInfo) -> Result<Guid, Error> {
|
||||
ctx.db
|
||||
.mutate(|db| db.as_index_mut().as_signers_mut().add_signer(&signer))
|
||||
.await
|
||||
@@ -155,7 +157,7 @@ pub async fn cli_add_signer(
|
||||
},
|
||||
..
|
||||
}: HandlerArgs<CliContext, CliAddSignerParams>,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<Guid, Error> {
|
||||
let signer = SignerInfo {
|
||||
name,
|
||||
contact,
|
||||
@@ -165,15 +167,16 @@ pub async fn cli_add_signer(
|
||||
TypedPatchDb::<RegistryDatabase>::load(PatchDb::open(database).await?)
|
||||
.await?
|
||||
.mutate(|db| db.as_index_mut().as_signers_mut().add_signer(&signer))
|
||||
.await?;
|
||||
.await
|
||||
} else {
|
||||
ctx.call_remote::<RegistryContext>(
|
||||
&parent_method.into_iter().chain(method).join("."),
|
||||
to_value(&signer)?,
|
||||
from_value(
|
||||
ctx.call_remote::<RegistryContext>(
|
||||
&parent_method.into_iter().chain(method).join("."),
|
||||
to_value(&signer)?,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncWrite;
|
||||
@@ -20,6 +21,8 @@ use crate::s9pk::S9pk;
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RegistryAsset<Commitment> {
|
||||
#[ts(type = "string")]
|
||||
pub published_at: DateTime<Utc>,
|
||||
#[ts(type = "string")]
|
||||
pub url: Url,
|
||||
pub commitment: Commitment,
|
||||
|
||||
@@ -27,7 +27,6 @@ use crate::util::serde::Base64;
|
||||
pub const AUTH_SIG_HEADER: &str = "X-StartOS-Registry-Auth-Sig";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Metadata {
|
||||
#[serde(default)]
|
||||
admin: bool,
|
||||
@@ -75,9 +74,7 @@ pub struct RegistryAdminLogRecord {
|
||||
pub key: AnyVerifyingKey,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SignatureHeader {
|
||||
#[serde(flatten)]
|
||||
pub commitment: RequestCommitment,
|
||||
pub signer: AnyVerifyingKey,
|
||||
pub signature: AnySignature,
|
||||
@@ -93,14 +90,9 @@ impl SignatureHeader {
|
||||
HeaderValue::from_str(url.query().unwrap_or_default()).unwrap()
|
||||
}
|
||||
pub fn from_header(header: &HeaderValue) -> Result<Self, Error> {
|
||||
let url: Url = format!(
|
||||
"http://localhost/?{}",
|
||||
header.to_str().with_kind(ErrorKind::Utf8)?
|
||||
)
|
||||
.parse()?;
|
||||
let query: BTreeMap<_, _> = url.query_pairs().collect();
|
||||
let query: BTreeMap<_, _> = form_urlencoded::parse(header.as_bytes()).collect();
|
||||
Ok(Self {
|
||||
commitment: RequestCommitment::from_query(&url)?,
|
||||
commitment: RequestCommitment::from_query(&header)?,
|
||||
signer: query.get("signer").or_not_found("signer")?.parse()?,
|
||||
signature: query.get("signature").or_not_found("signature")?.parse()?,
|
||||
})
|
||||
|
||||
@@ -200,6 +200,19 @@ impl CallRemote<RegistryContext> for CliContext {
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !res.status().is_success() {
|
||||
let status = res.status();
|
||||
let txt = res.text().await?;
|
||||
let mut res = Err(Error::new(
|
||||
eyre!("{}", status.canonical_reason().unwrap_or(status.as_str())),
|
||||
ErrorKind::Network,
|
||||
));
|
||||
if !txt.is_empty() {
|
||||
res = res.with_ctx(|_| (ErrorKind::Network, txt));
|
||||
}
|
||||
return res.map_err(From::from);
|
||||
}
|
||||
|
||||
match res
|
||||
.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
@@ -210,7 +223,7 @@ impl CallRemote<RegistryContext> for CliContext {
|
||||
.with_kind(ErrorKind::Deserialization)?
|
||||
.result
|
||||
}
|
||||
_ => Err(Error::new(eyre!("missing content type"), ErrorKind::Network).into()),
|
||||
_ => Err(Error::new(eyre!("unknown content type"), ErrorKind::Network).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,6 +260,19 @@ impl CallRemote<RegistryContext, RegistryUrlParams> for RpcContext {
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !res.status().is_success() {
|
||||
let status = res.status();
|
||||
let txt = res.text().await?;
|
||||
let mut res = Err(Error::new(
|
||||
eyre!("{}", status.canonical_reason().unwrap_or(status.as_str())),
|
||||
ErrorKind::Network,
|
||||
));
|
||||
if !txt.is_empty() {
|
||||
res = res.with_ctx(|_| (ErrorKind::Network, txt));
|
||||
}
|
||||
return res.map_err(From::from);
|
||||
}
|
||||
|
||||
match res
|
||||
.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
@@ -257,7 +283,7 @@ impl CallRemote<RegistryContext, RegistryUrlParams> for RpcContext {
|
||||
.with_kind(ErrorKind::Deserialization)?
|
||||
.result
|
||||
}
|
||||
_ => Err(Error::new(eyre!("missing content type"), ErrorKind::Network).into()),
|
||||
_ => Err(Error::new(eyre!("unknown content type"), ErrorKind::Network).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,7 @@ impl DeviceInfo {
|
||||
HeaderValue::from_str(url.query().unwrap_or_default()).unwrap()
|
||||
}
|
||||
pub fn from_header_value(header: &HeaderValue) -> Result<Self, Error> {
|
||||
let url: Url = format!(
|
||||
"http://localhost/?{}",
|
||||
header.to_str().with_kind(ErrorKind::ParseUrl)?
|
||||
)
|
||||
.parse()?;
|
||||
let query: BTreeMap<_, _> = url.query_pairs().collect();
|
||||
let query: BTreeMap<_, _> = form_urlencoded::parse(header.as_bytes()).collect();
|
||||
Ok(Self {
|
||||
os: OsInfo {
|
||||
version: query
|
||||
@@ -151,7 +146,6 @@ impl From<&RpcContext> for HardwareInfo {
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Metadata {
|
||||
#[serde(default)]
|
||||
get_device_info: bool,
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use axum::Router;
|
||||
use futures::future::ready;
|
||||
use imbl_value::InternedString;
|
||||
use models::DataUrl;
|
||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler, Server};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -16,7 +17,7 @@ use crate::registry::auth::Auth;
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::device_info::DeviceInfoMiddleware;
|
||||
use crate::registry::os::index::OsIndex;
|
||||
use crate::registry::package::index::PackageIndex;
|
||||
use crate::registry::package::index::{Category, PackageIndex};
|
||||
use crate::registry::signer::SignerInfo;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::util::serde::HandlerExtSerde;
|
||||
@@ -45,6 +46,7 @@ impl RegistryDatabase {}
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct FullIndex {
|
||||
pub name: Option<String>,
|
||||
pub icon: Option<DataUrl<'static>>,
|
||||
pub package: PackageIndex,
|
||||
pub os: OsIndex,
|
||||
@@ -55,6 +57,25 @@ pub async fn get_full_index(ctx: RegistryContext) -> Result<FullIndex, Error> {
|
||||
ctx.db.peek().await.into_index().de()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RegistryInfo {
|
||||
pub name: Option<String>,
|
||||
pub icon: Option<DataUrl<'static>>,
|
||||
#[ts(as = "BTreeMap::<String, Category>")]
|
||||
pub categories: BTreeMap<InternedString, Category>,
|
||||
}
|
||||
|
||||
pub async fn get_info(ctx: RegistryContext) -> Result<RegistryInfo, Error> {
|
||||
let peek = ctx.db.peek().await.into_index();
|
||||
Ok(RegistryInfo {
|
||||
name: peek.as_name().de()?,
|
||||
icon: peek.as_icon().de()?,
|
||||
categories: peek.as_package().as_categories().de()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn registry_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
@@ -63,6 +84,12 @@ pub fn registry_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_display_serializable()
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"info",
|
||||
from_fn_async(get_info)
|
||||
.with_display_serializable()
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("os", os::os_api::<C>())
|
||||
.subcommand("package", package::package_api::<C>())
|
||||
.subcommand("admin", admin::admin_api::<C>())
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashMap};
|
||||
use std::panic::UnwindSafe;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use chrono::Utc;
|
||||
use clap::Parser;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
@@ -12,7 +13,7 @@ use url::Url;
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::{FullProgressTracker};
|
||||
use crate::progress::FullProgressTracker;
|
||||
use crate::registry::asset::RegistryAsset;
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::os::index::OsVersionInfo;
|
||||
@@ -33,19 +34,19 @@ pub fn add_api<C: Context>() -> ParentHandler<C> {
|
||||
.subcommand(
|
||||
"iso",
|
||||
from_fn_async(add_iso)
|
||||
.with_metadata("getSigner", Value::Bool(true))
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"img",
|
||||
from_fn_async(add_img)
|
||||
.with_metadata("getSigner", Value::Bool(true))
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"squashfs",
|
||||
from_fn_async(add_squashfs)
|
||||
.with_metadata("getSigner", Value::Bool(true))
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
}
|
||||
@@ -107,6 +108,7 @@ async fn add_asset(
|
||||
)
|
||||
.upsert(&platform, || {
|
||||
Ok(RegistryAsset {
|
||||
published_at: Utc::now(),
|
||||
url,
|
||||
commitment: commitment.clone(),
|
||||
signatures: HashMap::new(),
|
||||
|
||||
@@ -30,19 +30,19 @@ pub fn sign_api<C: Context>() -> ParentHandler<C> {
|
||||
.subcommand(
|
||||
"iso",
|
||||
from_fn_async(sign_iso)
|
||||
.with_metadata("getSigner", Value::Bool(true))
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"img",
|
||||
from_fn_async(sign_img)
|
||||
.with_metadata("getSigner", Value::Bool(true))
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"squashfs",
|
||||
from_fn_async(sign_squashfs)
|
||||
.with_metadata("getSigner", Value::Bool(true))
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn version_api<C: Context>() -> ParentHandler<C> {
|
||||
"add",
|
||||
from_fn_async(add_version)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.with_metadata("getSigner", Value::Bool(true))
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
@@ -121,7 +121,7 @@ pub async fn remove_version(
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct GetVersionParams {
|
||||
pub struct GetOsVersionParams {
|
||||
#[ts(type = "string | null")]
|
||||
#[arg(long = "src")]
|
||||
pub source: Option<VersionString>,
|
||||
@@ -138,12 +138,12 @@ pub struct GetVersionParams {
|
||||
|
||||
pub async fn get_version(
|
||||
ctx: RegistryContext,
|
||||
GetVersionParams {
|
||||
GetOsVersionParams {
|
||||
source,
|
||||
target,
|
||||
server_id,
|
||||
arch,
|
||||
}: GetVersionParams,
|
||||
}: GetOsVersionParams,
|
||||
) -> Result<BTreeMap<VersionString, OsVersionInfo>, Error> {
|
||||
if let (Some(pool), Some(server_id), Some(arch)) = (&ctx.pool, server_id, arch) {
|
||||
let created_at = Utc::now();
|
||||
|
||||
@@ -11,13 +11,14 @@ use url::Url;
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::FullProgressTracker;
|
||||
use crate::progress::{FullProgressTracker, ProgressTrackerWriter};
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::package::index::PackageVersionInfo;
|
||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||
use crate::registry::signer::sign::ed25519::Ed25519;
|
||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||
use crate::s9pk::v2::SIG_CONTEXT;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::util::io::TrackingIO;
|
||||
@@ -126,13 +127,16 @@ pub async fn cli_add_package(
|
||||
sign_phase.complete();
|
||||
|
||||
verify_phase.start();
|
||||
let mut src = S9pk::deserialize(
|
||||
&Arc::new(HttpSource::new(ctx.client.clone(), url.clone()).await?),
|
||||
Some(&commitment),
|
||||
)
|
||||
.await?;
|
||||
src.serialize(&mut TrackingIO::new(0, tokio::io::sink()), true)
|
||||
let source = HttpSource::new(ctx.client.clone(), url.clone()).await?;
|
||||
let len = source.size().await;
|
||||
let mut src = S9pk::deserialize(&Arc::new(source), Some(&commitment)).await?;
|
||||
if let Some(len) = len {
|
||||
verify_phase.set_total(len);
|
||||
}
|
||||
let mut verify_writer = ProgressTrackerWriter::new(tokio::io::sink(), verify_phase);
|
||||
src.serialize(&mut TrackingIO::new(0, &mut verify_writer), true)
|
||||
.await?;
|
||||
let (_, mut verify_phase) = verify_writer.into_inner();
|
||||
verify_phase.complete();
|
||||
|
||||
index_phase.start();
|
||||
@@ -140,7 +144,7 @@ pub async fn cli_add_package(
|
||||
&parent_method.into_iter().chain(method).join("."),
|
||||
imbl_value::json!({
|
||||
"url": &url,
|
||||
"signature": signature,
|
||||
"signature": AnySignature::Ed25519(signature),
|
||||
"commitment": commitment,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::util::VersionString;
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub enum PackageDetailLevel {
|
||||
None,
|
||||
Short,
|
||||
Full,
|
||||
}
|
||||
@@ -50,7 +51,9 @@ pub struct GetPackageParams {
|
||||
#[arg(skip)]
|
||||
#[serde(rename = "__device_info")]
|
||||
pub device_info: Option<DeviceInfo>,
|
||||
pub other_versions: Option<PackageDetailLevel>,
|
||||
#[serde(default)]
|
||||
#[arg(default_value = "none")]
|
||||
pub other_versions: PackageDetailLevel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
@@ -126,7 +129,6 @@ fn get_matching_models<'a>(
|
||||
db: &'a Model<PackageIndex>,
|
||||
GetPackageParams {
|
||||
id,
|
||||
version,
|
||||
source_version,
|
||||
device_info,
|
||||
..
|
||||
@@ -148,22 +150,18 @@ fn get_matching_models<'a>(
|
||||
.into_iter()
|
||||
.map(|(v, info)| {
|
||||
Ok::<_, Error>(
|
||||
if version
|
||||
if source_version.as_ref().map_or(Ok(true), |source_version| {
|
||||
Ok::<_, Error>(
|
||||
source_version.satisfies(
|
||||
&info
|
||||
.as_source_version()
|
||||
.de()?
|
||||
.unwrap_or(VersionRange::any()),
|
||||
),
|
||||
)
|
||||
})? && device_info
|
||||
.as_ref()
|
||||
.map_or(true, |version| v.satisfies(version))
|
||||
&& source_version.as_ref().map_or(Ok(true), |source_version| {
|
||||
Ok::<_, Error>(
|
||||
source_version.satisfies(
|
||||
&info
|
||||
.as_source_version()
|
||||
.de()?
|
||||
.unwrap_or(VersionRange::any()),
|
||||
),
|
||||
)
|
||||
})?
|
||||
&& device_info
|
||||
.as_ref()
|
||||
.map_or(Ok(true), |device_info| info.works_for_device(device_info))?
|
||||
.map_or(Ok(true), |device_info| info.works_for_device(device_info))?
|
||||
{
|
||||
Some((k.clone(), ExtendedVersion::from(v), info))
|
||||
} else {
|
||||
@@ -187,24 +185,27 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
||||
let mut other: BTreeMap<PackageId, BTreeMap<VersionString, &Model<PackageVersionInfo>>> =
|
||||
Default::default();
|
||||
for (id, version, info) in get_matching_models(&peek.as_index().as_package(), ¶ms)? {
|
||||
let mut package_best = best.remove(&id).unwrap_or_default();
|
||||
let mut package_other = other.remove(&id).unwrap_or_default();
|
||||
for worse_version in package_best
|
||||
.keys()
|
||||
.filter(|k| ***k < version)
|
||||
.cloned()
|
||||
.collect_vec()
|
||||
let package_best = best.entry(id.clone()).or_default();
|
||||
let package_other = other.entry(id.clone()).or_default();
|
||||
if params
|
||||
.version
|
||||
.as_ref()
|
||||
.map_or(true, |v| version.satisfies(v))
|
||||
&& package_best.keys().all(|k| !(**k > version))
|
||||
{
|
||||
if let Some(info) = package_best.remove(&worse_version) {
|
||||
package_other.insert(worse_version, info);
|
||||
for worse_version in package_best
|
||||
.keys()
|
||||
.filter(|k| ***k < version)
|
||||
.cloned()
|
||||
.collect_vec()
|
||||
{
|
||||
if let Some(info) = package_best.remove(&worse_version) {
|
||||
package_other.insert(worse_version, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
if package_best.keys().all(|k| !(**k > version)) {
|
||||
package_best.insert(version.into(), info);
|
||||
}
|
||||
best.insert(id.clone(), package_best);
|
||||
if params.other_versions.is_some() {
|
||||
other.insert(id.clone(), package_other);
|
||||
} else {
|
||||
package_other.insert(version.into(), info);
|
||||
}
|
||||
}
|
||||
if let Some(id) = params.id {
|
||||
@@ -224,12 +225,12 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
||||
.try_collect()?;
|
||||
let other = other.remove(&id).unwrap_or_default();
|
||||
match params.other_versions {
|
||||
None => to_value(&GetPackageResponse {
|
||||
PackageDetailLevel::None => to_value(&GetPackageResponse {
|
||||
categories,
|
||||
best,
|
||||
other_versions: None,
|
||||
}),
|
||||
Some(PackageDetailLevel::Short) => to_value(&GetPackageResponse {
|
||||
PackageDetailLevel::Short => to_value(&GetPackageResponse {
|
||||
categories,
|
||||
best,
|
||||
other_versions: Some(
|
||||
@@ -239,7 +240,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
||||
.try_collect()?,
|
||||
),
|
||||
}),
|
||||
Some(PackageDetailLevel::Full) => to_value(&GetPackageResponseFull {
|
||||
PackageDetailLevel::Full => to_value(&GetPackageResponseFull {
|
||||
categories,
|
||||
best,
|
||||
other_versions: other
|
||||
@@ -250,7 +251,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
||||
}
|
||||
} else {
|
||||
match params.other_versions {
|
||||
None => to_value(
|
||||
PackageDetailLevel::None => to_value(
|
||||
&best
|
||||
.into_iter()
|
||||
.map(|(id, best)| {
|
||||
@@ -276,7 +277,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
||||
})
|
||||
.try_collect::<_, GetPackagesResponse, _>()?,
|
||||
),
|
||||
Some(PackageDetailLevel::Short) => to_value(
|
||||
PackageDetailLevel::Short => to_value(
|
||||
&best
|
||||
.into_iter()
|
||||
.map(|(id, best)| {
|
||||
@@ -310,7 +311,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
||||
})
|
||||
.try_collect::<_, GetPackagesResponse, _>()?,
|
||||
),
|
||||
Some(PackageDetailLevel::Full) => to_value(
|
||||
PackageDetailLevel::Full => to_value(
|
||||
&best
|
||||
.into_iter()
|
||||
.map(|(id, best)| {
|
||||
@@ -354,7 +355,7 @@ pub fn display_package_info(
|
||||
}
|
||||
|
||||
if let Some(_) = params.rest.id {
|
||||
if params.rest.other_versions == Some(PackageDetailLevel::Full) {
|
||||
if params.rest.other_versions == PackageDetailLevel::Full {
|
||||
for table in from_value::<GetPackageResponseFull>(info)?.tables() {
|
||||
table.print_tty(false)?;
|
||||
println!();
|
||||
@@ -366,7 +367,7 @@ pub fn display_package_info(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if params.rest.other_versions == Some(PackageDetailLevel::Full) {
|
||||
if params.rest.other_versions == PackageDetailLevel::Full {
|
||||
for (_, package) in from_value::<GetPackagesResponseFull>(info)? {
|
||||
for table in package.tables() {
|
||||
table.print_tty(false)?;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use chrono::Utc;
|
||||
use exver::{Version, VersionRange};
|
||||
use imbl_value::InternedString;
|
||||
use models::{DataUrl, PackageId, VersionString};
|
||||
@@ -15,7 +16,7 @@ use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment
|
||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey};
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::s9pk::git_hash::GitHash;
|
||||
use crate::s9pk::manifest::{Description, HardwareRequirements};
|
||||
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements};
|
||||
use crate::s9pk::merkle_archive::source::FileSource;
|
||||
use crate::s9pk::S9pk;
|
||||
|
||||
@@ -49,12 +50,25 @@ pub struct Category {
|
||||
pub description: Description,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct DependencyMetadata {
|
||||
#[ts(type = "string | null")]
|
||||
pub title: Option<InternedString>,
|
||||
pub icon: Option<DataUrl<'static>>,
|
||||
pub description: Option<String>,
|
||||
pub optional: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct PackageVersionInfo {
|
||||
pub title: String,
|
||||
#[ts(type = "string")]
|
||||
pub title: InternedString,
|
||||
pub icon: DataUrl<'static>,
|
||||
pub description: Description,
|
||||
pub release_notes: String,
|
||||
@@ -70,6 +84,10 @@ pub struct PackageVersionInfo {
|
||||
pub support_site: Url,
|
||||
#[ts(type = "string")]
|
||||
pub marketing_site: Url,
|
||||
#[ts(type = "string | null")]
|
||||
pub donation_url: Option<Url>,
|
||||
pub alerts: Alerts,
|
||||
pub dependency_metadata: BTreeMap<PackageId, DependencyMetadata>,
|
||||
#[ts(type = "string")]
|
||||
pub os_version: Version,
|
||||
pub hardware_requirements: HardwareRequirements,
|
||||
@@ -80,6 +98,19 @@ pub struct PackageVersionInfo {
|
||||
impl PackageVersionInfo {
|
||||
pub async fn from_s9pk<S: FileSource + Clone>(s9pk: &S9pk<S>, url: Url) -> Result<Self, Error> {
|
||||
let manifest = s9pk.as_manifest();
|
||||
let mut dependency_metadata = BTreeMap::new();
|
||||
for (id, info) in &manifest.dependencies.0 {
|
||||
let metadata = s9pk.dependency_metadata(id).await?;
|
||||
dependency_metadata.insert(
|
||||
id.clone(),
|
||||
DependencyMetadata {
|
||||
title: metadata.map(|m| m.title),
|
||||
icon: s9pk.dependency_icon_data_url(id).await?,
|
||||
description: info.description.clone(),
|
||||
optional: info.optional,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(Self {
|
||||
title: manifest.title.clone(),
|
||||
icon: s9pk.icon_data_url().await?,
|
||||
@@ -91,10 +122,14 @@ impl PackageVersionInfo {
|
||||
upstream_repo: manifest.upstream_repo.clone(),
|
||||
support_site: manifest.support_site.clone(),
|
||||
marketing_site: manifest.marketing_site.clone(),
|
||||
donation_url: manifest.donation_url.clone(),
|
||||
alerts: manifest.alerts.clone(),
|
||||
dependency_metadata,
|
||||
os_version: manifest.os_version.clone(),
|
||||
hardware_requirements: manifest.hardware_requirements.clone(),
|
||||
source_version: None, // TODO
|
||||
s9pk: RegistryAsset {
|
||||
published_at: Utc::now(),
|
||||
url,
|
||||
commitment: s9pk.as_archive().commitment().await?,
|
||||
signatures: [(
|
||||
@@ -114,8 +149,11 @@ impl PackageVersionInfo {
|
||||
table.add_row(row![bc => &self.title]);
|
||||
table.add_row(row![br -> "VERSION", AsRef::<str>::as_ref(version)]);
|
||||
table.add_row(row![br -> "RELEASE NOTES", &self.release_notes]);
|
||||
table.add_row(row![br -> "ABOUT", &self.description.short]);
|
||||
table.add_row(row![br -> "DESCRIPTION", &self.description.long]);
|
||||
table.add_row(row![br -> "ABOUT", &textwrap::wrap(&self.description.short, 80).join("\n")]);
|
||||
table.add_row(row![
|
||||
br -> "DESCRIPTION",
|
||||
&textwrap::wrap(&self.description.long, 80).join("\n")
|
||||
]);
|
||||
table.add_row(row![br -> "GIT HASH", AsRef::<str>::as_ref(&self.git_hash)]);
|
||||
table.add_row(row![br -> "LICENSE", &self.license]);
|
||||
table.add_row(row![br -> "PACKAGE REPO", &self.wrapper_repo.to_string()]);
|
||||
|
||||
@@ -16,14 +16,21 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_display_serializable()
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("add", from_fn_async(add::add_package).no_cli())
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add::add_package)
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand("add", from_fn_async(add::cli_add_package).no_display())
|
||||
.subcommand(
|
||||
"get",
|
||||
from_fn_async(get::get_package)
|
||||
.with_metadata("get_device_info", Value::Bool(true))
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| {
|
||||
get::display_package_info(handle.params, result)
|
||||
}),
|
||||
})
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,35 @@ pub struct MerkleArchiveCommitment {
|
||||
#[ts(type = "number")]
|
||||
pub root_maxsize: u64,
|
||||
}
|
||||
impl MerkleArchiveCommitment {
|
||||
pub fn from_query(query: &str) -> Result<Option<Self>, Error> {
|
||||
let mut root_sighash = None;
|
||||
let mut root_maxsize = None;
|
||||
for (k, v) in form_urlencoded::parse(dbg!(query).as_bytes()) {
|
||||
match &*k {
|
||||
"rootSighash" => {
|
||||
root_sighash = Some(dbg!(v).parse()?);
|
||||
}
|
||||
"rootMaxsize" => {
|
||||
root_maxsize = Some(v.parse()?);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if root_sighash.is_some() || root_maxsize.is_some() {
|
||||
Ok(Some(Self {
|
||||
root_sighash: root_sighash
|
||||
.or_not_found("rootSighash required if rootMaxsize specified")
|
||||
.with_kind(ErrorKind::InvalidRequest)?,
|
||||
root_maxsize: root_maxsize
|
||||
.or_not_found("rootMaxsize required if rootSighash specified")
|
||||
.with_kind(ErrorKind::InvalidRequest)?,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Digestable for MerkleArchiveCommitment {
|
||||
fn update<D: Update>(&self, digest: &mut D) {
|
||||
digest.update(&*self.root_sighash);
|
||||
|
||||
@@ -5,6 +5,7 @@ use axum::body::Body;
|
||||
use axum::extract::Request;
|
||||
use digest::Update;
|
||||
use futures::TryStreamExt;
|
||||
use http::HeaderValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio_util::io::StreamReader;
|
||||
@@ -37,8 +38,8 @@ impl RequestCommitment {
|
||||
.append_pair("size", &self.size.to_string())
|
||||
.append_pair("blake3", &self.blake3.to_string());
|
||||
}
|
||||
pub fn from_query(url: &Url) -> Result<Self, Error> {
|
||||
let query: BTreeMap<_, _> = url.query_pairs().collect();
|
||||
pub fn from_query(query: &HeaderValue) -> Result<Self, Error> {
|
||||
let query: BTreeMap<_, _> = form_urlencoded::parse(query.as_bytes()).collect();
|
||||
Ok(Self {
|
||||
timestamp: query.get("timestamp").or_not_found("timestamp")?.parse()?,
|
||||
nonce: query.get("nonce").or_not_found("nonce")?.parse()?,
|
||||
|
||||
Reference in New Issue
Block a user