1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::path::PathBuf;

use clap::{Parser, ValueHint};
use eyre::eyre;
use itertools::Itertools;
use reqwest::{Client, StatusCode};
use serde_json::Value;

use crate::{
    cmd::utils::{generate_pdf_from_url, upload_ipfs},
    constants::{AUDIT_ENDPOINT, TRUSTBLOCK_API_KEY_HEADER},
    types::{Audit, Chains, Project},
    utils::{apply_dotenv, parse_json, validate_links, validate_pdf},
};

#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Parser)]
pub struct PublishAuditArgs {
    #[clap(
        short,
        long = "audit-data",
        help = "File path to JSON file with Audit data",
        value_name = "AUDIT_DATA_JSON_FILE",
        value_hint = ValueHint::FilePath,
        required(true)
    )]
    audit_file_path: PathBuf,

    #[clap(
        short,
        long = "report-pdf",
        help = "File path to audit report PDF file",
        value_name = "AUDIT_REPORT_PDF_FILE",
        value_hint = ValueHint::FilePath,
        value_parser = validate_pdf,
        required(true)
    )]
    report_pdf_file_path: Option<PathBuf>,

    #[clap(
        short = 'u',
        long = "report-url",
        help = "Url to audit report",
        value_name = "AUDIT_REPORT_URL",
        value_hint = ValueHint::Url,
        value_parser = validate_links,
        conflicts_with = "report_pdf_file_path",
        required(true)
    )]
    report_url: Option<String>,

    #[clap(short = 'k', long)]
    api_key: Option<String>,
}

impl PublishAuditArgs {
    #[allow(clippy::future_not_send)]
    pub async fn run(self) -> eyre::Result<()> {
        apply_dotenv()?;

        let audit_data = parse_json::<Audit>(&self.audit_file_path)?;

        let api_key = match self.api_key {
            Some(token) => token,
            None => std::env::var("API_KEY")?,
        };

        let project_id = audit_data
            .project
            .clone()
            .fetch_project_id(&api_key)
            .await?;

        let report_pdf_file_path = match self.report_pdf_file_path {
            Some(path) => path,
            None => {
                generate_pdf_from_url(self.report_url.expect("should not fail"), &api_key).await?
            }
        };

        let (report_hash, report_file_url) = upload_ipfs(report_pdf_file_path, &api_key).await?;

        let client = Client::new();

        let audit_endpoint =
            std::env::var("AUDIT_ENDPOINT").unwrap_or_else(|_| AUDIT_ENDPOINT.to_string());

        let chains = audit_data
            .contracts
            .iter()
            .map(|contract| contract.chain)
            .unique()
            .collect::<Vec<Chains>>();

        let project = Project {
            id: project_id,
            ..audit_data.clone().project
        };

        let audit_data_send = Audit {
            chains,
            report_hash: report_hash.clone(),
            report_file_url,
            project,
            ..audit_data
        };

        let response = client
            .post(audit_endpoint)
            .header(TRUSTBLOCK_API_KEY_HEADER, api_key)
            .json(&audit_data_send)
            .send()
            .await?;

        let status = response.status();

        let body = response.json::<Value>().await?;

        //TODO: create a separate error struct with "thiserror" crate
        match status {
            StatusCode::BAD_REQUEST => {
                let error = &body["error"];

                if error == "Report hash is not a unique value." {
                    println!("Audit already published to DB!\n");
                    return Ok(());
                }

                if error == "Project domain is not a unique value." {
                    println!("Project already exists on DB!\n");
                    return Ok(());
                }

                Err(eyre!(
                    "Could not publish to DB. Check validity of the audit data: {body} "
                ))
            }
            StatusCode::CREATED => {
                println!("Audit published successfully!\n");
                Ok(())
            }
            _ => Err(eyre!("Could not publish to DB. Response: {status}")),
        }
    }
}