diff --git a/fplus-http-server/src/router/application.rs b/fplus-http-server/src/router/application.rs index 164aab52..db26f232 100644 --- a/fplus-http-server/src/router/application.rs +++ b/fplus-http-server/src/router/application.rs @@ -6,7 +6,7 @@ use fplus_lib::core::{ #[post("/application")] pub async fn create(info: web::Json) -> impl Responder { - match LDNApplication::new(info.into_inner()).await { + match LDNApplication::new_from_issue(info.into_inner()).await { Ok(app) => HttpResponse::Ok().body(format!( "Created new application for issue: {}", app.application_id.clone() @@ -37,13 +37,15 @@ pub async fn trigger( return HttpResponse::BadRequest().body(e.to_string()); } }; + dbg!(&ldn_application); match ldn_application .complete_governance_review(info.into_inner()) .await { Ok(app) => HttpResponse::Ok().body(serde_json::to_string_pretty(&app).unwrap()), - Err(_) => { - return HttpResponse::BadRequest().body("Application is not in the correct state"); + Err(e) => { + return HttpResponse::BadRequest() + .body(format!("Application is not in the correct state {}", e)); } } } @@ -81,6 +83,7 @@ pub async fn approve( return HttpResponse::BadRequest().body(e.to_string()); } }; + dbg!(&ldn_application); match ldn_application .complete_new_application_approval(info.into_inner()) .await diff --git a/fplus-lib/src/base64.rs b/fplus-lib/src/base64.rs index 662ad382..284a1217 100644 --- a/fplus-lib/src/base64.rs +++ b/fplus-lib/src/base64.rs @@ -2,7 +2,7 @@ use std::io::Cursor; use base64; -use crate::core::application::ApplicationFile; +use crate::core::application::file::ApplicationFile; pub fn decode(i: &str) -> Option { let mut binding = Cursor::new(i); diff --git a/fplus-lib/src/core/application/allocation.rs b/fplus-lib/src/core/application/allocation.rs new file mode 100644 index 00000000..40cc7cfb --- /dev/null +++ b/fplus-lib/src/core/application/allocation.rs @@ -0,0 +1,128 @@ +use chrono::Utc; + +use super::file::{ + Allocation, AllocationRequest, AllocationRequestType, Allocations, Notaries, Notary, +}; + +impl Default for Notaries { + fn default() -> Self { + Self(vec![]) + } +} + +impl Notaries { + pub fn add(&self, signer: Notary) -> Self { + let mut res = self.0.clone(); + res.push(signer); + Self(res) + } +} + +impl AllocationRequest { + pub fn new( + actor: String, + id: String, + kind: AllocationRequestType, + allocation_amount: String, + ) -> Self { + Self { + actor, + id, + kind, + allocation_amount, + is_active: true, + } + } +} + +impl Allocation { + pub fn new(request_information: AllocationRequest) -> Self { + Self { + id: request_information.id, + request_type: request_information.kind, + created_at: Utc::now().to_string(), + updated_at: Utc::now().to_string(), + is_active: true, + amount: request_information.allocation_amount, + signers: Notaries::default(), + } + } +} + +impl Default for Allocations { + fn default() -> Self { + Self(vec![]) + } +} + +impl Allocations { + pub fn init(request_information: AllocationRequest) -> Self { + let allocation = Allocation::new(request_information); + Self(vec![allocation]) + } + + // should be changed to option + pub fn find_one(&self, request_id: String) -> Option { + let curr: Vec = self.0.clone(); + let mut allocation: Option = None; + for alloc in curr.iter() { + if alloc.id == request_id { + allocation = Some(alloc.clone()); + break; + } + } + allocation + } + + // should be changed to option + pub fn is_active(&self, request_id: String) -> bool { + let curr: Vec = self.0.clone(); + let mut is_active = false; + for alloc in curr.iter() { + if alloc.id == request_id { + is_active = alloc.is_active; + break; + } + } + is_active + } + + pub fn add_signer(&self, request_id: String, signer: Notary) -> Self { + let mut res: Vec = self.0.clone(); + for allocation in res.iter_mut() { + if allocation.id == request_id && allocation.is_active { + allocation.signers = allocation.signers.add(signer); + break; + } + } + Self(res) + } + + pub fn add_signer_and_complete(&self, request_id: String, signer: Notary) -> Self { + let mut res: Vec = self.0.clone(); + for allocation in res.iter_mut() { + if allocation.id == request_id && allocation.is_active { + allocation.signers = allocation.signers.add(signer); + allocation.is_active = false; + break; + } + } + Self(res) + } + + pub fn complete_allocation(&self, request_id: String) -> Self { + let mut res: Vec = self.0.clone(); + for allocation in res.iter_mut() { + if allocation.id == request_id && allocation.is_active { + allocation.is_active = false; + } + } + Self(res) + } + + pub fn push(&mut self, request: AllocationRequest) -> Self { + let allocation = Allocation::new(request); + self.0.push(allocation); + self.clone() + } +} diff --git a/fplus-lib/src/core/application/allocations.rs b/fplus-lib/src/core/application/allocations.rs deleted file mode 100644 index 0d8b5317..00000000 --- a/fplus-lib/src/core/application/allocations.rs +++ /dev/null @@ -1,169 +0,0 @@ -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ApplicationAllocationsSigner { - pub signing_address: String, - pub time_of_signature: String, - pub message_cid: String, - pub username: String, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ApplicationAllocationsSigners(Vec); - -impl Default for ApplicationAllocationsSigners { - fn default() -> Self { - Self(vec![]) - } -} - -impl ApplicationAllocationsSigners { - pub fn add(&self, signer: ApplicationAllocationsSigner) -> Self { - let mut res = self.0.clone(); - res.push(signer); - Self(res) - } -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub enum ApplicationAllocationTypes { - New, - Removal, - Refill, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct AllocationRequest { - pub actor: String, - pub id: String, - pub request_type: ApplicationAllocationTypes, - pub client_address: String, - pub created_at: String, - pub is_active: bool, - pub allocation_amount: String, -} - -impl AllocationRequest { - pub fn new( - actor: String, - id: String, - request_type: ApplicationAllocationTypes, - client_address: String, - created_at: String, - allocation_amount: String, - ) -> Self { - Self { - actor, - id, - request_type, - client_address, - created_at, - allocation_amount, - is_active: true, - } - } -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ApplicationAllocation { - pub request_information: AllocationRequest, - pub signers: ApplicationAllocationsSigners, -} - -impl ApplicationAllocation { - pub fn new(request_information: AllocationRequest) -> Self { - Self { - request_information, - signers: ApplicationAllocationsSigners::default(), - } - } -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ApplicationAllocations(Vec); - -impl Default for ApplicationAllocations { - fn default() -> Self { - Self(vec![]) - } -} - -impl ApplicationAllocations { - pub fn new(&self, request_information: AllocationRequest) -> Self { - let allocation = ApplicationAllocation::new(request_information); - Self(vec![allocation]) - } - - // should be changed to option - pub fn find_one(&self, request_id: String) -> Option { - let curr: Vec = self.0.clone(); - let mut allocation: Option = None; - for alloc in curr.iter() { - if alloc.request_information.id == request_id { - allocation = Some(alloc.clone()); - break; - } - } - allocation - } - - // should be changed to option - pub fn is_active(&self, request_id: String) -> bool { - let curr: Vec = self.0.clone(); - let mut is_active = false; - for alloc in curr.iter() { - if alloc.request_information.id == request_id { - is_active = alloc.request_information.is_active; - break; - } - } - is_active - } - - pub fn add_signer(&self, request_id: String, signer: ApplicationAllocationsSigner) -> Self { - let mut res: Vec = self.0.clone(); - for allocation in res.iter_mut() { - if allocation.request_information.id == request_id - && allocation.request_information.is_active - { - allocation.signers = allocation.signers.add(signer); - break; - } - } - Self(res) - } - - pub fn add_signer_and_complete( - &self, - request_id: String, - signer: ApplicationAllocationsSigner, - ) -> Self { - let mut res: Vec = self.0.clone(); - for allocation in res.iter_mut() { - if allocation.request_information.id == request_id - && allocation.request_information.is_active - { - allocation.signers = allocation.signers.add(signer); - allocation.request_information.is_active = false; - break; - } - } - Self(res) - } - - pub fn complete_allocation(&self, request_id: String) -> Self { - let mut res: Vec = self.0.clone(); - for allocation in res.iter_mut() { - if allocation.request_information.id == request_id - && allocation.request_information.is_active - { - allocation.request_information.is_active = false; - } - } - Self(res) - } - - pub fn add_new_request(&mut self, request: AllocationRequest) -> Self { - let allocation = ApplicationAllocation::new(request); - self.0.push(allocation); - self.clone() - } -} diff --git a/fplus-lib/src/core/application/client.rs b/fplus-lib/src/core/application/client.rs new file mode 100644 index 00000000..87ea11b3 --- /dev/null +++ b/fplus-lib/src/core/application/client.rs @@ -0,0 +1,24 @@ +use super::file::Client; + +impl Client { + fn new(i: Client) -> Self { + Self { ..i } + } + + fn validate(&self) -> bool { + let Client { + name, + region, + industry, + website, + social_media, + role, + } = self; + name.len() > 0 + && region.len() > 0 + && industry.len() > 0 + && website.len() > 0 + && social_media.len() > 0 + && role.len() > 0 + } +} diff --git a/fplus-lib/src/core/application/core_info.rs b/fplus-lib/src/core/application/core_info.rs deleted file mode 100644 index 9b2faeb9..00000000 --- a/fplus-lib/src/core/application/core_info.rs +++ /dev/null @@ -1,61 +0,0 @@ -use super::{allocations::ApplicationAllocations, lifecycle::ApplicationLifecycle}; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ApplicationInfo { - pub core_information: ApplicationCoreInfo, - pub application_lifecycle: ApplicationLifecycle, - pub datacap_allocations: ApplicationAllocations, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ApplicationCoreInfo { - pub data_owner_name: String, - pub data_owner_github_handle: String, - pub data_owner_region: String, - pub data_owner_industry: String, - pub data_owner_address: String, - pub datacap_weekly_allocation: String, - pub requested_amount: String, - pub website: String, - pub social_media: String, -} - -impl ApplicationCoreInfo { - pub fn new( - data_owner_name: String, - data_owner_region: String, - data_owner_github_handle: String, - data_owner_industry: String, - data_owner_address: String, - requested_amount: String, - datacap_weekly_allocation: String, - website: String, - social_media: String, - ) -> Self { - ApplicationCoreInfo { - data_owner_name, - data_owner_region, - data_owner_github_handle, - data_owner_address, - requested_amount, - datacap_weekly_allocation, - data_owner_industry, - website, - social_media, - } - } -} - -impl ApplicationInfo { - pub fn new( - core_information: ApplicationCoreInfo, - application_lifecycle: ApplicationLifecycle, - datacap_allocations: ApplicationAllocations, - ) -> Self { - ApplicationInfo { - core_information, - application_lifecycle, - datacap_allocations, - } - } -} diff --git a/fplus-lib/src/core/application/datacap.rs b/fplus-lib/src/core/application/datacap.rs new file mode 100644 index 00000000..ab84b7fc --- /dev/null +++ b/fplus-lib/src/core/application/datacap.rs @@ -0,0 +1,7 @@ +use super::file::Datacap; + +impl Datacap { + fn new(i: Datacap) -> Self { + Self { ..i } + } +} diff --git a/fplus-lib/src/core/application/file.rs b/fplus-lib/src/core/application/file.rs new file mode 100644 index 00000000..5f9c88d1 --- /dev/null +++ b/fplus-lib/src/core/application/file.rs @@ -0,0 +1,322 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum DatacapGroup { + #[serde(rename = "da")] + DA, + #[serde(rename = "ldn-v3")] + LDN, + #[serde(rename = "e-fil")] + EFIL, +} + +impl FromStr for DatacapGroup { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "da" => Ok(Self::DA), + "ldn-v3" => Ok(Self::LDN), + "e-fil" => Ok(Self::EFIL), + _ => Err(format!("{} is not a valid datacap group", s)), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ApplicationFile { + #[serde(rename = "Version")] + pub version: u8, + #[serde(rename = "ID")] + pub id: String, + #[serde(rename = "Issue Number")] + pub issue_number: String, + #[serde(rename = "Client")] + pub client: Client, + #[serde(rename = "Project")] + pub project: Project, + #[serde(rename = "Datacap")] + pub datacap: Datacap, + #[serde(rename = "Lifecycle")] + pub lifecycle: LifeCycle, + #[serde(rename = "Allocation Requests")] + pub allocation: Allocations, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct Client { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Region")] + pub region: String, + #[serde(rename = "Industry")] + pub industry: String, + #[serde(rename = "Website")] + pub website: String, + #[serde(rename = "Social Media")] + pub social_media: String, + #[serde(rename = "Role")] + pub role: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Datacap { + #[serde(rename = "Type")] + pub _group: DatacapGroup, + #[serde(rename = "Data Type")] + pub data_type: DataType, + #[serde(rename = "Total requested amount")] + pub total_requested_amount: String, + #[serde(rename = "Single size dataset")] + pub single_size_dataset: String, + #[serde(rename = "Replicas")] + pub replicas: u8, + #[serde(rename = "Weekly Allocation")] + pub weekly_allocation: String, +} + +impl Default for Datacap { + fn default() -> Self { + Self { + _group: DatacapGroup::LDN, + data_type: DataType::Slingshot, + total_requested_amount: "0".to_string(), + single_size_dataset: "0".to_string(), + replicas: 0, + weekly_allocation: "0".to_string(), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum DataType { + #[serde(rename = "Slingshot")] + Slingshot, + #[serde(rename = "Public, Open Dataset (Research/Non-Profit)")] + PublicOpenDatasetResearchNonProfit, + #[serde(rename = "Public, Open Commercial/Enterprise")] + PublicOpenCommercialEnterprise, + #[serde(rename = "Private Commercial/Enterprise")] + PrivateCommercialEnterprise, + #[serde(rename = "Private Non-Profit / Social impact")] + PrivateNonProfitSocialImpact, +} + +impl FromStr for DataType { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "Slingshot" => Ok(Self::Slingshot), + "Public, Open Dataset (Research/Non-Profit)" => { + Ok(Self::PublicOpenDatasetResearchNonProfit) + } + "Public, Open Commercial/Enterprise" => Ok(Self::PublicOpenCommercialEnterprise), + "Private Commercial/Enterprise" => Ok(Self::PrivateCommercialEnterprise), + "Private Non-Profit / Social impact" => Ok(Self::PrivateNonProfitSocialImpact), + _ => Err(format!("{} is not a valid data type", s)), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum AssociatedProjects { + Yes(String), + No, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum PublicDataset { + Yes, + No(String), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum RetrivalFrequency { + Daily, + Weekly, + Monthly, + Yearly, + Sporadic, + Never, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum StorageModularity { + IPFS, + Lotus, + Singularity, + Graphsplit, + Other(String), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum StorageProviders { + AWSCloud, + GoogleCloud, + AzureCloud, + InternalStorage, + Other(String), +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct Project { + #[serde(rename = "Project Id")] + pub project_id: String, + #[serde(rename = "Brief history of your project and organization")] + pub history: String, + #[serde(rename = "Is this project associated with other projects/ecosystem stakeholders?")] + pub associated_projects: String, + #[serde(rename = "Describe the data being stored onto Filecoin")] + pub stored_data_desc: String, + #[serde(rename = "Where was the data currently stored in this dataset sourced from} ")] + pub previous_stoarge: String, + #[serde(rename = "How do you plan to prepare the dataset")] + pub dataset_prepare: String, + #[serde( + rename = "Please share a sample of the data (a link to a file, an image, a table, etc., are good ways to do this.)" + )] + pub data_sample_link: String, + #[serde( + rename = "Confirm that this is a public dataset that can be retrieved by anyone on the network (i.e., no specific permissions or access rights are required to view the data)" + )] + pub public_dataset: String, + #[serde(rename = "What is the expected retrieval frequency for this data")] + pub retrival_frequency: String, + #[serde(rename = "For how long do you plan to keep this dataset stored on Filecoin")] + pub dataset_life_span: String, + #[serde(rename = "In which geographies do you plan on making storage deals")] + pub geographis: String, + #[serde(rename = "How will you be distributing your data to storage providers")] + pub distribution: String, + #[serde( + rename = "Please list the provider IDs and location of the storage providers you will be working with. Note that it is a requirement to list a minimum of 5 unique provider IDs, and that your client address will be verified against this list in the future" + )] + pub providers: String, + #[serde( + rename = "Can you confirm that you will follow the Fil+ guideline (Data owner should engage at least 4 SPs and no single SP ID should receive >30% of a client's allocated DataCap)" + )] + pub filplus_guideline: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum DatasetLifeSpan { + LessThanAYear, + OneToOneAndHalfYears, + OneAndHalfToTwoYears, + TwoToThreeYears, + MoreThanThreeYears, + Permanently, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Provider { + #[serde(rename = "ID")] + pub id: String, + #[serde(rename = "Location")] + pub location: String, + #[serde(rename = "SPOrg")] + pub spo_org: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum AppState { + Submitted, + ReadyToSign, + StartSignDatacap, + Granted, + TotalDatacapReached, + Error, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct LifeCycle { + #[serde(rename = "State")] + pub state: AppState, + #[serde(rename = "Validated At")] + pub validated_at: String, + #[serde(rename = "Validated By")] + pub validated_by: String, + #[serde(rename = "Active")] + pub is_active: bool, + #[serde(rename = "Updated At")] + pub updated_at: String, + #[serde(rename = "Active Request ID")] + pub active_request: Option, + #[serde(rename = "On Chain Address")] + pub client_on_chain_address: String, + #[serde(rename = "Multisig Address")] + pub multisig_address: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Allocations(pub Vec); + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub enum AllocationRequestType { + First, + Removal, + Refill(u8), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Allocation { + #[serde(rename = "ID")] + pub id: String, + #[serde(rename = "Request Type")] + pub request_type: AllocationRequestType, + #[serde(rename = "Created At")] + pub created_at: String, + #[serde(rename = "Updated At")] + pub updated_at: String, + #[serde(rename = "Active")] + pub is_active: bool, + #[serde(rename = "Allocation Amount")] + pub amount: String, + #[serde(rename = "Signers")] + pub signers: Notaries, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Notaries(pub Vec); + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct NotaryInput { + pub github_username: String, + pub signing_address: String, + pub created_at: String, + pub message_cid: String, +} + +impl From for Notary { + fn from(input: NotaryInput) -> Self { + Self { + github_username: input.github_username, + signing_address: input.signing_address, + created_at: input.created_at, + message_cid: input.message_cid, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Notary { + #[serde(rename = "Github Username")] + pub github_username: String, + #[serde(rename = "Signing Address")] + pub signing_address: String, + #[serde(rename = "Created At")] + pub created_at: String, + #[serde(rename = "Message CID")] + pub message_cid: String, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct AllocationRequest { + pub actor: String, + pub id: String, + pub kind: AllocationRequestType, + pub is_active: bool, + pub allocation_amount: String, +} diff --git a/fplus-lib/src/core/application/lifecycle.rs b/fplus-lib/src/core/application/lifecycle.rs index de0bbb28..a047dcb1 100644 --- a/fplus-lib/src/core/application/lifecycle.rs +++ b/fplus-lib/src/core/application/lifecycle.rs @@ -1,107 +1,86 @@ use chrono::prelude::*; -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ApplicationLifecycle { - state: ApplicationFileState, - pub validated_time: String, - pub validated_by: String, - pub first_allocation_time: String, - pub is_active: bool, - pub time_of_new_state: String, - pub current_allocation_id: Option, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] -pub enum ApplicationFileState { - Validation, - GovernanceReview, - Proposal, - Approval, - Confirmed, -} +use super::file::{AppState, LifeCycle}; -impl ApplicationFileState { +impl AppState { pub fn as_str(&self) -> &str { match *self { - ApplicationFileState::Validation => "Validation", - ApplicationFileState::GovernanceReview => "Governance Review", - ApplicationFileState::Proposal => "Proposal", - ApplicationFileState::Approval => "Approval", - ApplicationFileState::Confirmed => "Confirmed", + AppState::Submitted => "Submitted", + AppState::ReadyToSign => "Ready to Sign Datacap", + AppState::StartSignDatacap => "Start Sign Datacap", + AppState::Granted => "Granted", + AppState::TotalDatacapReached => "Total Datacap Reached", + AppState::Error => "Error", } } } -impl ApplicationLifecycle { - pub fn governance_review_state(current_allocation_id: Option) -> Self { - ApplicationLifecycle { - state: ApplicationFileState::GovernanceReview, - validated_time: Utc::now().to_string(), - first_allocation_time: "".to_string(), - validated_by: "".to_string(), +impl LifeCycle { + pub fn submitted(client_on_chain_address: String, multisig_address: String) -> Self { + let empty = "".to_string(); + LifeCycle { + state: AppState::Submitted, + validated_at: empty.clone(), + validated_by: empty.clone(), is_active: true, - time_of_new_state: Utc::now().to_string(), - current_allocation_id, + updated_at: Utc::now().to_string(), + active_request: None, + client_on_chain_address, + multisig_address, } } /// Change Application state to Proposal from Governance Review /// Actor input is the actor who is changing the state - pub fn set_proposal_state(&self, actor: String, current_allocation_id: Option) -> Self { - ApplicationLifecycle { - state: ApplicationFileState::Proposal, - first_allocation_time: "".to_string(), + pub fn finish_governance_review(&self, actor: String, current_allocation_id: String) -> Self { + LifeCycle { + state: AppState::ReadyToSign, validated_by: actor, - is_active: true, - time_of_new_state: Utc::now().to_string(), - current_allocation_id, + validated_at: Utc::now().to_string(), + updated_at: Utc::now().to_string(), + active_request: Some(current_allocation_id), ..self.clone() } } - pub fn set_refill_proposal_state(&self, current_allocation_id: Option) -> Self { - ApplicationLifecycle { - state: ApplicationFileState::Proposal, - is_active: true, - time_of_new_state: Utc::now().to_string(), - current_allocation_id, + pub fn finish_proposal(&self) -> Self { + LifeCycle { + state: AppState::StartSignDatacap, + updated_at: Utc::now().to_string(), ..self.clone() } } - pub fn set_approval_state(&self, current_allocation_id: Option) -> Self { - ApplicationLifecycle { - state: ApplicationFileState::Approval, - is_active: true, - time_of_new_state: Utc::now().to_string(), - current_allocation_id, + pub fn finish_approval(&self) -> Self { + LifeCycle { + state: AppState::Granted, + updated_at: Utc::now().to_string(), ..self.clone() } } - pub fn set_confirmed_state(&self, current_allocation_id: Option) -> Self { - ApplicationLifecycle { - state: ApplicationFileState::Confirmed, - first_allocation_time: Utc::now().to_string(), - is_active: true, - time_of_new_state: Utc::now().to_string(), - current_allocation_id, - ..self.clone() - } - } - - pub fn get_state(&self) -> ApplicationFileState { + pub fn get_state(&self) -> AppState { let res = self.state.clone(); res } + pub fn start_refill_request(&self, request_id: String) -> Self { + LifeCycle { + state: AppState::ReadyToSign, + updated_at: Utc::now().to_string(), + active_request: Some(request_id), + ..self.clone() + } + } + pub fn get_active_allocation_id(self) -> Option { - self.current_allocation_id + self.active_request } pub fn reached_total_datacap(self) -> Self { - ApplicationLifecycle { + LifeCycle { is_active: false, + active_request: None, ..self } } diff --git a/fplus-lib/src/core/application/mod.rs b/fplus-lib/src/core/application/mod.rs index d3d736f2..6e74457c 100644 --- a/fplus-lib/src/core/application/mod.rs +++ b/fplus-lib/src/core/application/mod.rs @@ -1,162 +1,95 @@ -use serde::{Serialize, Deserialize}; +use self::file::{AllocationRequest, Allocations, LifeCycle, Notary}; -use self::{ - allocations::{AllocationRequest, ApplicationAllocationTypes, ApplicationAllocationsSigner}, - core_info::ApplicationInfo, - lifecycle::ApplicationLifecycle, -}; +pub mod allocation; +pub mod client; +pub mod datacap; +pub mod file; +pub mod lifecycle; +pub mod project; -pub(crate) mod allocations; -pub(crate) mod core_info; -pub(crate) mod lifecycle; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ApplicationType { - DA, - LDN, - EFIL, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ApplicationFile { - pub id: String, - pub _type: ApplicationType, - pub info: ApplicationInfo, -} - -impl ApplicationFile { - pub async fn new(app_info: ApplicationInfo, application_id: String) -> Self { - ApplicationFile { - id: application_id, - _type: ApplicationType::LDN, - info: app_info, +impl file::ApplicationFile { + pub async fn new( + issue_number: String, + multisig_address: String, + version: u8, + id: String, + client: file::Client, + project: file::Project, + datacap: file::Datacap, + ) -> Self { + let allocation = Allocations::default(); + let lifecycle = LifeCycle::submitted(id.clone(), multisig_address.clone()); + Self { + version, + issue_number, + id, + client, + project, + datacap, + lifecycle, + allocation, } } pub fn reached_total_datacap(&self) -> Self { - let new_life_cycle = self - .info - .application_lifecycle - .clone() - .reached_total_datacap(); - let info = ApplicationInfo { - core_information: self.info.core_information.clone(), - application_lifecycle: new_life_cycle, - datacap_allocations: self.info.datacap_allocations.clone(), - }; - ApplicationFile { - id: self.id.clone(), - _type: self._type.clone(), - info, + let new_life_cycle = self.lifecycle.clone().reached_total_datacap(); + Self { + lifecycle: new_life_cycle, + ..self.clone() } } - - pub fn complete_governance_review(&self, actor: String, request_id: String) -> Self { + pub fn complete_governance_review(&self, actor: String, request: AllocationRequest) -> Self { let new_life_cycle = self - .info - .application_lifecycle + .lifecycle .clone() - .set_proposal_state(actor, Some(request_id)); - let info = ApplicationInfo { - core_information: self.info.core_information.clone(), - application_lifecycle: new_life_cycle, - datacap_allocations: self.info.datacap_allocations.clone(), - }; - ApplicationFile { - id: self.id.clone(), - _type: self._type.clone(), - info, + .finish_governance_review(actor, request.id.clone()); + let allocations = Allocations::init(request.clone()); + Self { + lifecycle: new_life_cycle, + allocation: allocations, + ..self.clone() } } - pub fn start_new_allocation(&self, request: AllocationRequest) -> Self { - match request.request_type { - ApplicationAllocationTypes::New => { - let new_allocation = self.info.datacap_allocations.clone().new(request.clone()); - let new_life_cycle = self - .info - .application_lifecycle - .clone() - .set_proposal_state(request.actor.clone(), Some(request.id)); - let info = ApplicationInfo { - core_information: self.info.core_information.clone(), - application_lifecycle: new_life_cycle, - datacap_allocations: new_allocation, - }; - return ApplicationFile { - id: self.id.clone(), - _type: self._type.clone(), - info, - }; - } - ApplicationAllocationTypes::Refill => { - let new_allocation = self.info.datacap_allocations.clone().add_new_request(request.clone()); - let new_life_cycle = self - .info - .application_lifecycle - .clone() - .set_proposal_state(request.actor.clone(), Some(request.id)); - let info = ApplicationInfo { - core_information: self.info.core_information.clone(), - application_lifecycle: new_life_cycle, - datacap_allocations: new_allocation, - }; - return ApplicationFile { - id: self.id.clone(), - _type: self._type.clone(), - info, - }; - } - ApplicationAllocationTypes::Removal => { - unimplemented!() - } + pub fn start_refill_request(&mut self, request: AllocationRequest) -> Self { + let new_life_cycle = self.lifecycle.clone().start_refill_request(request.id.clone()); + let allocations = self.allocation.clone().push(request.clone()); + Self { + lifecycle: new_life_cycle, + allocation: allocations, + ..self.clone() } } pub fn add_signer_to_allocation( &self, - signer: ApplicationAllocationsSigner, + signer: Notary, request_id: String, - app_lifecycle: ApplicationLifecycle, + app_lifecycle: LifeCycle, ) -> Self { - let new_allocation = self - .info - .datacap_allocations - .clone() - .add_signer(request_id, signer); - let info = ApplicationInfo { - core_information: self.info.core_information.clone(), - application_lifecycle: app_lifecycle, - datacap_allocations: new_allocation, - }; - ApplicationFile { - id: self.id.clone(), - _type: self._type.clone(), - info, + let new_allocation = self.allocation.clone().add_signer(request_id, signer); + Self { + allocation: new_allocation, + lifecycle: app_lifecycle, + ..self.clone() } } pub fn add_signer_to_allocation_and_complete( &self, - signer: ApplicationAllocationsSigner, + signer: Notary, request_id: String, - app_lifecycle: ApplicationLifecycle, + app_lifecycle: LifeCycle, ) -> Self { let new_allocation = self - .info - .datacap_allocations + .allocation .clone() .add_signer_and_complete(request_id, signer); - let info = ApplicationInfo { - core_information: self.info.core_information.clone(), - application_lifecycle: app_lifecycle, - datacap_allocations: new_allocation, - }; - ApplicationFile { - id: self.id.clone(), - _type: self._type.clone(), - info, + Self { + allocation: new_allocation, + lifecycle: app_lifecycle, + ..self.clone() } } } diff --git a/fplus-lib/src/core/application/project.rs b/fplus-lib/src/core/application/project.rs new file mode 100644 index 00000000..07bd7c00 --- /dev/null +++ b/fplus-lib/src/core/application/project.rs @@ -0,0 +1,40 @@ +use super::file::Project; + +impl Project { + fn new(i: Project) -> Self { + Self { ..i } + } + + fn validate(&self) -> bool { + let Project { + project_id, + associated_projects, + dataset_prepare, + filplus_guideline, + dataset_life_span, + geographis, + retrival_frequency, + previous_stoarge, + public_dataset, + providers, + data_sample_link, + stored_data_desc, + distribution, + history, + } = self; + project_id.len() > 0 + && associated_projects.len() > 0 + && dataset_prepare.len() > 0 + && filplus_guideline.len() > 0 + && dataset_life_span.len() > 0 + && geographis.len() > 0 + && retrival_frequency.len() > 0 + && previous_stoarge.len() > 0 + && public_dataset.len() > 0 + && providers.len() > 0 + && data_sample_link.len() > 0 + && stored_data_desc.len() > 0 + && distribution.len() > 0 + && history.len() > 0 + } +} diff --git a/fplus-lib/src/core/mod.rs b/fplus-lib/src/core/mod.rs index 66828574..0502f066 100644 --- a/fplus-lib/src/core/mod.rs +++ b/fplus-lib/src/core/mod.rs @@ -1,69 +1,44 @@ -use chrono::Utc; use futures::future; use octocrab::models::{ - pulls::{FileDiff, PullRequest}, - repos::ContentItems, + pulls::PullRequest, + repos::{Content, ContentItems}, }; use reqwest::Response; use serde::{Deserialize, Serialize}; -use self::application::{ - allocations::{AllocationRequest, ApplicationAllocationTypes, ApplicationAllocationsSigner}, - lifecycle::ApplicationFileState, - ApplicationFile, -}; - use crate::{ base64, - core::application::{ - allocations::ApplicationAllocations, - core_info::{ApplicationCoreInfo, ApplicationInfo}, - lifecycle::ApplicationLifecycle, - }, - error::LDNApplicationError, + error::LDNError, external_services::github::{ CreateMergeRequestData, CreateRefillMergeRequestData, GithubWrapper, }, - parsers::{parse_ldn_app_body, ParsedLDN}, + parsers::ParsedIssue, +}; + +use self::application::file::{ + AllocationRequest, AllocationRequestType, AppState, ApplicationFile, Notary, NotaryInput, }; pub mod application; #[derive(Deserialize)] pub struct CreateApplicationInfo { - pub application_id: String, + pub issue_number: String, } #[derive(Deserialize, Serialize, Debug)] pub struct CompleteNewApplicationProposalInfo { - signer: ApplicationAllocationsSigner, + signer: NotaryInput, request_id: String, } -#[derive(Deserialize, Serialize, Debug)] -pub struct ProposeApplicationInfo { - uuid: String, - client_address: String, - notary_address: String, - time_of_signature: String, - message_cid: String, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct ApproveApplicationInfo { - uuid: String, - client_address: String, - notary_address: String, - allocation_amount: String, - time_of_signature: String, - message_cid: String, -} - #[derive(Debug)] pub struct LDNApplication { github: GithubWrapper<'static>, pub application_id: String, - file_sha: String, + pub file_sha: String, + pub file_name: String, + pub branch_name: String, } #[derive(Debug, Serialize, Deserialize)] @@ -79,186 +54,173 @@ pub struct RefillInfo { } impl LDNApplication { - pub async fn single_active(pr_number: u64) -> Result { + pub async fn single_active(pr_number: u64) -> Result { let gh: GithubWrapper = GithubWrapper::new(); - let pull_request: Vec = gh.get_pull_request_files(pr_number).await.unwrap(); + let (_, pull_request) = gh.get_pull_request_files(pr_number).await.unwrap(); let pull_request = pull_request.get(0).unwrap(); - let pull_request: Response = reqwest::Client::new() .get(&pull_request.raw_url.to_string()) .send() .await - .map_err(|e| { - LDNApplicationError::LoadApplicationError(format!( - "Failed to get pull request files /// {}", - e - )) - })?; - let pull_request = pull_request.text().await.map_err(|e| { - LDNApplicationError::LoadApplicationError(format!( - "Failed to get pull request files /// {}", - e - )) - })?; + .map_err(|e| LDNError::Load(format!("Failed to get pull request files /// {}", e)))?; + let pull_request = pull_request + .text() + .await + .map_err(|e| LDNError::Load(format!("Failed to get pull request files /// {}", e)))?; if let Ok(app) = serde_json::from_str::(&pull_request) { Ok(app) } else { - Err(LDNApplicationError::LoadApplicationError(format!( + Err(LDNError::Load(format!( "Pull Request {} Application file is corrupted", pr_number ))) } } - pub async fn active( - filter: Option, - ) -> Result, LDNApplicationError> { + async fn load_pr_files( + pr: PullRequest, + ) -> Result, LDNError> { + let gh = GithubWrapper::new(); + let files = match gh.get_pull_request_files(pr.number).await { + Ok(files) => files, + Err(_) => return Ok(None), + }; + let raw_url = match files.1.get(0) { + Some(f) => f.raw_url.clone(), + None => return Ok(None), + }; + let response = reqwest::Client::new().get(raw_url).send().await; + let response = match response { + Ok(response) => response, + Err(_) => return Ok(None), + }; + let response = response.text().await; + let response = match response { + Ok(response) => response, + Err(_) => return Ok(None), + }; + dbg!(&response); + let app = match serde_json::from_str::(&response) { + Ok(app) => app, + Err(e) => { + dbg!(&e); + return Ok(None); + } + }; + Ok(Some(( + files.1.get(0).unwrap().sha.clone(), + files.1.get(0).unwrap().filename.clone(), + app, + pr.clone(), + ))) + } + + pub async fn load(application_id: String) -> Result { let gh: GithubWrapper = GithubWrapper::new(); - let mut apps: Vec = Vec::new(); let pull_requests = gh.list_pull_requests().await.unwrap(); - let pull_requests = future::try_join_all( pull_requests .into_iter() - .map(|pr: PullRequest| { - let number = pr.number; - gh.get_pull_request_files(number) - }) - .collect::>(), - ) - .await - .unwrap() - .into_iter() - .flatten(); - - let pull_requests: Vec = match future::try_join_all( - pull_requests - .into_iter() - .map(|fd: FileDiff| reqwest::Client::new().get(&fd.raw_url.to_string()).send()) + .map(|pr: PullRequest| (LDNApplication::load_pr_files(pr))) .collect::>(), ) - .await - { - Ok(res) => res, - Err(_) => { - return Err(LDNApplicationError::LoadApplicationError( - "Failed to get pull request files".to_string(), - )) + .await?; + let pull_requests = pull_requests + .into_iter() + .filter(|pr| pr.is_some()) + .collect::>(); + for r in pull_requests { + let r = r.unwrap(); + if String::from(r.2.id.clone()) == application_id.clone() { + return Ok(Self { + github: gh, + application_id: r.2.id.clone(), + file_sha: r.0, + file_name: r.1, + branch_name: r.3.head.ref_field, + }); } - }; + } + Err(LDNError::Load(format!("No Apps Found!"))) + } - let pull_requests = match future::try_join_all( + pub async fn active(filter: Option) -> Result, LDNError> { + let gh: GithubWrapper = GithubWrapper::new(); + let mut apps: Vec = Vec::new(); + let pull_requests = gh.list_pull_requests().await.unwrap(); + let pull_requests = future::try_join_all( pull_requests .into_iter() - .map(|r: Response| r.text()) + .map(|pr: PullRequest| LDNApplication::load_pr_files(pr)) .collect::>(), ) .await - { - Ok(res) => res, - Err(_) => { - return Err(LDNApplicationError::LoadApplicationError( - "Failed to get pull request files".to_string(), - )) - } - }; - + .unwrap(); + dbg!(&pull_requests); for r in pull_requests { - match serde_json::from_str::(&r) { - Ok(app) => { - if filter.is_none() { - apps.push(app) - } else { - if app.id == filter.clone().unwrap() { - apps.push(app) - } + if r.is_some() { + let r = r.unwrap(); + dbg!(&r); + if filter.is_none() { + apps.push(r.2) + } else { + if r.2.id == filter.clone().unwrap() { + apps.push(r.2) } } - Err(_) => continue, } } - Ok(apps) } - pub async fn load(application_id: String) -> Result { - let gh: GithubWrapper = GithubWrapper::new(); - let app_path = LDNPullRequest::application_path(&application_id); - let app_branch_name = LDNPullRequest::application_branch_name(&application_id); - let file = gh - .get_file(&app_path, &app_branch_name) - .await - .map_err(|e| { - LDNApplicationError::LoadApplicationError(format!( - "Application issue {} file does not exist /// {}", - application_id, e - )) - })?; - let file_sha = get_file_sha(&file).unwrap(); - Ok(LDNApplication { - github: gh, - application_id, - file_sha, - }) - } /// Create New Application - pub async fn new(info: CreateApplicationInfo) -> Result { - let application_id = info.application_id; + pub async fn new_from_issue(info: CreateApplicationInfo) -> Result { + let issue_number = info.issue_number; let gh: GithubWrapper = GithubWrapper::new(); - let (parsed_ldn, _) = - LDNApplication::parse_application_issue(application_id.clone()).await?; - let app_path = LDNPullRequest::application_path(&application_id); - let app_branch_name = LDNPullRequest::application_branch_name(&application_id); + let (parsed_ldn, _) = LDNApplication::parse_application_issue(issue_number.clone()).await?; + let application_id = parsed_ldn.id.clone(); + let file_name = LDNPullRequest::application_path(&application_id); + let branch_name = LDNPullRequest::application_branch_name(&application_id); - match gh.get_file(&app_path, &app_branch_name).await { + match gh.get_file(&file_name, &branch_name).await { Err(_) => { - let file_sha = LDNPullRequest::create_empty_pr( - application_id.clone(), - parsed_ldn.name.clone(), - LDNPullRequest::application_branch_name(&application_id), - None, + let application_file = ApplicationFile::new( + issue_number.clone(), + "MULTISIG ADDRESS".to_string(), + parsed_ldn.version, + parsed_ldn.id, + parsed_ldn.client.clone(), + parsed_ldn.project, + parsed_ldn.datacap, ) - .await?; - let app_allocations = ApplicationAllocations::default(); - let app_lifecycle = ApplicationLifecycle::governance_review_state(None); - let app_core_info: ApplicationCoreInfo = ApplicationCoreInfo::new( - parsed_ldn.name.clone(), - parsed_ldn.region, - "GithubHandleTodo".to_string(), - "industry".to_string(), - parsed_ldn.address, - parsed_ldn.datacap_requested, - parsed_ldn.datacap_weekly_allocation, - parsed_ldn.website, - "social_media".to_string(), - ); - let app_info = ApplicationInfo::new(app_core_info, app_lifecycle, app_allocations); - let application_file = ApplicationFile::new(app_info, application_id.clone()).await; + .await; let file_content = match serde_json::to_string_pretty(&application_file) { Ok(f) => f, Err(e) => { - return Err(LDNApplicationError::NewApplicationError(format!( + return Err(LDNError::New(format!( "Application issue file is corrupted /// {}", e ))) } }; - let pr_handler = LDNPullRequest::load(&application_id, &parsed_ldn.name); - pr_handler - .add_commit( - LDNPullRequest::application_move_to_governance_review(), - file_content, - file_sha.clone(), - ) - .await; + let file_sha = LDNPullRequest::create_pr( + issue_number.clone(), + parsed_ldn.client.name.clone(), + branch_name.clone(), + file_name.clone(), + file_content.clone(), + ) + .await?; Ok(LDNApplication { github: gh, application_id, file_sha, + file_name, + branch_name, }) } Ok(_) => { - return Err(LDNApplicationError::NewApplicationError(format!( + return Err(LDNError::New(format!( "Application issue {} already exists", application_id ))) @@ -270,53 +232,44 @@ impl LDNApplication { pub async fn complete_governance_review( &self, info: CompleteGovernanceReviewInfo, - ) -> Result { + ) -> Result { match self.app_state().await { Ok(s) => match s { - ApplicationFileState::GovernanceReview => { + AppState::Submitted => { let app_file: ApplicationFile = self.file().await?; - let app_pull_request = LDNPullRequest::load( - &self.application_id, - &app_file.info.core_information.data_owner_name, - ); let uuid = uuidv4::uuid::v4(); - let app_file = - app_file.complete_governance_review(info.actor.clone(), uuid.clone()); - let (parsed_ldn, issue_creator) = - Self::parse_application_issue(self.application_id.clone()).await?; - let new_alloc = AllocationRequest::new( - issue_creator, + let request = AllocationRequest::new( + info.actor.clone(), uuid, - ApplicationAllocationTypes::New, - parsed_ldn.address, - Utc::now().to_string(), - parsed_ldn.datacap_requested, + AllocationRequestType::First, + app_file.datacap.total_requested_amount.clone(), ); - let app_file = app_file.start_new_allocation(new_alloc); + let app_file = app_file.complete_governance_review(info.actor.clone(), request); let file_content = serde_json::to_string_pretty(&app_file).unwrap(); - match app_pull_request - .add_commit( - LDNPullRequest::application_move_to_proposal_commit(&info.actor), - file_content, - self.file_sha.clone(), - ) - .await + match LDNPullRequest::add_commit_to( + self.file_name.clone(), + self.branch_name.clone(), + LDNPullRequest::application_move_to_proposal_commit(&info.actor), + file_content, + self.file_sha.clone(), + ) + .await { Some(()) => Ok(app_file), None => { - return Err(LDNApplicationError::NewApplicationError(format!( + return Err(LDNError::New(format!( "Application issue {} cannot be triggered(1)", self.application_id ))) } } } - _ => Err(LDNApplicationError::NewApplicationError(format!( + _ => Err(LDNError::New(format!( "Application issue {} cannot be triggered(2)", self.application_id ))), }, - Err(e) => Err(LDNApplicationError::NewApplicationError(format!( + Err(e) => Err(LDNError::New(format!( "Application issue {} cannot be triggered {}(3)", self.application_id, e ))), @@ -327,118 +280,100 @@ impl LDNApplication { pub async fn complete_new_application_proposal( &self, info: CompleteNewApplicationProposalInfo, - ) -> Result { + ) -> Result { let CompleteNewApplicationProposalInfo { signer, request_id } = info; match self.app_state().await { Ok(s) => match s { - ApplicationFileState::Proposal => { + AppState::ReadyToSign => { let app_file: ApplicationFile = self.file().await?; - if !app_file - .info - .datacap_allocations - .is_active(request_id.clone()) - { - return Err(LDNApplicationError::LoadApplicationError(format!( + if !app_file.allocation.is_active(request_id.clone()) { + return Err(LDNError::Load(format!( "Request {} is not active", request_id ))); } - let app_pull_request = LDNPullRequest::load( - &self.application_id, - &app_file.info.core_information.data_owner_name.clone(), - ); - let app_lifecycle = app_file - .info - .application_lifecycle - .set_approval_state(Some(request_id.clone())); - + let app_lifecycle = app_file.lifecycle.finish_proposal(); let app_file = app_file.add_signer_to_allocation( - signer.clone(), + signer.clone().into(), request_id, app_lifecycle, ); let file_content = serde_json::to_string_pretty(&app_file).unwrap(); - match app_pull_request - .add_commit( - LDNPullRequest::application_move_to_approval_commit( - &signer.signing_address, - ), - file_content, - self.file_sha.clone(), - ) - .await + match LDNPullRequest::add_commit_to( + self.file_name.clone(), + self.branch_name.clone(), + LDNPullRequest::application_move_to_approval_commit( + &signer.signing_address, + ), + file_content, + self.file_sha.clone(), + ) + .await { Some(()) => Ok(app_file), None => { - return Err(LDNApplicationError::NewApplicationError(format!( + return Err(LDNError::New(format!( "Application issue {} cannot be proposed(1)", self.application_id ))) } } } - _ => Err(LDNApplicationError::NewApplicationError(format!( + _ => Err(LDNError::New(format!( "Application issue {} cannot be proposed(2)", self.application_id ))), }, - Err(e) => Err(LDNApplicationError::NewApplicationError(format!( + Err(e) => Err(LDNError::New(format!( "Application issue {} cannot be proposed {}(3)", self.application_id, e ))), } } - /// Move application from Governance Review to Proposal pub async fn complete_new_application_approval( &self, info: CompleteNewApplicationProposalInfo, - ) -> Result { + ) -> Result { let CompleteNewApplicationProposalInfo { signer, request_id } = info; match self.app_state().await { Ok(s) => match s { - ApplicationFileState::Approval => { + AppState::StartSignDatacap => { let app_file: ApplicationFile = self.file().await?; - let app_pull_request = LDNPullRequest::load( - &self.application_id.clone(), - &app_file.info.core_information.data_owner_name, - ); - let app_lifecycle = app_file - .info - .application_lifecycle - .set_confirmed_state(Some(request_id.clone())); - + dbg!(&app_file); + let app_lifecycle = app_file.lifecycle.finish_approval(); let app_file = app_file.add_signer_to_allocation_and_complete( - signer.clone(), + signer.clone().into(), request_id, app_lifecycle, ); let file_content = serde_json::to_string_pretty(&app_file).unwrap(); - match app_pull_request - .add_commit( - LDNPullRequest::application_move_to_confirmed_commit( - &signer.signing_address, - ), - file_content, - self.file_sha.clone(), - ) - .await + match LDNPullRequest::add_commit_to( + self.file_name.clone(), + self.branch_name.clone(), + LDNPullRequest::application_move_to_confirmed_commit( + &signer.signing_address, + ), + file_content, + self.file_sha.clone(), + ) + .await { Some(()) => Ok(app_file), None => { - return Err(LDNApplicationError::NewApplicationError(format!( + return Err(LDNError::New(format!( "Application issue {} cannot be proposed(1)", self.application_id ))) } } } - _ => Err(LDNApplicationError::NewApplicationError(format!( + _ => Err(LDNError::New(format!( "Application issue {} cannot be proposed(2)", self.application_id ))), }, - Err(e) => Err(LDNApplicationError::NewApplicationError(format!( + Err(e) => Err(LDNError::New(format!( "Application issue {} cannot be proposed {}(3)", self.application_id, e ))), @@ -446,65 +381,63 @@ impl LDNApplication { } async fn parse_application_issue( - application_id: String, - ) -> Result<(ParsedLDN, String), LDNApplicationError> { + issue_number: String, + ) -> Result<(ParsedIssue, String), LDNError> { let gh: GithubWrapper = GithubWrapper::new(); - let issue = match gh.list_issue(application_id.parse().unwrap()).await { + let issue = match gh.list_issue(issue_number.parse().unwrap()).await { Ok(issue) => issue, Err(e) => { - return Err(LDNApplicationError::LoadApplicationError(format!( + return Err(LDNError::Load(format!( "Application issue {} does not exist /// {}", - application_id, e + issue_number, e ))) } }; let issue_body = match issue.body { Some(body) => body, None => { - return Err(LDNApplicationError::LoadApplicationError(format!( + return Err(LDNError::Load(format!( "Application issue {} is empty", - application_id + issue_number ))) } }; - Ok((parse_ldn_app_body(&issue_body), issue.user.login)) + Ok((ParsedIssue::from_issue_body(&issue_body), issue.user.login)) } /// Return Application state - async fn app_state(&self) -> Result { + async fn app_state(&self) -> Result { let f = self.file().await?; - Ok(f.info.application_lifecycle.get_state()) + dbg!(&f); + Ok(f.lifecycle.get_state()) } /// Return Application state - pub async fn total_dc_reached(id: String) -> Result { + pub async fn total_dc_reached(application_id: String) -> Result { let merged = Self::merged().await?; - let app = match merged.iter().find(|app| app.id == id) { + let app = match merged.iter().find(|(_, app)| app.id == application_id) { Some(app) => app, None => { - return Err(LDNApplicationError::LoadApplicationError(format!( + return Err(LDNError::Load(format!( "Application issue {} does not exist", - id + application_id ))) } }; - match app.info.application_lifecycle.get_state() { - ApplicationFileState::Confirmed => { - let app = app.reached_total_datacap(); - let pr_handler = - LDNPullRequest::load(&app.id, &app.info.core_information.data_owner_name); + match app.1.lifecycle.get_state() { + AppState::Granted => { + let app = app.1.reached_total_datacap(); let gh: GithubWrapper<'_> = GithubWrapper::new(); - - let ContentItems { items } = gh - .get_file(&pr_handler.path, &pr_handler.branch_name) - .await - .unwrap(); + let ldn_app = LDNApplication::load(application_id.clone()).await?; + let ContentItems { items } = gh.get_file(&ldn_app.file_name, "main").await.unwrap(); LDNPullRequest::create_refill_pr( app.id.clone(), - app.info.core_information.data_owner_name.clone(), - items[0].sha.clone(), + app.client.name.clone(), serde_json::to_string_pretty(&app).unwrap(), + ldn_app.file_name.clone(), + "dc-reached".to_string(), + items[0].sha.clone(), ) .await?; // let app_file: ApplicationFile = self.file().await?; @@ -515,36 +448,32 @@ impl LDNApplication { } } - fn content_items_to_app_file( - file: ContentItems, - ) -> Result { + fn content_items_to_app_file(file: ContentItems) -> Result { let f = match &file.items[0].content { Some(f) => f, - None => { - return Err(LDNApplicationError::LoadApplicationError(format!( - "Application file is corrupted", - ))) - } + None => return Err(LDNError::Load(format!("Application file is corrupted",))), }; match base64::decode(&f.replace("\n", "")) { Some(f) => { return Ok(ApplicationFile::from(f)); } None => { - return Err(LDNApplicationError::LoadApplicationError(format!( + return Err(LDNError::Load(format!( "Application issue file is corrupted", ))) } } } - async fn file(&self) -> Result { - let app_path = LDNPullRequest::application_path(&self.application_id); - let app_branch_name = LDNPullRequest::application_branch_name(&self.application_id); - match self.github.get_file(&app_path, &app_branch_name).await { + async fn file(&self) -> Result { + match self + .github + .get_file(&self.file_name, &self.branch_name) + .await + { Ok(file) => Ok(LDNApplication::content_items_to_app_file(file)?), Err(e) => { - return Err(LDNApplicationError::LoadApplicationError(format!( + return Err(LDNError::Load(format!( "Application issue {} file does not exist /// {}", self.application_id, e ))) @@ -552,107 +481,111 @@ impl LDNApplication { } } - pub async fn merged() -> Result, LDNApplicationError> { - let gh: GithubWrapper<'_> = GithubWrapper::new(); + async fn single_merged(application_id: String) -> Result<(Content, ApplicationFile), LDNError> { + let merged = LDNApplication::merged().await?; + let app = match merged.into_iter().find(|(_, app)| app.id == application_id) { + Some(app) => Ok(app), + None => Err(LDNError::Load(format!( + "Application issue {} does not exist", + application_id + ))), + }; + app + } + + pub async fn think_merged( + item: Content, + ) -> Result, LDNError> { + if item.download_url.is_none() { + return Ok(None); + } + let file = reqwest::Client::new() + .get(&item.download_url.clone().unwrap()) + .send() + .await + .map_err(|e| LDNError::Load(format!("here {}", e)))?; + let file = file + .text() + .await + .map_err(|e| LDNError::Load(format!("here1 {}", e)))?; + let app = match serde_json::from_str::(&file) { + Ok(app) => { + if app.lifecycle.is_active { + app + } else { + return Ok(None); + } + } + Err(_) => { + return Ok(None); + } + }; + Ok(Some((item, app))) + } + + pub async fn merged() -> Result, LDNError> { + let gh = GithubWrapper::new(); let mut all_files = gh.get_all_files().await.map_err(|e| { - LDNApplicationError::LoadApplicationError(format!( + LDNError::Load(format!( "Failed to retrieve all files from GitHub. Reason: {}", e )) })?; all_files .items - .retain(|item| item.download_url.is_some() && item.name.starts_with("Application")); + .retain(|item| item.download_url.is_some() && item.name.ends_with(".json")); let all_files = future::try_join_all( all_files .items .into_iter() - .map(|fd| reqwest::Client::new().get(&fd.download_url.unwrap()).send()) + .map(|fd| LDNApplication::think_merged(fd)) .collect::>(), ) .await .map_err(|e| { - LDNApplicationError::LoadApplicationError(format!( + LDNError::Load(format!( "Failed to fetch application files from their URLs. Reason: {}", e )) })?; - let mut apps: Vec = vec![]; + let mut apps: Vec<(Content, ApplicationFile)> = vec![]; let active: Vec = Self::active(None).await?; - for f in all_files { - let f = match f.text().await { - Ok(f) => f, - Err(_) => { - continue; - } - }; - match serde_json::from_str::(&f) { - Ok(app) => { - if active.iter().find(|a| a.id == app.id).is_none() - && app.info.application_lifecycle.is_active - { - apps.push(app); - } - } - Err(_) => { - continue; + for app in all_files { + if app.is_some() { + let app = app.unwrap(); + if active.iter().find(|a| a.id == app.1.id).is_none() && app.1.lifecycle.is_active { + apps.push(app); } - }; + } } Ok(apps) } - pub async fn refill(refill_info: RefillInfo) -> Result { - let gh = GithubWrapper::new(); + pub async fn refill(refill_info: RefillInfo) -> Result { let apps = LDNApplication::merged().await?; - - if let Some(app) = apps.iter().find(|app| app.id == refill_info.id) { + if let Some((content, mut app)) = apps.into_iter().find(|(_, app)| app.id == refill_info.id) + { let uuid = uuidv4::uuid::v4(); - let app_lifecycle = app - .info - .application_lifecycle - .set_refill_proposal_state(Some(uuid.clone())); - let new_request: AllocationRequest = AllocationRequest { - actor: "SSA Bot".to_string(), - id: uuid.clone(), - request_type: ApplicationAllocationTypes::Refill, - client_address: app.info.core_information.data_owner_address.clone(), - created_at: Utc::now().to_string(), - is_active: true, - allocation_amount: format!("{}{}", refill_info.amount, refill_info.amount_type), - }; - let app_allocations = app - .clone() - .info - .datacap_allocations - .add_new_request(new_request); - let app_info = ApplicationInfo::new( - app.info.core_information.clone(), - app_lifecycle, - app_allocations, + let new_request = AllocationRequest::new( + "SSA Bot".to_string(), + uuid.clone(), + AllocationRequestType::Refill(0), + format!("{}{}", refill_info.amount, refill_info.amount_type), ); - let application_file = ApplicationFile::new(app_info, app.id.clone()).await; - let pr_handler = - LDNPullRequest::load(&app.id, &app.info.core_information.data_owner_name); - - let ContentItems { items } = gh - .get_file(&pr_handler.path, &pr_handler.branch_name) - .await - .unwrap(); - + let app_file = app.start_refill_request(new_request); LDNPullRequest::create_refill_pr( app.id.clone(), - app.info.core_information.data_owner_name.clone(), - items[0].sha.clone(), - serde_json::to_string_pretty(&application_file).unwrap(), + app.client.name.clone(), + serde_json::to_string_pretty(&app_file).unwrap(), + content.name.clone(), // filename + "nrewew_Brtach".to_string(), + content.sha, ) .await?; return Ok(true); } - Err(LDNApplicationError::LoadApplicationError( - "Failed to get application file".to_string(), - )) + Err(LDNError::Load("Failed to get application file".to_string())) } } @@ -665,77 +598,92 @@ pub struct LDNPullRequest { } impl LDNPullRequest { - async fn create_empty_pr( + async fn create_pr( application_id: String, owner_name: String, app_branch_name: String, - base_hash: Option, - ) -> Result { + file_name: String, + file_content: String, + ) -> Result { let initial_commit = Self::application_initial_commit(&owner_name, &application_id); let gh: GithubWrapper = GithubWrapper::new(); - let create_ref_request = - match gh.build_create_ref_request(app_branch_name.clone(), base_hash) { - Ok(req) => req, - Err(e) => { - return Err(LDNApplicationError::NewApplicationError(format!( - "Application issue cannot create branch request object /// {}", - e - ))) - } - }; + let head_hash = gh.get_main_branch_sha().await.unwrap(); + let create_ref_request = gh + .build_create_ref_request(app_branch_name.clone(), head_hash) + .map_err(|e| { + return LDNError::New(format!( + "Application issue {} cannot create branch /// {}", + application_id, e + )); + })?; - let (_pr, file_sha) = match gh + let (_pr, file_sha) = gh .create_merge_request(CreateMergeRequestData { application_id: application_id.clone(), + branch_name: app_branch_name, + file_name, owner_name, ref_request: create_ref_request, - file_content: "{}".to_string(), + file_content, commit: initial_commit, }) .await - { - Ok((pr, file_sha)) => (pr, file_sha), - Err(e) => { - return Err(LDNApplicationError::NewApplicationError(format!( - "Application issue {} cannot create branch /// {}", + .map_err(|e| { + return LDNError::New(format!( + "Application issue {} cannot create merge request /// {}", application_id, e - ))); - } - }; + )); + })?; + Ok(file_sha) } async fn create_refill_pr( application_id: String, owner_name: String, - file_sha: String, file_content: String, - ) -> Result { + file_name: String, + branch_name: String, + file_sha: String, + ) -> Result { let initial_commit = Self::application_initial_commit(&owner_name, &application_id); let gh: GithubWrapper = GithubWrapper::new(); + let head_hash = gh.get_main_branch_sha().await.unwrap(); + let create_ref_request = gh + .build_create_ref_request(branch_name.clone(), head_hash) + .map_err(|e| { + return LDNError::New(format!( + "Application issue {} cannot create branch /// {}", + application_id, e + )); + })?; let pr = match gh .create_refill_merge_request(CreateRefillMergeRequestData { application_id: application_id.clone(), owner_name, + file_name, + file_sha, + ref_request: create_ref_request, + branch_name, file_content, commit: initial_commit, - file_sha, }) .await { Ok(pr) => pr, Err(e) => { - return Err(LDNApplicationError::NewApplicationError(format!( + return Err(LDNError::New(format!( "Application issue {} cannot create branch /// {}", application_id, e ))); } }; - Ok(pr.number) + Ok(pr.0.number) } - pub(super) async fn add_commit( - &self, + pub(super) async fn add_commit_to( + path: String, + branch_name: String, commit_message: String, new_content: String, file_sha: String, @@ -743,10 +691,10 @@ impl LDNPullRequest { let gh: GithubWrapper = GithubWrapper::new(); match gh .update_file_content( - &self.path, + &path, &commit_message, &new_content, - &self.branch_name, + &branch_name, &file_sha, ) .await @@ -756,36 +704,10 @@ impl LDNPullRequest { } } - pub(super) fn load(application_id: &str, owner_name: &str) -> Self { - LDNPullRequest { - branch_name: LDNPullRequest::application_branch_name(application_id), - title: LDNPullRequest::application_title(application_id, owner_name), - body: LDNPullRequest::application_body(application_id), - path: LDNPullRequest::application_path(application_id), - } - } - - pub(super) fn load_refill(application_id: &str) -> Self { - LDNPullRequest { - branch_name: format!("Refill/{}", application_id), - title: format!("Refill:{}", application_id), - body: format!("Refill Application #{}", application_id), - path: format!("Refill:{}.json", application_id), - } - } - pub(super) fn application_branch_name(application_id: &str) -> String { format!("Application/{}", application_id) } - pub(super) fn application_title(application_id: &str, owner_name: &str) -> String { - format!("Application_{}_{}", application_id, owner_name) - } - - pub(super) fn application_body(application_id: &str) -> String { - format!("resolves #{}", application_id) - } - pub(super) fn application_path(application_id: &str) -> String { format!("{}.json", application_id) } @@ -794,10 +716,6 @@ impl LDNPullRequest { format!("Start Application: {}-{}", owner_name, application_id) } - pub(super) fn application_move_to_governance_review() -> String { - format!("Application is under review of governance team") - } - pub(super) fn application_move_to_proposal_commit(actor: &str) -> String { format!( "Governance Team User {} Moved Application to Proposal State from Governance Review State", @@ -842,25 +760,22 @@ mod tests { let gh: GithubWrapper = GithubWrapper::new(); // let branches = gh.list_branches().await.unwrap(); - let issue = gh.list_issue(63).await.unwrap(); + let issue = gh.list_issue(359).await.unwrap(); let test_issue: Issue = gh .create_issue("from test", &issue.body.unwrap()) .await .unwrap(); - assert!(LDNApplication::new(CreateApplicationInfo { - application_id: test_issue.number.to_string(), + let ldn_application = LDNApplication::new_from_issue(CreateApplicationInfo { + issue_number: test_issue.number.to_string(), }) .await - .is_ok()); + .unwrap(); - let application_id = test_issue.number.to_string(); + let application_id = ldn_application.application_id.to_string(); // validate file was created assert!(gh - .get_file( - &LDNPullRequest::application_path(application_id.as_str()), - &LDNPullRequest::application_branch_name(application_id.as_str()) - ) + .get_file(&ldn_application.file_name, &ldn_application.branch_name) .await .is_ok()); @@ -871,6 +786,7 @@ mod tests { )) .await .is_ok()); + sleep(Duration::from_millis(2000)).await; // Test Triggering an application let ldn_application_before_trigger = @@ -885,7 +801,7 @@ mod tests { LDNApplication::load(application_id.clone()).await.unwrap(); assert_eq!( ldn_application_after_trigger.app_state().await.unwrap(), - ApplicationFileState::Proposal + AppState::ReadyToSign ); dbg!("waiting for 2 second"); sleep(Duration::from_millis(1000)).await; @@ -897,18 +813,17 @@ mod tests { .file() .await .unwrap() - .info - .application_lifecycle + .lifecycle .get_active_allocation_id() .unwrap(); ldn_application_after_trigger_success .complete_new_application_proposal(CompleteNewApplicationProposalInfo { request_id: active_request_id.clone(), - signer: ApplicationAllocationsSigner { + signer: NotaryInput { signing_address: "signing_address".to_string(), - time_of_signature: "time_of_signature".to_string(), + created_at: "time_of_signature".to_string(), message_cid: "message_cid".to_string(), - username: "gh_username".to_string(), + github_username: "gh_username".to_string(), }, }) .await @@ -918,7 +833,7 @@ mod tests { LDNApplication::load(application_id.clone()).await.unwrap(); assert_eq!( ldn_application_after_proposal.app_state().await.unwrap(), - ApplicationFileState::Approval + AppState::StartSignDatacap ); dbg!("waiting for 2 second"); sleep(Duration::from_millis(1000)).await; @@ -929,11 +844,11 @@ mod tests { ldn_application_after_proposal_success .complete_new_application_approval(CompleteNewApplicationProposalInfo { request_id: active_request_id.clone(), - signer: ApplicationAllocationsSigner { + signer: NotaryInput { signing_address: "signing_address".to_string(), - time_of_signature: "time_of_signature".to_string(), + created_at: "time_of_signature".to_string(), message_cid: "message_cid".to_string(), - username: "gh_username".to_string(), + github_username: "gh_username".to_string(), }, }) .await @@ -942,7 +857,7 @@ mod tests { LDNApplication::load(application_id.clone()).await.unwrap(); assert_eq!( ldn_application_after_approval.app_state().await.unwrap(), - ApplicationFileState::Confirmed + AppState::Granted ); dbg!("waiting for 2 second"); sleep(Duration::from_millis(1000)).await; @@ -968,283 +883,3 @@ mod tests { assert!(gh.remove_branch(remove_branch_request).await.is_ok()); } } - -// mod tests { -// #[tokio::test] -// async fn refill() { -// let res: Result, LDNApplicationError> = -// LDNApplication::refill_existing_application(vec![RefillInfo { -// id: "229".to_string(), -// amount: "10".to_string(), -// amount_type: "PiB".to_string(), -// }]) -// .await; -// dbg!(&res); -// assert!(false); -// } -// #[ignore] -// #[tokio::test] -// async fn ldnapplication() { -// let res: Result, LDNApplicationError> = -// LDNApplication::get_merged_applications().await; -// dbg!(&res); -// assert!(false); -// } -// #[ignore] -// #[tokio::test] -// async fn end_to_end() { -// // Test Creating an application -// let gh: GithubWrapper = GithubWrapper::new(); - -// // let branches = gh.list_branches().await.unwrap(); -// let issue: Issue = gh.list_issue(63).await.unwrap(); -// let test_issue: Issue = gh -// .create_issue("from test", &issue.body.unwrap()) -// .await -// .unwrap(); -// assert!(LDNApplication::new(CreateApplicationInfo { -// application_id: test_issue.number.to_string(), -// }) -// .await -// .is_ok()); - -// let application_id = test_issue.number.to_string(); - -// // validate file was created -// assert!(gh -// .get_file( -// &LDNPullRequest::application_path(application_id.as_str()), -// &LDNPullRequest::application_branch_name(application_id.as_str()) -// ) -// .await -// .is_ok()); - -// // validate pull request was created -// assert!(gh -// .get_pull_request_by_head(&LDNPullRequest::application_branch_name( -// application_id.as_str() -// )) -// .await -// .is_ok()); - -// // Test Triggering an application -// let ldn_application_before_trigger = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// ldn_application_before_trigger -// .complete_governance_review(CompleteGovernanceReviewInfo { -// actor: "actor_address".to_string(), -// }) -// .await -// .unwrap(); -// let ldn_application_after_trigger = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// assert_eq!( -// ldn_application_after_trigger.app_state().await.unwrap(), -// ApplicationFileState::Proposal -// ); -// dbg!("waiting for 2 second"); -// sleep(Duration::from_millis(1000)).await; - -// // // Test Proposing an application -// let ldn_application_after_trigger_success = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// ldn_application_after_trigger_success -// .complete_new_application_proposal(CompleteNewApplicationProposalInfo { -// request_id: "request_id".to_string(), -// signer: ApplicationAllocationsSigner { -// signing_address: "signing_address".to_string(), -// time_of_signature: "time_of_signature".to_string(), -// message_cid: "message_cid".to_string(), -// }, -// }) -// .await -// .unwrap(); -// let ldn_application_after_proposal = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// assert_eq!( -// ldn_application_after_proposal.app_state().await.unwrap(), -// ApplicationFileState::Approval -// ); -// dbg!("waiting for 2 second"); -// sleep(Duration::from_millis(1000)).await; - -// // Test Approving an application -// let ldn_application_after_proposal_success = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// ldn_application_after_proposal_success -// .complete_new_application_approval(CompleteNewApplicationProposalInfo { -// request_id: "request_id".to_string(), -// signer: ApplicationAllocationsSigner { -// signing_address: "signing_address".to_string(), -// time_of_signature: "time_of_signature".to_string(), -// message_cid: "message_cid".to_string(), -// }, -// }) -// .await -// .unwrap(); -// let ldn_application_after_approval = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// assert_eq!( -// ldn_application_after_approval.app_state().await.unwrap(), -// ApplicationFileState::Confirmed -// ); -// dbg!("waiting for 2 second"); -// sleep(Duration::from_millis(1000)).await; - -// // // Cleanup -// assert!(gh.close_issue(test_issue.number).await.is_ok()); -// assert!(gh -// .close_pull_request( -// gh.get_pull_request_by_head(&LDNPullRequest::application_branch_name( -// &application_id.clone() -// )) -// .await -// .unwrap()[0] -// .number, -// ) -// .await -// .is_ok()); -// let remove_branch_request = gh -// .build_remove_ref_request(LDNPullRequest::application_branch_name( -// &application_id.clone(), -// )) -// .unwrap(); -// assert!(gh.remove_branch(remove_branch_request).await.is_ok()); -// } - -// #[cfg(test)] -// mod tests { -// use super::*; -// use octocrab::models::issues::Issue; -// use tokio::time::{sleep, Duration}; - -// #[ignore] -// #[tokio::test] -// async fn ldnapplication() { -// let res: Result, LDNApplicationError> = -// LDNApplication::get_merged_applications().await; -// dbg!(&res); -// assert!(false); -// } -// #[ignore] -// #[tokio::test] -// async fn end_to_end() { -// // Test Creating an application -// let gh: GithubWrapper = GithubWrapper::new(); - -// // let branches = gh.list_branches().await.unwrap(); -// let issue: Issue = gh.list_issue(63).await.unwrap(); -// let test_issue: Issue = gh -// .create_issue("from test", &issue.body.unwrap()) -// .await -// .unwrap(); -// assert!(LDNApplication::new(CreateApplicationInfo { -// application_id: test_issue.number.to_string(), -// }) -// .await -// .is_ok()); - -// let application_id = test_issue.number.to_string(); - -// // validate file was created -// assert!(gh -// .get_file( -// &LDNPullRequest::application_path(application_id.as_str()), -// &LDNPullRequest::application_branch_name(application_id.as_str()) -// ) -// .await -// .is_ok()); - -// // validate pull request was created -// assert!(gh -// .get_pull_request_by_head(&LDNPullRequest::application_branch_name( -// application_id.as_str() -// )) -// .await -// .is_ok()); - -// // Test Triggering an application -// let ldn_application_before_trigger = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// ldn_application_before_trigger -// .complete_governance_review(CompleteGovernanceReviewInfo { -// actor: "actor_address".to_string(), -// }) -// .await -// .unwrap(); -// let ldn_application_after_trigger = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// assert_eq!( -// ldn_application_after_trigger.app_state().await.unwrap(), -// ApplicationFileState::Proposal -// ); -// dbg!("waiting for 2 second"); -// sleep(Duration::from_millis(1000)).await; - -// // // Test Proposing an application -// let ldn_application_after_trigger_success = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// ldn_application_after_trigger_success -// .complete_new_application_proposal(CompleteNewApplicationProposalInfo { -// request_id: "request_id".to_string(), -// signer: ApplicationAllocationsSigner { -// signing_address: "signing_address".to_string(), -// time_of_signature: "time_of_signature".to_string(), -// message_cid: "message_cid".to_string(), -// username: "gh_username".to_string(), -// }, -// }) -// .await -// .unwrap(); -// let ldn_application_after_proposal = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// assert_eq!( -// ldn_application_after_proposal.app_state().await.unwrap(), -// ApplicationFileState::Approval -// ); -// dbg!("waiting for 2 second"); -// sleep(Duration::from_millis(1000)).await; - -// // Test Approving an application -// let ldn_application_after_proposal_success = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// ldn_application_after_proposal_success -// .complete_new_application_approval(CompleteNewApplicationProposalInfo { -// request_id: "request_id".to_string(), -// signer: ApplicationAllocationsSigner { -// signing_address: "signing_address".to_string(), -// time_of_signature: "time_of_signature".to_string(), -// message_cid: "message_cid".to_string(), -// username: "gh_username".to_string(), -// }, -// }) -// .await -// .unwrap(); -// let ldn_application_after_approval = -// LDNApplication::load(application_id.clone()).await.unwrap(); -// assert_eq!( -// ldn_application_after_approval.app_state().await.unwrap(), -// ApplicationFileState::Confirmed -// ); -// dbg!("waiting for 2 second"); -// sleep(Duration::from_millis(1000)).await; - -// // // Cleanup -// assert!(gh.close_issue(test_issue.number).await.is_ok()); -// assert!(gh -// .close_pull_request( -// gh.get_pull_request_by_head(&LDNPullRequest::application_branch_name( -// &application_id.clone() -// )) -// .await -// .unwrap()[0] -// .number, -// ) -// .await -// .is_ok()); -// let remove_branch_request = gh -// .build_remove_ref_request(LDNPullRequest::application_branch_name( -// &application_id.clone(), -// )) -// .unwrap(); -// assert!(gh.remove_branch(remove_branch_request).await.is_ok()); diff --git a/fplus-lib/src/error.rs b/fplus-lib/src/error.rs index b7b2f59c..c4ec830f 100644 --- a/fplus-lib/src/error.rs +++ b/fplus-lib/src/error.rs @@ -10,31 +10,31 @@ use actix_web::{ }; #[derive(Debug)] -pub enum LDNApplicationError { - NewApplicationError(String), - LoadApplicationError(String), +pub enum LDNError { + New(String), + Load(String), } -impl Display for LDNApplicationError { +impl Display for LDNError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - LDNApplicationError::LoadApplicationError(e) => { - write!(f, "LoadApplicationError: {}", e) + LDNError::Load(e) => { + write!(f, "Load: {}", e) } - LDNApplicationError::NewApplicationError(e) => { - write!(f, "NewApplicationError: {}", e) + LDNError::New(e) => { + write!(f, "New: {}", e) } } } } -impl MessageBody for LDNApplicationError { +impl MessageBody for LDNError { type Error = std::convert::Infallible; fn size(&self) -> BodySize { match self { - LDNApplicationError::LoadApplicationError(e) => BodySize::Sized(e.len() as u64), - LDNApplicationError::NewApplicationError(e) => BodySize::Sized(e.len() as u64), + LDNError::Load(e) => BodySize::Sized(e.len() as u64), + LDNError::New(e) => BodySize::Sized(e.len() as u64), } } @@ -42,13 +42,9 @@ impl MessageBody for LDNApplicationError { self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll>> { - match Pin::<&mut LDNApplicationError>::into_inner(self) { - LDNApplicationError::LoadApplicationError(e) => { - Poll::Ready(Some(Ok(Bytes::from(e.clone())))) - } - LDNApplicationError::NewApplicationError(e) => { - Poll::Ready(Some(Ok(Bytes::from(e.clone())))) - } + match Pin::<&mut LDNError>::into_inner(self) { + LDNError::Load(e) => Poll::Ready(Some(Ok(Bytes::from(e.clone())))), + LDNError::New(e) => Poll::Ready(Some(Ok(Bytes::from(e.clone())))), } } } diff --git a/fplus-lib/src/external_services/github.rs b/fplus-lib/src/external_services/github.rs index 61a006b5..87d1d105 100644 --- a/fplus-lib/src/external_services/github.rs +++ b/fplus-lib/src/external_services/github.rs @@ -3,7 +3,6 @@ use http::header::USER_AGENT; use http::{Request, Uri}; use hyper_rustls::HttpsConnectorBuilder; -use crate::core::LDNPullRequest; use octocrab::auth::AppAuth; use octocrab::models::issues::{Comment, Issue}; use octocrab::models::pulls::PullRequest; @@ -13,16 +12,53 @@ use octocrab::params::{pulls::State as PullState, State}; use octocrab::service::middleware::base_uri::BaseUriLayer; use octocrab::service::middleware::extra_headers::ExtraHeadersLayer; use octocrab::{AuthState, Error as OctocrabError, Octocrab, OctocrabBuilder, Page}; +use serde::{Deserialize, Serialize}; +use serde_json::json; use std::sync::Arc; const GITHUB_API_URL: &str = "https://api.github.com"; +struct LDNPullRequest { + pub title: String, + pub body: String, + pub branch_name: String, + pub path: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct RefObject { + sha: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct RefData { + #[serde(rename = "ref")] + _ref: String, + object: RefObject, +} +#[derive(Serialize, Deserialize, Debug, Clone)] +struct RefList(pub Vec); + +impl LDNPullRequest { + pub fn load(application_id: &str, owner_name: &str) -> Self { + Self { + title: format!("Refill Datacap for {}", application_id), + body: format!( + r#"This is an automated pull request to refill datacap for application: {}. +Please do not merge this pull request manually. +If you have any questions, please contact @filecoin-plus/lotus-devnet-team."#, + application_id + ), + branch_name: format!("refill-datacap-{}", application_id), + path: format!("applications/{}/{}.json", owner_name, application_id), + } + } +} struct GithubParams<'a> { pub owner: &'a str, pub repo: &'a str, pub app_id: u64, pub installation_id: u64, - pub main_branch_hash: &'a str, } impl GithubParams<'static> { @@ -32,7 +68,6 @@ impl GithubParams<'static> { repo: "filplus-tooling-backend-test", app_id: 373258, installation_id: 40514592, - main_branch_hash: "650a0aec11dc1cc436a45b316db5bb747e518514", } } } @@ -41,7 +76,10 @@ impl GithubParams<'static> { pub struct CreateRefillMergeRequestData { pub application_id: String, pub owner_name: String, + pub ref_request: Request, pub file_content: String, + pub file_name: String, + pub branch_name: String, pub commit: String, pub file_sha: String, } @@ -52,6 +90,8 @@ pub struct CreateMergeRequestData { pub owner_name: String, pub ref_request: Request, pub file_content: String, + pub file_name: String, + pub branch_name: String, pub commit: String, } @@ -60,7 +100,6 @@ pub struct GithubWrapper<'a> { pub inner: Arc, pub owner: &'a str, pub repo: &'a str, - pub main_branch_hash: &'a str, } impl GithubWrapper<'static> { @@ -70,7 +109,6 @@ impl GithubWrapper<'static> { repo, app_id, installation_id, - main_branch_hash, } = GithubParams::test_env(); dotenv::dotenv().ok(); let gh_private_key = match std::env::var("GH_PRIVATE_KEY") { @@ -112,7 +150,6 @@ impl GithubWrapper<'static> { let iod: InstallationId = installation_id.try_into().expect("Invalid installation id"); let installation = octocrab.installation(iod); Self { - main_branch_hash, owner, repo, inner: Arc::new(installation), @@ -176,14 +213,14 @@ impl GithubWrapper<'static> { pub async fn get_pull_request_files( &self, pr_number: u64, - ) -> Result, OctocrabError> { + ) -> Result<(u64, Vec), OctocrabError> { let iid: Page = self .inner .pulls(self.owner, self.repo) .media_type(octocrab::params::pulls::MediaType::Full) .list_files(pr_number) .await?; - Ok(iid.items.into_iter().map(|i| i.into()).collect()) + Ok((pr_number, iid.items.into_iter().map(|i| i.into()).collect())) } pub async fn list_branches(&self) -> Result, OctocrabError> { @@ -329,15 +366,36 @@ impl GithubWrapper<'static> { Ok(request) } + pub async fn get_main_branch_sha(&self) -> Result { + let url = + "https://api.github.com/repos/filecoin-project/filplus-tooling-backend-test/git/refs"; + let request = http::request::Builder::new().method(http::Method::GET).uri(url); + let request = self.inner.build_request::(request, None).unwrap(); + + let mut response = match self.inner.execute(request).await { + Ok( r) => r, + Err(e) => { + println!("Error getting main branch sha: {:?}", e); + return Ok("".to_string()); + } + }; + let response = response.body_mut(); + let body = hyper::body::to_bytes(response).await.unwrap(); + let shas = body.into_iter().map(|b| b as char).collect::(); + let shas: RefList = serde_json::from_str(&shas).unwrap(); + for sha in shas.0 { + if sha._ref == "refs/heads/main" { + return Ok(sha.object.sha); + } + } + Ok("".to_string()) + } + pub fn build_create_ref_request( &self, name: String, - head_hash: Option, + head_hash: String, ) -> Result, http::Error> { - let hash = match head_hash { - Some(hash) => hash, - None => self.main_branch_hash.to_string(), - }; let request = Request::builder() .method("POST") .uri(format!( @@ -346,7 +404,7 @@ impl GithubWrapper<'static> { )) .body(format!( r#"{{"ref": "refs/heads/{}","sha": "{}" }}"#, - name, hash + name, head_hash ))?; Ok(request) } @@ -401,32 +459,29 @@ impl GithubWrapper<'static> { pub async fn create_refill_merge_request( &self, data: CreateRefillMergeRequestData, - ) -> Result { + ) -> Result<(PullRequest, String), OctocrabError> { let CreateRefillMergeRequestData { - application_id, + application_id: _, + ref_request, owner_name, file_content, + file_name, + branch_name, commit, file_sha, } = data; - let pull_request_data = LDNPullRequest::load(&*application_id, &owner_name); - self.update_file_content( - &pull_request_data.path, - &commit, - &file_content, - &pull_request_data.branch_name, - &file_sha, - ) - .await?; + let _create_branch_res = self.create_branch(ref_request).await?; + self.update_file_content(&file_name, &commit, &file_content, &branch_name, &file_sha) + .await?; let pr = self .create_pull_request( - &pull_request_data.title, - &pull_request_data.branch_name, - &pull_request_data.body, + &format!("Datacap for {}", owner_name), + &branch_name, + &format!("BODY"), ) .await?; - Ok(pr) + Ok((pr, file_sha)) } pub async fn create_merge_request( @@ -434,28 +489,24 @@ impl GithubWrapper<'static> { data: CreateMergeRequestData, ) -> Result<(PullRequest, String), OctocrabError> { let CreateMergeRequestData { - application_id, + application_id: _, ref_request, owner_name, file_content, + file_name, + branch_name, commit, } = data; - let pull_request_data = LDNPullRequest::load(&*application_id, &owner_name); let _create_branch_res = self.create_branch(ref_request).await?; let add_file_res = self - .add_file( - &pull_request_data.path, - &file_content, - &commit, - &pull_request_data.branch_name, - ) + .add_file(&file_name, &file_content, &commit, &branch_name) .await?; let file_sha = add_file_res.content.sha; let pr = self .create_pull_request( - &pull_request_data.title, - &pull_request_data.branch_name, - &pull_request_data.body, + &format!("Datacap for {}", owner_name), + &branch_name, + &format!("BODY"), ) .await?; @@ -483,27 +534,31 @@ impl GithubWrapper<'static> { Ok(contents_items) } + + pub async fn get_all_files_from_branch( + &self, + branch: &str, + ) -> Result { + let contents_items = self + .inner + .repos(self.owner, self.repo) + .get_content() + .r#ref(branch) + .send() + .await?; + + Ok(contents_items) + } } #[cfg(test)] mod tests { - use octocrab::models::repos::Content; - use crate::external_services::github::GithubWrapper; - #[ignore] #[tokio::test] async fn test_basic_integration() { let gh = GithubWrapper::new(); - let files = gh.get_all_files().await.unwrap(); - // get a single valid file - let valid_filename = "Application:218.json"; - let file: Vec = files - .items - .into_iter() - .filter(|f| f.name == valid_filename) - .collect(); - dbg!(file); - assert!(false); + let res = gh.get_main_branch_sha().await.unwrap(); + assert_eq!(res.len() > 0, true); } } diff --git a/fplus-lib/src/parsers.rs b/fplus-lib/src/parsers.rs index cda57cd4..b42b950e 100644 --- a/fplus-lib/src/parsers.rs +++ b/fplus-lib/src/parsers.rs @@ -1,134 +1,314 @@ +use std::str::FromStr; + use markdown::{mdast::Node, to_mdast, ParseOptions}; use serde::{Deserialize, Serialize}; +use crate::core::application::file::{ + Client, DataType, Datacap, DatacapGroup, Project, +}; + #[derive(Serialize, Deserialize, Debug)] pub enum ParsedApplicationDataFields { + Version, + Address, + // Client Info Name, Region, + Industry, Website, - DatacapRequested, - DatacapWeeklyAllocation, - Address, - Identifier, - DataType, + SocialMedia, + Role, + // Project Info + ProjectID, + ProjectBriefHistory, + AssociatedProjects, + DataDesc, + DataSrc, + DataPrepare, + DataSampleLink, + ConfirmPublicDataset, + RetrivalFreq, + DataLifeSpan, + DataGeographies, + DataDistribution, + ProviderIDs, + // Datacap Info + DatacapGroup, + Type, + TotalRequestedAmount, + SingleSizeDataset, + Replicas, + WeeklyAllocation, InvalidField, } impl From for ParsedApplicationDataFields { fn from(s: String) -> Self { match s.as_str() { - "Data Owner Name" => ParsedApplicationDataFields::Name, - "Data Owner Country/Region" => ParsedApplicationDataFields::Region, - "Website" => ParsedApplicationDataFields::Website, - // "Custom multisig" => ParsedApplicationDataFields::CustomNotary, - "Identifier" => ParsedApplicationDataFields::Identifier, - "Data Type of Application" => ParsedApplicationDataFields::DataType, - "Total amount of DataCap being requested" => { - ParsedApplicationDataFields::DatacapRequested - } - "Weekly allocation of DataCap requested" => { - ParsedApplicationDataFields::DatacapWeeklyAllocation - } - "On-chain address for first allocation" => ParsedApplicationDataFields::Address, - _ => ParsedApplicationDataFields::InvalidField, - } + "Version" => ParsedApplicationDataFields::Version, + "On Chain Address" => ParsedApplicationDataFields::Address, + // Client Info + "Name" => ParsedApplicationDataFields::Name, + "Region" => ParsedApplicationDataFields::Region, + "Industry" => ParsedApplicationDataFields::Industry, + "Website" => ParsedApplicationDataFields::Website, + "Social Media" => ParsedApplicationDataFields::SocialMedia, + "Role" => ParsedApplicationDataFields::Role, + // Project Info + "Project ID" => ParsedApplicationDataFields::ProjectID, + "Brief history of your project and organization" => { + ParsedApplicationDataFields::ProjectBriefHistory + } + "Is this project associated with other projects/ecosystem stakeholders?" => { + ParsedApplicationDataFields::AssociatedProjects + } + "Describe the data being stored onto Filecoin" => { + ParsedApplicationDataFields::DataDesc + }, + "Where was the data currently stored in this dataset sourced from"=> { + ParsedApplicationDataFields::DataSrc + }, + "How do you plan to prepare the dataset" => { + ParsedApplicationDataFields::DataPrepare + }, + "Please share a sample of the data (a link to a file, an image, a table, etc., are good ways to do this." => { + ParsedApplicationDataFields::DataSampleLink + }, + "Confirm that this is a public dataset that can be retrieved by anyone on the network (i.e., no specific permissions or access rights are required to view the data)" => { + ParsedApplicationDataFields::ConfirmPublicDataset + }, + "What is the expected retrieval frequency for this data" => { + ParsedApplicationDataFields::RetrivalFreq + }, + "For how long do you plan to keep this dataset stored on Filecoin" => { + ParsedApplicationDataFields::DataLifeSpan + }, + "In which geographies do you plan on making storage deals" => { + ParsedApplicationDataFields::DataGeographies + }, + "How will you be distributing your data to storage providers" => { + ParsedApplicationDataFields::DataDistribution + }, + "Please list the provider IDs and location of the storage providers you will be working with. Note that it is a requirement to list a minimum of 5 unique provider IDs, and that your client address will be verified against this list in the future" => { + ParsedApplicationDataFields::ProviderIDs + }, + // Datacap info + "Group" => ParsedApplicationDataFields::DatacapGroup, + "Type" => ParsedApplicationDataFields::Type, + "Total Requested Amount" => ParsedApplicationDataFields::TotalRequestedAmount, + "single size dataset" => ParsedApplicationDataFields::SingleSizeDataset, + "Replicas" => ParsedApplicationDataFields::Replicas, + "Weekly Allocation" => ParsedApplicationDataFields::WeeklyAllocation, + // Invalid field + _ => ParsedApplicationDataFields::InvalidField, + } } } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ParsedLDN { - pub name: String, - pub region: String, - pub website: String, - pub datacap_requested: String, - pub datacap_weekly_allocation: String, - pub address: String, - pub identifier: String, - pub data_type: String, +pub struct ParsedIssue { + pub version: u8, + pub id: String, + pub client: Client, + pub project: Project, + pub datacap: Datacap, } -pub fn parse_ldn_app_body(body: &str) -> ParsedLDN { - let tree: Node = to_mdast(body, &ParseOptions::default()).unwrap(); - let mut name: Option = None; - let mut website: Option = None; - let mut data_type: Option = None; - let mut identifier: Option = None; - let mut datacap_weekly_allocation: Option = None; - let mut address: Option = None; - let mut datacap_requested: Option = None; - let mut region: Option = None; - for (index, i) in tree.children().unwrap().into_iter().enumerate().step_by(2) { - let prop: ParsedApplicationDataFields = i.to_string().into(); - let tree = tree.children().unwrap().into_iter(); - let value = match tree.skip(index + 1).next() { - Some(v) => v.to_string(), - None => continue, - }; - match prop { - ParsedApplicationDataFields::Name => { - name = Some(value); - } - ParsedApplicationDataFields::Region => { - region = Some(value); - } - ParsedApplicationDataFields::Website => { - website = Some(value); +impl ParsedIssue { + pub fn from_issue_body(body: &str) -> Self { + let tree: Node = to_mdast(body, &ParseOptions::default()).unwrap(); + let mut data: IssueValidData = IssueValidData::default(); + for (index, i) in tree.children().unwrap().into_iter().enumerate().step_by(2) { + let prop = i.to_string(); + let tree = tree.children().unwrap().into_iter(); + let value = match tree.skip(index + 1).next() { + Some(v) => v.to_string(), + None => continue, + }; + match prop.clone().into() { + ParsedApplicationDataFields::InvalidField => { + continue; + } + _ => data.0.push((Prop(prop), Value(value))), } - ParsedApplicationDataFields::DatacapRequested => { - datacap_requested = Some(value); - } - ParsedApplicationDataFields::DatacapWeeklyAllocation => { - datacap_weekly_allocation = Some(value); - } - ParsedApplicationDataFields::Address => { - address = Some(value); - } - ParsedApplicationDataFields::Identifier => { - identifier = Some(value); + } + let client = Client::from(data.clone()); + let project = Project::from(data.clone()); + let datacap = Datacap::from(data.clone()); + let version = data + .clone() + .0 + .into_iter() + .find(|(prop, _)| prop.0 == "Version") + .unwrap() + .1 + .0 + .parse::() + .unwrap(); + let id = data + .0 + .into_iter() + .find(|(prop, _)| prop.0 == "On Chain Address") + .unwrap() + .1 + .0; + + Self { + id, + version, + client, + project, + datacap, + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct Prop(pub String); +#[derive(Debug, Clone, Default)] +pub struct Value(pub String); + +#[derive(Debug, Clone, Default)] +pub struct IssueValidData(pub Vec<(Prop, Value)>); + +impl From for Project { + fn from(data: IssueValidData) -> Self { + let mut project = Project::default(); + for (prop, value) in data.0 { + match prop.0.into() { + ParsedApplicationDataFields::ProjectID => { + project.project_id = value.0; + } + ParsedApplicationDataFields::ProjectBriefHistory => { + project.history = value.0; + } + ParsedApplicationDataFields::AssociatedProjects => { + project.associated_projects = value.0; + } + ParsedApplicationDataFields::DataDesc => { + project.stored_data_desc = value.0; + } + ParsedApplicationDataFields::DataSrc => { + project.previous_stoarge = value.0; + } + ParsedApplicationDataFields::DataPrepare => { + project.dataset_prepare = value.0; + } + ParsedApplicationDataFields::DataSampleLink => { + project.data_sample_link = value.0; + } + ParsedApplicationDataFields::ConfirmPublicDataset => { + project.public_dataset = value.0; + } + ParsedApplicationDataFields::RetrivalFreq => { + project.retrival_frequency = value.0; + } + ParsedApplicationDataFields::DataLifeSpan => { + project.dataset_life_span = value.0; + } + ParsedApplicationDataFields::DataGeographies => { + project.geographis = value.0; + } + ParsedApplicationDataFields::DataDistribution => { + project.distribution = value.0; + } + ParsedApplicationDataFields::ProviderIDs => { + project.providers = value.0; + } + _ => {} } - ParsedApplicationDataFields::DataType => { - data_type = Some(value); + } + project + } +} + +impl From for Client { + fn from(data: IssueValidData) -> Self { + let mut client = Client::default(); + for (prop, value) in data.0 { + match prop.0.into() { + ParsedApplicationDataFields::Name => { + client.name = value.0; + } + ParsedApplicationDataFields::Region => { + client.region = value.0; + } + ParsedApplicationDataFields::Industry => { + client.industry = value.0; + } + ParsedApplicationDataFields::Website => { + client.website = value.0; + } + ParsedApplicationDataFields::SocialMedia => { + client.social_media = value.0; + } + ParsedApplicationDataFields::Role => { + client.role = value.0; + } + _ => {} } - ParsedApplicationDataFields::InvalidField => { - continue; + } + client + } +} + +impl From for Datacap { + fn from(data: IssueValidData) -> Self { + let mut datacap = Datacap::default(); + for (prop, value) in data.0 { + match prop.0.into() { + ParsedApplicationDataFields::DatacapGroup => { + datacap._group = DatacapGroup::from_str(&value.0).unwrap(); + } + ParsedApplicationDataFields::Type => { + datacap.data_type = DataType::from_str(&value.0).unwrap(); + } + ParsedApplicationDataFields::TotalRequestedAmount => { + datacap.total_requested_amount = value.0; + } + ParsedApplicationDataFields::SingleSizeDataset => { + datacap.single_size_dataset = value.0; + } + ParsedApplicationDataFields::Replicas => { + datacap.replicas = value.0.parse::().unwrap(); + } + ParsedApplicationDataFields::WeeklyAllocation => { + datacap.weekly_allocation = value.0; + } + _ => {} } } + datacap } - let parsed_ldn = ParsedLDN { - name: name.unwrap_or_else(|| "No Name".to_string()), - region: region.unwrap_or_else(|| "No Region".to_string()), - website: website.unwrap_or_else(|| "No Website".to_string()), - datacap_requested: datacap_requested.unwrap_or_else(|| "No Datacap Requested".to_string()), - datacap_weekly_allocation: datacap_weekly_allocation - .unwrap_or_else(|| "No Datacap Weekly Allocation".to_string()), - address: address.unwrap_or_else(|| "No Address".to_string()), - identifier: identifier.unwrap_or_else(|| "No Identifier".to_string()), - data_type: data_type.unwrap_or_else(|| "No Data Type".to_string()), - }; - parsed_ldn } #[cfg(test)] mod tests { use crate::external_services::github::GithubWrapper; - use super::*; - #[tokio::test] async fn test_parser() { let gh = GithubWrapper::new(); - let issue = gh.list_issue(63).await.unwrap(); - let parsed_ldn = parse_ldn_app_body(&issue.body.unwrap()); - assert_eq!(parsed_ldn.name, "Stojan"); - assert_eq!(parsed_ldn.region, "Afghanistan"); - assert_eq!(parsed_ldn.website, "https://pangeo-data.github.io/pangeo-cmip6-cloud/"); - assert_eq!(parsed_ldn.data_type, "Public, Open Dataset (Research/Non-Profit)"); - assert_eq!(parsed_ldn.datacap_requested, "15PiB"); - assert_eq!(parsed_ldn.datacap_weekly_allocation, "1PiB"); + let issue = gh.list_issue(359).await.unwrap(); + let parsed_ldn = super::ParsedIssue::from_issue_body(&issue.body.unwrap()); + + assert_eq!(parsed_ldn.version, 1); assert_eq!( - parsed_ldn.address, - "f1473tjqo3p5atezygb2koobcszvy5vftalcomcrq" + parsed_ldn.id, + "f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1v".to_string() ); - assert_eq!(parsed_ldn.identifier, "No response"); + + assert_eq!(parsed_ldn.client.name, "ClientNme".to_string()); + assert_eq!(parsed_ldn.client.industry, "ClientIndustry".to_string()); + assert_eq!(parsed_ldn.client.region, "Afghanistan".to_string()); + assert_eq!(parsed_ldn.client.website, "ClientWebsite".to_string()); + assert_eq!(parsed_ldn.client.social_media, "ClientSM".to_string()); + assert_eq!(parsed_ldn.client.role, "ClientRole".to_string()); + + assert_eq!(parsed_ldn.project.project_id, "IDID".to_string()); + assert_eq!(parsed_ldn.project.history, "history".to_string()); + assert_eq!(parsed_ldn.project.associated_projects, "asodfjads".to_string()); + + assert_eq!(parsed_ldn.datacap.total_requested_amount, "11GB".to_string()); } }