mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-11-28 01:01:53 +03:00
publish is now part of api module
This commit is contained in:
parent
5adb0ec2d0
commit
87aff4b715
84
leo/api.rs
84
leo/api.rs
@ -16,11 +16,20 @@
|
||||
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use reqwest::{
|
||||
blocking::{Client, Response},
|
||||
blocking::{multipart::Form, Client, Response},
|
||||
Method,
|
||||
StatusCode,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
/// Format to use.
|
||||
/// Default is JSON, but publish route uses FormData
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ContentType {
|
||||
JSON,
|
||||
FormData,
|
||||
}
|
||||
|
||||
/// API Routes and Request bodies.
|
||||
/// Structs that implement Route MUST also support Serialize to be usable in Api::run_route(r: Route)
|
||||
@ -35,6 +44,9 @@ pub trait Route {
|
||||
/// The URL path without the first forward slash (e.g. v1/package/fetch)
|
||||
const PATH: &'static str;
|
||||
|
||||
/// Content type: JSON or Multipart/FormData. Only usable in POST/PUT queries.
|
||||
const CONTENT_TYPE: ContentType;
|
||||
|
||||
/// The output type for this route. For example, the login route output is [`String`].
|
||||
/// But for other routes may be more complex.
|
||||
type Output;
|
||||
@ -42,6 +54,11 @@ pub trait Route {
|
||||
/// Process the reqwest Response and turn it into an Output.
|
||||
fn process(&self, res: Response) -> Result<Self::Output>;
|
||||
|
||||
/// Represent self as a form data for multipart (ContentType::FormData) requests.
|
||||
fn to_form(&self) -> Option<Form> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Transform specific status codes into correct errors for this route.
|
||||
/// For example 404 on package fetch should mean that 'Package is not found'
|
||||
fn status_to_err(&self, _status: StatusCode) -> Error {
|
||||
@ -95,7 +112,16 @@ impl Api {
|
||||
|
||||
// add body for POST and PUT requests
|
||||
if T::METHOD == Method::POST || T::METHOD == Method::PUT {
|
||||
res = res.json(&route);
|
||||
res = match T::CONTENT_TYPE {
|
||||
ContentType::JSON => res.json(&route),
|
||||
ContentType::FormData => {
|
||||
let form = route
|
||||
.to_form()
|
||||
.unwrap_or_else(|| unimplemented!("to_form is not implemented for this route"));
|
||||
|
||||
res.multipart(form)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// if Route::Auth is true and token is present - pass it
|
||||
@ -131,6 +157,7 @@ impl Route for Fetch {
|
||||
type Output = Response;
|
||||
|
||||
const AUTH: bool = true;
|
||||
const CONTENT_TYPE: ContentType = ContentType::JSON;
|
||||
const METHOD: Method = Method::POST;
|
||||
const PATH: &'static str = "api/package/fetch";
|
||||
|
||||
@ -167,6 +194,7 @@ impl Route for Login {
|
||||
type Output = Response;
|
||||
|
||||
const AUTH: bool = false;
|
||||
const CONTENT_TYPE: ContentType = ContentType::JSON;
|
||||
const METHOD: Method = Method::POST;
|
||||
const PATH: &'static str = "api/account/authenticate";
|
||||
|
||||
@ -188,6 +216,57 @@ impl Route for Login {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Publish {
|
||||
pub name: String,
|
||||
pub remote: String,
|
||||
pub version: String,
|
||||
pub file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PublishResponse {
|
||||
package_id: String,
|
||||
}
|
||||
|
||||
impl Route for Publish {
|
||||
type Output = String;
|
||||
|
||||
const AUTH: bool = true;
|
||||
const CONTENT_TYPE: ContentType = ContentType::FormData;
|
||||
const METHOD: Method = Method::POST;
|
||||
const PATH: &'static str = "api/package/publish";
|
||||
|
||||
fn to_form(&self) -> Option<Form> {
|
||||
Form::new()
|
||||
.text("name", self.name.clone())
|
||||
.text("remote", self.remote.clone())
|
||||
.text("version", self.version.clone())
|
||||
.file("file", self.file.clone())
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn process(&self, res: Response) -> Result<Self::Output> {
|
||||
let status = res.status();
|
||||
|
||||
if status == StatusCode::OK {
|
||||
let body: PublishResponse = res.json()?;
|
||||
Ok(body.package_id)
|
||||
} else {
|
||||
let res: HashMap<String, String> = res.json()?;
|
||||
Err(match status {
|
||||
StatusCode::BAD_REQUEST => anyhow!("{}", res.get("message").unwrap()),
|
||||
StatusCode::UNAUTHORIZED => anyhow!("You are not logged in. Please use `leo login` to login"),
|
||||
StatusCode::FAILED_DEPENDENCY => anyhow!("This package version is already published"),
|
||||
StatusCode::INTERNAL_SERVER_ERROR => {
|
||||
anyhow!("Server error, please contact us at https://github.com/AleoHQ/leo/issues")
|
||||
}
|
||||
_ => anyhow!("Unknown status code"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for 'my_profile' route. Meant to be used to get profile details but
|
||||
/// in the current application it is used to check if the user is logged in. Any non-200 response
|
||||
/// is treated as Unauthorized.
|
||||
@ -204,6 +283,7 @@ impl Route for Profile {
|
||||
type Output = Option<String>;
|
||||
|
||||
const AUTH: bool = true;
|
||||
const CONTENT_TYPE: ContentType = ContentType::JSON;
|
||||
const METHOD: Method = Method::GET;
|
||||
const PATH: &'static str = "api/account/my_profile";
|
||||
|
||||
|
@ -15,27 +15,15 @@
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::build::Build;
|
||||
use crate::{commands::Command, context::Context};
|
||||
use crate::{api::Publish as PublishRoute, commands::Command, context::Context};
|
||||
use leo_package::{
|
||||
outputs::OutputsDirectory,
|
||||
root::{ZipFile, AUTHOR_PLACEHOLDER},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use reqwest::{
|
||||
blocking::{multipart::Form, Client},
|
||||
header::{HeaderMap, HeaderValue},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use structopt::StructOpt;
|
||||
|
||||
pub const PUBLISH_URL: &str = "v1/package/publish";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ResponseJson {
|
||||
package_id: String,
|
||||
}
|
||||
|
||||
/// Publish package to Aleo Package Manager
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
@ -43,7 +31,7 @@ pub struct Publish {}
|
||||
|
||||
impl Command for Publish {
|
||||
type Input = <Build as Command>::Output;
|
||||
type Output = Option<String>;
|
||||
type Output = String;
|
||||
|
||||
/// Build program before publishing
|
||||
fn prelude(&self, context: Context) -> Result<Self::Input> {
|
||||
@ -90,58 +78,19 @@ impl Command for Publish {
|
||||
if zip_file.exists_at(&path) {
|
||||
tracing::debug!("Existing package zip file found. Clearing it to regenerate.");
|
||||
// Remove the existing package zip file
|
||||
ZipFile::new(&package_name).remove(&path)?;
|
||||
zip_file.remove(&path)?;
|
||||
}
|
||||
|
||||
zip_file.write(&path)?;
|
||||
|
||||
let form_data = Form::new()
|
||||
.text("name", package_name.clone())
|
||||
.text("remote", format!("{}/{}", package_remote.author, package_name))
|
||||
.text("version", package_version)
|
||||
.file("file", zip_file.get_file_path(&path))?;
|
||||
// Make an API request with zip file and package data.
|
||||
let package_id = context.api.run_route(PublishRoute {
|
||||
name: package_name.clone(),
|
||||
remote: format!("{}/{}", package_remote.author, package_name),
|
||||
version: package_version,
|
||||
file: zip_file.get_file_path(&path).into(),
|
||||
})?;
|
||||
|
||||
// Client for make POST request
|
||||
let client = Client::new();
|
||||
|
||||
let token = context
|
||||
.api
|
||||
.auth_token()
|
||||
.ok_or_else(|| anyhow!("Login before publishing package: try leo login --help"))?;
|
||||
|
||||
// Headers for request to publish package
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("{} {}", "Bearer", token)).unwrap(),
|
||||
);
|
||||
|
||||
// Make a request to publish a package
|
||||
let response = client
|
||||
.post(format!("{}{}", context.api.host(), PUBLISH_URL).as_str())
|
||||
.headers(headers)
|
||||
.multipart(form_data)
|
||||
.send();
|
||||
|
||||
// Get a response result
|
||||
let result: ResponseJson = match response {
|
||||
Ok(json_result) => {
|
||||
let text = json_result.text()?;
|
||||
|
||||
match serde_json::from_str(&text) {
|
||||
Ok(json) => json,
|
||||
Err(_) => {
|
||||
return Err(anyhow!("Package not published: {}", text));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("{:?}", error);
|
||||
return Err(anyhow!("Connection unavailable"));
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!("Package published successfully with id: {}", result.package_id);
|
||||
Ok(Some(result.package_id))
|
||||
tracing::info!("Package published successfully with id: {}", &package_id);
|
||||
Ok(package_id)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user