mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: give more info from registry api
This commit is contained in:
parent
83286ff758
commit
4f03155af0
6 changed files with 115 additions and 97 deletions
|
@ -1,5 +1,3 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use actix_web::{http::header::ACCEPT, web, HttpRequest, HttpResponse, Responder};
|
||||
use rusty_s3::{actions::GetObject, S3Action};
|
||||
use semver::Version;
|
||||
|
@ -38,38 +36,16 @@ impl<'de> Deserialize<'de> for VersionRequest {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TargetRequest {
|
||||
All,
|
||||
Specific(TargetKind),
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TargetRequest {
|
||||
fn deserialize<D>(deserializer: D) -> Result<TargetRequest, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if s.eq_ignore_ascii_case("all") {
|
||||
return Ok(TargetRequest::All);
|
||||
}
|
||||
|
||||
TargetKind::from_str(&s)
|
||||
.map(TargetRequest::Specific)
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_package_version(
|
||||
request: HttpRequest,
|
||||
app_state: web::Data<AppState>,
|
||||
path: web::Path<(PackageName, VersionRequest, TargetRequest)>,
|
||||
path: web::Path<(PackageName, VersionRequest, TargetKind)>,
|
||||
) -> Result<impl Responder, Error> {
|
||||
let (name, version, target) = path.into_inner();
|
||||
|
||||
let (scope, name_part) = name.as_str();
|
||||
|
||||
let versions: IndexFile = {
|
||||
let entries: IndexFile = {
|
||||
let source = app_state.source.lock().unwrap();
|
||||
|
||||
match source.read_file([scope, name_part], &app_state.project, None)? {
|
||||
|
@ -78,33 +54,17 @@ pub async fn get_package_version(
|
|||
}
|
||||
};
|
||||
|
||||
let mut versions = entries.iter().filter(|(v_id, _)| *v_id.target() == target);
|
||||
|
||||
let version = match version {
|
||||
VersionRequest::Latest => versions
|
||||
.iter()
|
||||
.filter(|(v_id, _)| match target {
|
||||
TargetRequest::All => true,
|
||||
TargetRequest::Specific(target) => *v_id.target() == target,
|
||||
})
|
||||
.max_by_key(|(v, _)| v.version().clone()),
|
||||
VersionRequest::Specific(version) => versions.iter().find(|(v, _)| {
|
||||
*v.version() == version
|
||||
&& match target {
|
||||
TargetRequest::All => true,
|
||||
TargetRequest::Specific(target) => *v.target() == target,
|
||||
}
|
||||
}),
|
||||
VersionRequest::Latest => versions.max_by_key(|(v, _)| v.version().clone()),
|
||||
VersionRequest::Specific(version) => versions.find(|(v, _)| *v.version() == version),
|
||||
};
|
||||
|
||||
let Some((v_id, entry)) = version else {
|
||||
return Ok(HttpResponse::NotFound().finish());
|
||||
};
|
||||
|
||||
let other_targets = versions
|
||||
.iter()
|
||||
.filter(|(v, _)| v.version() == v_id.version() && v.target() != v_id.target())
|
||||
.map(|(v_id, _)| v_id.target().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if request
|
||||
.headers()
|
||||
.get(ACCEPT)
|
||||
|
@ -130,20 +90,19 @@ pub async fn get_package_version(
|
|||
));
|
||||
}
|
||||
|
||||
let entry = entry.clone();
|
||||
|
||||
let mut response = serde_json::to_value(PackageResponse {
|
||||
Ok(HttpResponse::Ok().json(PackageResponse {
|
||||
name: name.to_string(),
|
||||
version: v_id.version().to_string(),
|
||||
target: entry.target.into(),
|
||||
description: entry.description.unwrap_or_default(),
|
||||
published_at: entry.published_at,
|
||||
license: entry.license.unwrap_or_default(),
|
||||
})?;
|
||||
|
||||
if !other_targets.is_empty() {
|
||||
response["other_targets"] = serde_json::to_value(other_targets)?;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
targets: entries
|
||||
.values()
|
||||
.map(|entry| (&entry.target).into())
|
||||
.collect(),
|
||||
description: entry.description.clone().unwrap_or_default(),
|
||||
published_at: entries
|
||||
.values()
|
||||
.max_by_key(|entry| entry.published_at)
|
||||
.unwrap()
|
||||
.published_at,
|
||||
license: entry.license.clone().unwrap_or_default(),
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use actix_web::{web, HttpResponse, Responder};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::{error::Error, package::PackageResponse, AppState};
|
||||
use pesde::{
|
||||
|
@ -27,7 +28,7 @@ pub async fn get_package_versions(
|
|||
.map(|(v_id, entry)| PackageResponse {
|
||||
name: name.to_string(),
|
||||
version: v_id.version().to_string(),
|
||||
target: entry.target.into(),
|
||||
targets: BTreeSet::from([entry.target.into()]),
|
||||
description: entry.description.unwrap_or_default(),
|
||||
published_at: entry.published_at,
|
||||
license: entry.license.unwrap_or_default(),
|
||||
|
|
|
@ -26,7 +26,7 @@ use pesde::{
|
|||
use crate::{
|
||||
auth::UserId,
|
||||
benv,
|
||||
error::Error,
|
||||
error::{Error, ErrorResponse},
|
||||
package::{s3_name, S3_SIGN_DURATION},
|
||||
search::update_version,
|
||||
AppState,
|
||||
|
@ -208,6 +208,22 @@ pub async fn publish_package(
|
|||
dependencies,
|
||||
};
|
||||
|
||||
let this_version = entries
|
||||
.keys()
|
||||
.find(|v_id| *v_id.version() == manifest.version);
|
||||
if let Some(this_version) = this_version {
|
||||
let other_entry = entries.get(this_version).unwrap();
|
||||
|
||||
if other_entry.description != new_entry.description
|
||||
|| other_entry.license != new_entry.license
|
||||
{
|
||||
return Ok(HttpResponse::BadRequest().json(ErrorResponse {
|
||||
error: "same version with different description or license already exists"
|
||||
.to_string(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if entries
|
||||
.insert(
|
||||
VersionId::new(manifest.version.clone(), manifest.target.kind()),
|
||||
|
|
|
@ -2,14 +2,15 @@ use std::collections::HashMap;
|
|||
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use serde::Deserialize;
|
||||
use tantivy::{query::AllQuery, schema::Value, DateTime, Order};
|
||||
use tantivy::{collector::Count, query::AllQuery, schema::Value, DateTime, Order};
|
||||
|
||||
use crate::{error::Error, package::PackageResponse, AppState};
|
||||
use pesde::{
|
||||
names::PackageName,
|
||||
source::{git_index::GitBasedSource, pesde::IndexFile},
|
||||
};
|
||||
|
||||
use crate::{error::Error, package::PackageResponse, AppState};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Request {
|
||||
#[serde(default)]
|
||||
|
@ -46,12 +47,15 @@ pub async fn search_packages(
|
|||
query_parser.parse_query(query)?
|
||||
};
|
||||
|
||||
let top_docs = searcher
|
||||
let (count, top_docs) = searcher
|
||||
.search(
|
||||
&query,
|
||||
&tantivy::collector::TopDocs::with_limit(50)
|
||||
&(
|
||||
Count,
|
||||
tantivy::collector::TopDocs::with_limit(50)
|
||||
.and_offset(request.offset.unwrap_or_default())
|
||||
.order_by_fast_field::<DateTime>("published_at", Order::Desc),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -71,7 +75,7 @@ pub async fn search_packages(
|
|||
.unwrap();
|
||||
let (scope, name) = id.as_str();
|
||||
|
||||
let mut versions: IndexFile = toml::de::from_str(
|
||||
let versions: IndexFile = toml::de::from_str(
|
||||
&source
|
||||
.read_file([scope, name], &app_state.project, None)
|
||||
.unwrap()
|
||||
|
@ -79,18 +83,32 @@ pub async fn search_packages(
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let (version_id, entry) = versions.pop_last().unwrap();
|
||||
let (latest_version, entry) = versions
|
||||
.iter()
|
||||
.max_by_key(|(v_id, _)| v_id.version())
|
||||
.unwrap();
|
||||
|
||||
PackageResponse {
|
||||
name: id.to_string(),
|
||||
version: version_id.version().to_string(),
|
||||
target: entry.target.into(),
|
||||
description: entry.description.unwrap_or_default(),
|
||||
published_at: entry.published_at,
|
||||
license: entry.license.unwrap_or_default(),
|
||||
version: latest_version.version().to_string(),
|
||||
targets: versions
|
||||
.iter()
|
||||
.filter(|(v_id, _)| v_id.version() == latest_version.version())
|
||||
.map(|(_, entry)| (&entry.target).into())
|
||||
.collect(),
|
||||
description: entry.description.clone().unwrap_or_default(),
|
||||
published_at: versions
|
||||
.values()
|
||||
.max_by_key(|entry| entry.published_at)
|
||||
.unwrap()
|
||||
.published_at,
|
||||
license: entry.license.clone().unwrap_or_default(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(top_docs))
|
||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||
"data": top_docs,
|
||||
"count": count,
|
||||
})))
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use pesde::{
|
|||
source::version_id::VersionId,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use std::{collections::BTreeSet, time::Duration};
|
||||
|
||||
pub const S3_SIGN_DURATION: Duration = Duration::from_secs(60 * 60);
|
||||
|
||||
|
@ -13,7 +13,7 @@ pub fn s3_name(name: &PackageName, version_id: &VersionId) -> String {
|
|||
format!("{}+{}.tar.gz", name.escaped(), version_id.escaped())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct TargetInfo {
|
||||
kind: TargetKind,
|
||||
lib: bool,
|
||||
|
@ -30,11 +30,33 @@ impl From<Target> for TargetInfo {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&Target> for TargetInfo {
|
||||
fn from(target: &Target) -> Self {
|
||||
TargetInfo {
|
||||
kind: target.kind(),
|
||||
lib: target.lib_path().is_some(),
|
||||
bin: target.bin_path().is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TargetInfo {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.kind.cmp(&other.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for TargetInfo {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PackageResponse {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub target: TargetInfo,
|
||||
pub targets: BTreeSet<TargetInfo>,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
pub description: String,
|
||||
pub published_at: DateTime<Utc>,
|
||||
|
|
|
@ -406,34 +406,24 @@ impl PublishCommand {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
match reqwest
|
||||
let response = reqwest
|
||||
.post(format!("{}/v0/packages", config.api()))
|
||||
.multipart(reqwest::blocking::multipart::Form::new().part(
|
||||
"tarball",
|
||||
reqwest::blocking::multipart::Part::bytes(archive).file_name("package.tar.gz"),
|
||||
))
|
||||
.send()
|
||||
.context("failed to send request")?
|
||||
.error_for_status()
|
||||
.and_then(|response| response.text())
|
||||
{
|
||||
Ok(response) => {
|
||||
println!("{response}");
|
||||
.context("failed to send request")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e)
|
||||
if e.status()
|
||||
.is_some_and(|status| status == StatusCode::CONFLICT) =>
|
||||
{
|
||||
let status = response.status();
|
||||
let text = response.text().context("failed to get response text")?;
|
||||
match status {
|
||||
StatusCode::CONFLICT => {
|
||||
println!("{}", "package version already exists".red().bold());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e)
|
||||
if e.status()
|
||||
.is_some_and(|status| status == StatusCode::FORBIDDEN) =>
|
||||
{
|
||||
StatusCode::FORBIDDEN => {
|
||||
println!(
|
||||
"{}",
|
||||
"unauthorized to publish under this scope".red().bold()
|
||||
|
@ -441,7 +431,19 @@ impl PublishCommand {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e).context("failed to get response"),
|
||||
StatusCode::BAD_REQUEST => {
|
||||
println!("{}: {text}", "invalid package".red().bold());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
code if !code.is_success() => {
|
||||
anyhow::bail!("failed to publish package: {code} ({text})");
|
||||
}
|
||||
_ => {
|
||||
println!("{text}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue