Skip to content
This repository was archived by the owner on Sep 1, 2023. It is now read-only.

Commit ca20489

Browse files
feat(#3): add commit command
2 parents 30ab1f4 + b073b7a commit ca20489

File tree

14 files changed

+382
-52
lines changed

14 files changed

+382
-52
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dialoguer = "0.10.2"
1212
directories = "4.0.1"
1313
reqwest = { version = "0.11.11", features = ["blocking", "json", "multipart"] }
1414
sentry = "0.27.0"
15+
serde = { version = "1.0.144", features = ["derive"] }
1516
spinners = "4.1.0"
1617
walkdir = "2.3.2"
1718
zip = "0.6.2"

src/auth.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::api_url;
22

3-
pub fn login(token: String) -> std::io::Result<()>{
3+
pub fn login(token: String) -> std::io::Result<()> {
44
let token_file = crate::config_dir::get_path(".discloud_token").unwrap();
55
std::fs::write(token_file, token)?;
66
Ok(())
@@ -9,20 +9,19 @@ pub fn get_token() -> std::io::Result<String> {
99
let token_file = crate::config_dir::get_path(".discloud_token").unwrap();
1010
std::fs::read_to_string(token_file)
1111
}
12-
pub fn validate_token() -> bool{
12+
pub fn validate_token() -> bool {
1313
match get_token() {
1414
Ok(token) => {
1515
let client = reqwest::blocking::Client::new();
16-
let req = client.get(concat!(api_url!(), "/user"))
16+
let req = client
17+
.get(concat!(api_url!(), "/user"))
1718
.header("api-token", token);
1819
if let Ok(res) = req.send() {
1920
res.status().is_success()
2021
} else {
2122
false
2223
}
23-
},
24-
Err(_) => {
25-
false
2624
}
25+
Err(_) => false,
2726
}
28-
}
27+
}

src/commands/authstatus.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use std::io::ErrorKind;
21
use crate::auth;
2+
use std::io::ErrorKind;
33
pub fn authstatus() -> std::io::Result<()> {
44
match auth::get_token() {
55
Ok(token) => {
66
super::log("You're already logged in!\n");
77
let mut stars = String::new();
8-
for _ in 0..token.len()-5 {
8+
for _ in 0..token.len() - 5 {
99
stars.push('*');
1010
}
1111
super::log(&format!("Token: {}{}", &token[..5], stars));
@@ -14,11 +14,11 @@ pub fn authstatus() -> std::io::Result<()> {
1414
Err(err) => match err.kind() {
1515
ErrorKind::NotFound => {
1616
super::err("You're not logged in yet!");
17-
},
17+
}
1818
err => {
1919
super::err(&format!("Couldn't open token file: {}", err));
2020
}
21-
}
21+
},
2222
}
2323
Ok(())
24-
}
24+
}

src/commands/commit.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use colored::Colorize;
2+
use spinners::Spinner;
3+
use std::io::prelude::*;
4+
use std::io::{Seek, Write};
5+
use std::iter::Iterator;
6+
use zip::result::ZipError;
7+
use zip::write::FileOptions;
8+
9+
use std::fs::File;
10+
use std::path::{Path, PathBuf};
11+
use walkdir::{DirEntry, WalkDir};
12+
fn get_zip_file_path() -> PathBuf {
13+
let mut dst_file = std::env::temp_dir();
14+
dst_file.push("discloud.zip");
15+
dst_file
16+
}
17+
pub fn commit() {
18+
let token = super::expect_token();
19+
let app_id = match super::ask_for_app(token.clone()) {
20+
Ok(app_id) => app_id,
21+
Err(error) => {
22+
super::err(&format!("Couldn't fetch apps: {}", error));
23+
std::process::exit(1);
24+
}
25+
};
26+
27+
let src_dir = ".";
28+
let dst_file = get_zip_file_path();
29+
match zip_dir_to_file(src_dir, dst_file.to_str().unwrap(), METHOD_DEFLATED) {
30+
Ok(_) => {}
31+
Err(e) => super::err(&format!("Failed to zip: {:?}", e)),
32+
}
33+
let mut spinner = Spinner::new(spinners::Spinners::Earth, "Committing app...".to_string());
34+
let msg = match upload_zip(token, app_id) {
35+
Ok(()) => super::format_log("Your app was successfully commited!"),
36+
Err(err) => super::format_err(&err),
37+
};
38+
spinner.stop_with_message(msg);
39+
}
40+
41+
const METHOD_DEFLATED: zip::CompressionMethod = zip::CompressionMethod::Deflated;
42+
43+
fn zip_dir<T>(
44+
it: &mut dyn Iterator<Item = DirEntry>,
45+
prefix: &str,
46+
writer: T,
47+
method: zip::CompressionMethod,
48+
) -> zip::result::ZipResult<()>
49+
where
50+
T: Write + Seek,
51+
{
52+
let mut zip = zip::ZipWriter::new(writer);
53+
let options = FileOptions::default()
54+
.compression_method(method)
55+
.unix_permissions(0o755);
56+
57+
let mut buffer = Vec::new();
58+
for entry in it {
59+
let path = entry.path();
60+
let name = path.strip_prefix(Path::new(prefix)).unwrap();
61+
if path.is_file() {
62+
print!("⌛ Zipping file: {}\r", name.to_str().unwrap());
63+
zip.start_file(name.to_str().unwrap(), options)?;
64+
let mut f = File::open(path)?;
65+
66+
f.read_to_end(&mut buffer)?;
67+
zip.write_all(&*buffer)?;
68+
buffer.clear();
69+
println!("{}", "✔".green().bold());
70+
} else if name.as_os_str().len() != 0 {
71+
zip.add_directory(name.to_str().unwrap(), options)?;
72+
}
73+
}
74+
zip.finish()?;
75+
Result::Ok(())
76+
}
77+
78+
fn zip_dir_to_file(
79+
src_dir: &str,
80+
dst_file: &str,
81+
method: zip::CompressionMethod,
82+
) -> zip::result::ZipResult<()> {
83+
if !Path::new(src_dir).is_dir() {
84+
return Err(ZipError::FileNotFound);
85+
}
86+
let writer = File::create(dst_file).unwrap();
87+
88+
let walkdir = WalkDir::new(src_dir.to_string());
89+
let it = walkdir.into_iter();
90+
91+
zip_dir(
92+
&mut it.filter_map(|e| {
93+
if let Ok(e) = e {
94+
let components = e.path().components().collect::<Vec<_>>();
95+
if components.len() < 2 {
96+
Some(e)
97+
} else {
98+
match components[1].as_os_str().to_str().unwrap() {
99+
"target" | ".git" | "build" | "out" | "node_modules" | ".gitignore" => None,
100+
_ => Some(e),
101+
}
102+
}
103+
} else {
104+
None
105+
}
106+
}),
107+
src_dir,
108+
writer,
109+
method,
110+
)?;
111+
112+
Ok(())
113+
}
114+
fn upload_zip(token: String, app_id: u128) -> Result<(), String> {
115+
let file_path = get_zip_file_path();
116+
let file_path = file_path.to_str().unwrap();
117+
let client = reqwest::blocking::Client::new();
118+
let form = reqwest::blocking::multipart::Form::new().file("file", file_path);
119+
match form {
120+
Err(err) => Err(format!("Couldn't open zip file: {}", err)),
121+
Ok(form) => {
122+
let req = client
123+
.put(crate::api_url!(format!("/app/{}/commit", app_id)))
124+
.multipart(form)
125+
.header("api-token", token);
126+
let res = req.send();
127+
match res {
128+
Err(err) => Err(err.to_string()),
129+
Ok(res) => {
130+
if res.status().is_success() {
131+
Ok(())
132+
} else {
133+
Err(format!(
134+
"Discloud API returned {} http code: {}",
135+
res.status().as_u16(),
136+
res.text().unwrap()
137+
))
138+
}
139+
}
140+
}
141+
}
142+
}
143+
}

src/commands/init.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use dialoguer::{theme::ColorfulTheme, Select};
22

33
fn vec_from_str(s: String) -> Vec<String> {
4-
s.split(",").map(|s|s.trim().into()).collect()
4+
s.split(",").map(|s| s.trim().into()).collect()
55
}
66

77
#[derive(Default)]
@@ -26,9 +26,19 @@ impl App {
2626
match &self.typ {
2727
AppTyp::Site => {
2828
if self.apt.len() > 0 {
29-
format!("ID={}\nMAIN={}\nAUTORESTART={}\nRAM={}\nAPT={}\nTYPE=site\nVERSION=latest", self.subdomain, self.main, self.autorestart, self.ram, self.apt.join(","))
29+
format!(
30+
"ID={}\nMAIN={}\nAUTORESTART={}\nRAM={}\nAPT={}\nTYPE=site\nVERSION=latest",
31+
self.subdomain,
32+
self.main,
33+
self.autorestart,
34+
self.ram,
35+
self.apt.join(",")
36+
)
3037
} else {
31-
format!("ID={}\nMAIN={}\nAUTORESTART={}\nRAM={}\nTYPE=site\nVERSION=latest", self.subdomain, self.main, self.autorestart, self.ram)
38+
format!(
39+
"ID={}\nMAIN={}\nAUTORESTART={}\nRAM={}\nTYPE=site\nVERSION=latest",
40+
self.subdomain, self.main, self.autorestart, self.ram
41+
)
3242
}
3343
}
3444
AppTyp::Bot => {
@@ -82,9 +92,9 @@ pub fn init() -> std::io::Result<()> {
8292
.with_prompt("Memory (MB)")
8393
.interact_text()?;
8494
let apt: String = Input::with_theme(&ColorfulTheme::default())
85-
.with_prompt("APT Packages")
86-
.allow_empty(true)
87-
.interact_text()?;
95+
.with_prompt("APT Packages")
96+
.allow_empty(true)
97+
.interact_text()?;
8898
if apt.len() > 0 {
8999
app.apt = vec_from_str(apt);
90100
}

src/commands/login.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clap::*;
2-
pub fn login(matches: &ArgMatches) -> std::io::Result<()>{
2+
pub fn login(matches: &ArgMatches) -> std::io::Result<()> {
33
let token = matches.get_one::<String>("token").unwrap().clone();
44
if let Err(err) = crate::auth::login(token) {
55
super::err(format!("Couldn't save the token: {}", err.kind().to_string()).as_str());
@@ -9,4 +9,4 @@ pub fn login(matches: &ArgMatches) -> std::io::Result<()>{
99
super::check_token();
1010
Ok(())
1111
}
12-
}
12+
}

src/commands/mod.rs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
1-
pub mod login;
21
pub mod authstatus;
2+
pub mod commit;
33
pub mod init;
4+
pub mod login;
45
pub mod upload;
5-
use spinners::*;
66
use colored::Colorize;
7-
pub fn expect_token() -> String{
7+
use dialoguer::{theme::ColorfulTheme, Select};
8+
use spinners::*;
9+
10+
use crate::entities::FetchError;
11+
pub fn expect_token() -> String {
812
if crate::auth::validate_token() {
9-
format_log("Your token is valid!");
13+
log("Your token is valid!");
1014
return crate::auth::get_token().unwrap();
1115
} else {
12-
format_err("Your token is invalid!");
16+
err("Your token is invalid!");
1317
std::process::exit(1);
1418
}
1519
}
1620
pub fn check_token() {
1721
let mut validate_spinner = Spinner::new(Spinners::Dots12, "Checking token".into());
18-
validate_spinner.stop_with_message(
19-
if crate::auth::validate_token() {
20-
format_log("Your token is valid!")
21-
} else {
22-
format_err("Your token is invalid!")
23-
}
24-
);
22+
validate_spinner.stop_with_message(if crate::auth::validate_token() {
23+
format_log("Your token is valid!")
24+
} else {
25+
format_err("Your token is invalid!")
26+
});
2527
}
2628
pub fn format_log(msg: &str) -> String {
2729
format!("{} {}", "✔".green().bold(), msg)
@@ -67,4 +69,27 @@ mod tests {
6769
out.push_str(" Some warnings");
6870
assert_eq!(super::format_warn("Some warnings"), out)
6971
}
70-
}
72+
}
73+
pub fn ask_for_app(token: String) -> Result<u128, FetchError> {
74+
let user = crate::entities::user::fetch_user(token.clone())?;
75+
match user.apps.len() {
76+
0 => {
77+
err("You don't have any app on discloud, use `discloud up` to upload an app.");
78+
std::process::exit(1)
79+
}
80+
1 => Ok(user.apps[0].parse().unwrap()),
81+
_ => {
82+
let apps = crate::entities::app::fetch_apps(token)?;
83+
let options = apps
84+
.iter()
85+
.map(|app| format!("{}: ({}) {}", app.name, app.lang, app.id))
86+
.collect::<Vec<_>>();
87+
let chosen_opt = Select::with_theme(&ColorfulTheme::default())
88+
.items(&options)
89+
.with_prompt("Which app you want to commit?")
90+
.interact()
91+
.unwrap();
92+
Ok(apps[chosen_opt].id.parse().unwrap())
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)