From 26e2ef31658111d77e42fb15834b45b58349dd6c Mon Sep 17 00:00:00 2001 From: Tiago Dinis Date: Mon, 12 Sep 2022 18:24:37 +0100 Subject: [PATCH 1/2] feat: add mods remove command --- src/commands/mods/mod.rs | 1 + src/commands/mods/remove.rs | 26 +++++++ src/entities/app.rs | 2 +- src/entities/moderator.rs | 151 +++++++++++++++++++++++++++--------- src/main.rs | 27 ++++--- 5 files changed, 160 insertions(+), 47 deletions(-) create mode 100644 src/commands/mods/remove.rs diff --git a/src/commands/mods/mod.rs b/src/commands/mods/mod.rs index 8dd1bf3..7648803 100644 --- a/src/commands/mods/mod.rs +++ b/src/commands/mods/mod.rs @@ -1,2 +1,3 @@ pub mod add; +pub mod remove; use super::*; \ No newline at end of file diff --git a/src/commands/mods/remove.rs b/src/commands/mods/remove.rs new file mode 100644 index 0000000..0098541 --- /dev/null +++ b/src/commands/mods/remove.rs @@ -0,0 +1,26 @@ +use crate::{commands::{expect_token, ask_for_app}, entities::{moderator::Mod, app::App, user::fetch_user}}; +macro_rules! handle_result { + ($v:expr) => { + match $v { + Ok(v) => v, + Err(err) => { + super::err(&err.to_string()); + std::process::exit(1); + } + } + } +} +pub fn remove(id: u128) { + let token = expect_token(); + let app_id = handle_result!(ask_for_app(token.clone(), "remove the moderator")); + let app = handle_result!(App::fetch(token.clone(), app_id)); + let user = handle_result!(fetch_user(token.clone())); + let moderator = handle_result!(Mod::fetch_mod(token.clone(), &user, &app)); + if let Some(moderator) = moderator { + handle_result!(moderator.remove(token.clone())); + super::log(&format!("{} was removed from your app!", id)); + } else { + super::err(&format!("{} isn't a moderator on {} ({})", id, app.name, app.id)); + std::process::exit(1); + } +} \ No newline at end of file diff --git a/src/entities/app.rs b/src/entities/app.rs index a630da3..862a872 100644 --- a/src/entities/app.rs +++ b/src/entities/app.rs @@ -38,7 +38,7 @@ impl App { pub fn fetch(token: String, id: u128) -> Result { #[derive(Deserialize)] struct AppResponse { - pub apps: App, + pub apps: App } let client = reqwest::blocking::Client::new(); let req = client diff --git a/src/entities/moderator.rs b/src/entities/moderator.rs index b85f3d2..483b6a4 100644 --- a/src/entities/moderator.rs +++ b/src/entities/moderator.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use serde::{Deserialize, Serialize}; use serde_enum_str::*; -use super::{user::User, app::App, FetchError}; +use super::{app::App, user::User, FetchError}; #[derive(Deserialize_enum_str, Serialize_enum_str, Clone)] pub enum Feature { #[serde(rename = "start_app")] @@ -21,71 +21,148 @@ pub enum Feature { #[serde(rename = "edit_ram")] SetRam, #[serde(rename = "backup_app")] - Backup + Backup, } impl Debug for Feature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str( - match self { - Self::Backup => "backup", - Self::Commit => "commit", - Self::Restart => "restart", - Self::SeeLogs => "logs", - Self::SetRam => "ram", - Self::Start => "start", - Self::Status => "status", - Self::Stop => "stop" - } - ) + f.write_str(match self { + Self::Backup => "backup", + Self::Commit => "commit", + Self::Restart => "restart", + Self::SeeLogs => "logs", + Self::SetRam => "ram", + Self::Start => "start", + Self::Status => "status", + Self::Stop => "stop", + }) } } -#[derive(Deserialize, Serialize)] -pub struct Mod { +#[derive(Deserialize, Serialize, Clone)] +pub struct Mod<'a> { #[serde(rename = "modID")] user_id: u128, #[serde(rename = "perms")] - features: Vec + features: Vec, + #[serde(skip)] + app: Option<&'a App>, } -impl Mod { - pub fn new(token: String, user: &User, app: &App, features: Vec) -> Result { +impl<'a> Mod<'a> { + pub fn new( + token: String, + user: &User, + app: &'a App, + features: Vec, + ) -> Result, FetchError> { let moderator = Self { user_id: user.user_id.parse().unwrap(), - features + features, + app: Some(app), }; - moderator.add_to(token, app)?; + moderator.add(token)?; Ok(moderator) } + pub fn fetch_mod( + token: String, + user: &User, + app: &'a App, + ) -> Result>, FetchError> { + #[derive(Deserialize)] + struct Response<'a> { + status: String, + message: Option, + team: Option>>, + } + let client = reqwest::blocking::Client::new(); + let req = client + .get(crate::api_url!(format!("/app/{}/team", app.id))) + .header("api-token", token); + match req.send() { + Ok(res) => match res.json::() { + Err(err) => Err(FetchError::FailedWithMessage(err.to_string())), + Ok(response) => { + if response.status == "ok" { + let moderator = response + .team + .unwrap() + .iter() + .find(|m| m.user_id.to_string() == user.user_id) + .map(|m| Self { + app: Some(app), + ..m.clone() + }); + Ok(moderator) + } else { + Err(FetchError::FailedWithMessage(response.message.unwrap())) + } + } + }, + Err(err) => Err(FetchError::FailedToConnect(err)), + } + } pub fn get_features(&self) -> Vec { self.features.clone() } - pub fn add_to(&self, token: String, app: &App) -> Result<(), FetchError>{ + /// Returns the app this moderator is at + /// # Panics + /// This will panic if `app` is **None** + pub fn get_app(&'a self) -> &'a App { + self.app.unwrap() + } + /// Adds this moderator to the app + /// # Panics + /// This will panic if `app` is **None** + pub fn add(&'a self, token: String) -> Result<(), FetchError> { #[derive(Deserialize)] struct Response { status: String, - message: Option + message: Option, } let client = reqwest::blocking::Client::new(); let req = client - .post(crate::api_url!(format!("/app/{}/team", app.id))) + .post(crate::api_url!(format!("/app/{}/team", self.get_app().id))) .header("api-token", token) .json(self); match req.send() { - Ok(res) => { - match res.json::() { - Err(err) => { - Err(FetchError::FailedWithMessage(err.to_string())) + Ok(res) => match res.json::() { + Err(err) => Err(FetchError::FailedWithMessage(err.to_string())), + Ok(response) => { + if response.status == "ok" { + Ok(()) + } else { + Err(FetchError::FailedWithMessage(response.message.unwrap())) } - Ok(response) => { - if response.status == "ok" { - Ok(()) - } else { - Err(FetchError::FailedWithMessage(response.message.unwrap())) - } + } + }, + Err(err) => Err(FetchError::FailedToConnect(err)), + } + } + + pub fn remove(self, token: String) -> Result<(), FetchError> { + #[derive(Deserialize)] + struct Response { + status: String, + message: Option, + } + let client = reqwest::blocking::Client::new(); + let req = client + .delete(crate::api_url!(format!( + "/app/{}/team/{}", + self.get_app().id, + self.user_id + ))) + .header("api-token", token); + match req.send() { + Ok(res) => match res.json::() { + Err(err) => Err(FetchError::FailedWithMessage(err.to_string())), + Ok(response) => { + if response.status == "ok" { + Ok(()) + } else { + Err(FetchError::FailedWithMessage(response.message.unwrap())) } } - - } + }, Err(err) => Err(FetchError::FailedToConnect(err)), } } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index bd28a3d..fb68d7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,6 +113,12 @@ fn main() -> std::io::Result<()> { .about("Adds a mod to an app, by default, the mod can only see the logs and status, use `discloud mods allow` to allow more actions.") .arg(Arg::new("id").value_parser(value_parser!(u128)).action(clap::ArgAction::Set).required(true)) ) + .subcommand( + Command::new("remove") + .alias("rm") + .about("Removes a moderator from your app.") + .arg(Arg::new("id").value_parser(value_parser!(u128)).action(clap::ArgAction::Set).required(true)) + ) .after_help("Be careful with what people you add and what permissions you give: With Great Power comes Great Responsability.") ); let matches = cmd.get_matches(); @@ -157,16 +163,19 @@ fn main() -> std::io::Result<()> { commands::aboutme::aboutme(); Ok(()) } - Some(("mods", matches)) => { - match matches.subcommand() { - Some(("add", matches)) => { - let id: u128 = *matches.get_one("id").unwrap(); - commands::mods::add::add(id); - Ok(()) - } - _ => unreachable!() + Some(("mods", matches)) => match matches.subcommand() { + Some(("add", matches)) => { + let id: u128 = *matches.get_one("id").unwrap(); + commands::mods::add::add(id); + Ok(()) } - } + Some(("remove", matches)) => { + let id: u128 = *matches.get_one("id").unwrap(); + commands::mods::remove::remove(id); + Ok(()) + } + _ => unreachable!(), + }, _ => unreachable!(), } } From defe329cf2cbfa868b4222524a98295256366258 Mon Sep 17 00:00:00 2001 From: Tiago Dinis Date: Mon, 12 Sep 2022 21:40:55 +0100 Subject: [PATCH 2/2] fix: a lot of stuff --- Cargo.lock | 92 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 +++- src/auth.rs | 3 ++ src/commands/aboutme.rs | 85 +++++++++++++++++++--------------- src/commands/apps.rs | 1 + src/commands/authstatus.rs | 1 + src/commands/commit.rs | 9 +++- src/commands/init.rs | 1 + src/commands/login.rs | 1 + src/commands/logs.rs | 1 + src/commands/mod.rs | 35 +++++++++++--- src/commands/mods/add.rs | 27 ++++------- src/commands/mods/remove.rs | 37 ++++++++------- src/commands/remove.rs | 1 + src/commands/restart.rs | 1 + src/commands/start.rs | 1 + src/commands/stop.rs | 1 + src/commands/upload.rs | 22 +++++++-- src/config_dir.rs | 2 + src/entities/app.rs | 38 +++++++++------ src/entities/moderator.rs | 64 +++++++++++++------------- src/entities/user.rs | 19 ++++---- src/main.rs | 20 +++++--- 23 files changed, 318 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a43de84..be0460a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "atty" version = "0.2.14" @@ -363,9 +372,12 @@ dependencies = [ "directories", "reqwest", "sentry", + "sentry-tracing", "serde", "serde-enum-str", "spinners", + "tracing", + "tracing-subscriber", "walkdir", "zip", ] @@ -1089,6 +1101,7 @@ dependencies = [ "httpdate", "reqwest", "sentry-core", + "sentry-tracing", "tokio", ] @@ -1105,6 +1118,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "sentry-tracing" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea50bcf843510179a7ba41c9fe4d83a8958e9f8adf707ec731ff999297536344" +dependencies = [ + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "sentry-types" version = "0.27.0" @@ -1217,6 +1241,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "slab" version = "0.4.7" @@ -1226,6 +1259,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + [[package]] name = "socket2" version = "0.4.7" @@ -1357,6 +1396,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.44" @@ -1458,9 +1506,21 @@ checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.29" @@ -1468,6 +1528,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -1546,6 +1632,12 @@ dependencies = [ "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 71ecee6..2e8bd96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ version = "0.3.2-alpha" edition = "2021" description = "Blazingly fast Discloud CLI" license = "Apache-2.0" -license-file = "LICENSE" categories = ["command-line-utilities"] keywords = ["discloud", "cli", "bot", "fast", "discord"] readme = "README.md" @@ -19,9 +18,14 @@ colored = "2.0.0" dialoguer = "0.10.2" directories = "4.0.1" reqwest = { version = "0.11.11", features = ["blocking", "json", "multipart", "rustls-tls"], default-features=false } -sentry = {version = "0.27.0", default-features = false, features = ["rustls", "reqwest"]} +sentry = { version = "0.27.0", default-features = false, features = ["rustls", "reqwest", "tracing"] } +sentry-tracing = "0.27.0" serde = { version = "1.0.144", features = ["derive"] } serde-enum-str = "0.2.5" spinners = "4.1.0" +tracing = "0.1.36" +tracing-subscriber = { version = "0.3.15" } walkdir = "2.3.2" zip = "0.6.2" +[features] +sentry_prod = [] diff --git a/src/auth.rs b/src/auth.rs index e08909f..007b5ed 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,14 +1,17 @@ use crate::api_url; +#[tracing::instrument] pub fn login(token: String) -> std::io::Result<()> { let token_file = crate::config_dir::get_path(".discloud_token").unwrap(); std::fs::write(token_file, token)?; Ok(()) } +#[tracing::instrument] pub fn get_token() -> std::io::Result { let token_file = crate::config_dir::get_path(".discloud_token").unwrap(); std::fs::read_to_string(token_file) } +#[tracing::instrument] pub fn validate_token() -> bool { match get_token() { Ok(token) => { diff --git a/src/commands/aboutme.rs b/src/commands/aboutme.rs index 97785e6..329bbb2 100644 --- a/src/commands/aboutme.rs +++ b/src/commands/aboutme.rs @@ -1,53 +1,62 @@ use chrono::{Datelike, Timelike}; use colored::Colorize; -pub fn aboutme(){ +#[tracing::instrument] +pub fn aboutme() { let token = super::expect_token(); match crate::entities::user::fetch_user(token.clone()) { Ok(user) => { println!("ID: {}", user.user_id.bright_black()); println!("Plan: {}", color_plan(user.plan)); let end_date = user.plan_data_end; - println!("Your plan ends at: {}/{}/{} {}:{}", end_date.day(), end_date.month(), end_date.year(), end_date.hour(), end_date.minute()); - println!(" Which means you have {} days left!", user.last_data_left.days.to_string().green().bold()); + println!( + "Your plan ends at: {}/{}/{} {}:{}", + end_date.day(), + end_date.month(), + end_date.year(), + end_date.hour(), + end_date.minute() + ); + println!( + " Which means you have {} days left!", + user.last_data_left.days.to_string().green().bold() + ); println!("Memory:"); - println!(" Total: {}{}", user.total_ram_mb.to_string().green().bold(), "MB".green().bold()); - println!(" Used: {}{}", user.ram_used_mb.to_string().green().bold(), "MB".green().bold()); - println!(" Available: {}{}", (user.total_ram_mb - user.ram_used_mb).to_string().green().bold(), "MB".green().bold()); + println!( + " Total: {}{}", + user.total_ram_mb.to_string().green().bold(), + "MB".green().bold() + ); + println!( + " Used: {}{}", + user.ram_used_mb.to_string().green().bold(), + "MB".green().bold() + ); + println!( + " Available: {}{}", + (user.total_ram_mb - user.ram_used_mb) + .to_string() + .green() + .bold(), + "MB".green().bold() + ); println!("Locale: {}", user.locale.blue()); } - Err(err) => super::err(&err.to_string()) - } + Err(err) => super::err(&err.to_string()), + } } +#[tracing::instrument] fn color_plan(plan: String) -> String { - match plan.as_str() { - "Free" => { - plan.bright_black().to_string() - } - "Carbon" => { - plan.bright_black().bold().to_string() - } - "Gold" => { - plan.yellow().bold().to_string() - } - "Platinum" => { - plan.blue().bold().to_string() - } - "Diamond" => { - plan.cyan().bold().to_string() - } - "Ruby" => { - plan.red().bold().to_string() - } - "Sapphire" => { - plan.bright_red().bold().to_string() - } - "Krypton" => { - plan.bright_green().bold().to_string() - } - "Special" => { - plan.bright_cyan().bold().to_string() - } - _ => unreachable!() - } + match plan.as_str() { + "Free" => plan.bright_black().to_string(), + "Carbon" => plan.bright_black().bold().to_string(), + "Gold" => plan.yellow().bold().to_string(), + "Platinum" => plan.blue().bold().to_string(), + "Diamond" => plan.cyan().bold().to_string(), + "Ruby" => plan.red().bold().to_string(), + "Sapphire" => plan.bright_red().bold().to_string(), + "Krypton" => plan.bright_green().bold().to_string(), + "Special" => plan.bright_cyan().bold().to_string(), + _ => unreachable!(), + } } diff --git a/src/commands/apps.rs b/src/commands/apps.rs index a4a7d05..b12ca5e 100644 --- a/src/commands/apps.rs +++ b/src/commands/apps.rs @@ -1,6 +1,7 @@ use colored::Colorize; use crate::entities::FetchError; +#[tracing::instrument] pub fn apps() { let token = super::expect_token(); match crate::entities::app::App::fetch_all(token.clone()) { diff --git a/src/commands/authstatus.rs b/src/commands/authstatus.rs index 09e8192..2660f14 100644 --- a/src/commands/authstatus.rs +++ b/src/commands/authstatus.rs @@ -1,5 +1,6 @@ use crate::auth; use std::io::ErrorKind; +#[tracing::instrument] pub fn authstatus() -> std::io::Result<()> { match auth::get_token() { Ok(token) => { diff --git a/src/commands/commit.rs b/src/commands/commit.rs index 2464ca0..629f5bb 100644 --- a/src/commands/commit.rs +++ b/src/commands/commit.rs @@ -9,11 +9,13 @@ use zip::write::FileOptions; use std::fs::File; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; +#[tracing::instrument] fn get_zip_file_path() -> PathBuf { let mut dst_file = std::env::temp_dir(); dst_file.push("discloud.zip"); dst_file } +#[tracing::instrument] pub fn commit() { let token = super::expect_token(); let app_id = match super::ask_for_app(token.clone(), "commit") { @@ -75,6 +77,7 @@ where Result::Ok(()) } +#[tracing::instrument] fn zip_dir_to_file( src_dir: &str, dst_file: &str, @@ -111,10 +114,14 @@ fn zip_dir_to_file( Ok(()) } +#[tracing::instrument] fn upload_zip(token: String, app_id: u128) -> Result<(), String> { let file_path = get_zip_file_path(); let file_path = file_path.to_str().unwrap(); - let client = reqwest::blocking::Client::builder().timeout(None).build().unwrap(); + let client = reqwest::blocking::Client::builder() + .timeout(None) + .build() + .unwrap(); let form = reqwest::blocking::multipart::Form::new().file("file", file_path); match form { Err(err) => Err(format!("Couldn't open zip file: {}", err)), diff --git a/src/commands/init.rs b/src/commands/init.rs index 9c2d312..86b5848 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -51,6 +51,7 @@ impl App { } } } +#[tracing::instrument] pub fn init() -> std::io::Result<()> { use dialoguer::Input; if std::path::Path::new("discloud.config").exists() { diff --git a/src/commands/login.rs b/src/commands/login.rs index 53e8f21..2bcede0 100644 --- a/src/commands/login.rs +++ b/src/commands/login.rs @@ -1,4 +1,5 @@ use clap::*; +#[tracing::instrument] pub fn login(matches: &ArgMatches) -> std::io::Result<()> { let token = matches.get_one::("token").unwrap().clone(); if let Err(err) = crate::auth::login(token) { diff --git a/src/commands/logs.rs b/src/commands/logs.rs index 3ac5f76..0c7d4a2 100644 --- a/src/commands/logs.rs +++ b/src/commands/logs.rs @@ -1,5 +1,6 @@ use spinners::{Spinner, Spinners}; +#[tracing::instrument] pub fn logs(){ let token = super::expect_token(); match super::ask_for_app(token.clone(), "show the logs") { diff --git a/src/commands/mod.rs b/src/commands/mod.rs index e3cbc22..9193270 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,21 +1,42 @@ -pub mod mods; pub mod aboutme; -pub mod logs; -pub mod stop; -pub mod start; -pub mod restart; pub mod apps; pub mod authstatus; pub mod commit; pub mod init; pub mod login; -pub mod upload; +pub mod logs; +pub mod mods; pub mod remove; +pub mod restart; +pub mod start; +pub mod stop; +pub mod upload; use colored::Colorize; use dialoguer::{theme::ColorfulTheme, Select}; use spinners::*; - +#[macro_export] +macro_rules! handle_result { + ($v:expr) => { + match $v { + Ok(v) => v, + Err(err) => { + super::err(&err.to_string()); + std::process::exit(1); + } + } + }; + ($v:expr, $spinner:ident) => { + match $v { + Ok(v) => v, + Err(err) => { + $spinner.stop_with_message(super::format_err(&err.to_string())); + std::process::exit(1); + } + } + }; +} use crate::entities::FetchError; +#[tracing::instrument] pub fn expect_token() -> String { if crate::auth::validate_token() { log("Your token is valid!"); diff --git a/src/commands/mods/add.rs b/src/commands/mods/add.rs index d7030c1..dd83ae7 100644 --- a/src/commands/mods/add.rs +++ b/src/commands/mods/add.rs @@ -1,20 +1,13 @@ -use crate::{commands::{expect_token, ask_for_app}, entities::{moderator::{Mod, Feature}, app::App, user::fetch_user}}; -macro_rules! handle_result { - ($v:expr) => { - match $v { - Ok(v) => v, - Err(err) => { - super::err(&err.to_string()); - std::process::exit(1); - } - } - } -} +use spinners::Spinner; + +use crate::{commands::{expect_token, ask_for_app}, entities::{moderator::{Mod, Feature}}}; + +#[tracing::instrument] pub fn add(id: u128) { let token = expect_token(); - let app_id = handle_result!(ask_for_app(token.clone(), "add a moderator")); - let app = handle_result!(App::fetch(token.clone(), app_id)); - let user = handle_result!(fetch_user(token.clone())); - let moderator = handle_result!(Mod::new(token.clone(), &user, &app, vec![Feature::SeeLogs, Feature::Status])); - super::log(&format!("Permissions {:?} have been given to {}", moderator.get_features(), id)); + let app_id = crate::handle_result!(ask_for_app(token.clone(), "add a moderator")); + let mut spinner = Spinner::new(spinners::Spinners::Bounce, format!("Adding {} as a moderator", id)); + + let moderator = crate::handle_result!(Mod::new(token.clone(), id, app_id, vec![Feature::SeeLogs, Feature::Status]), spinner); + spinner.stop_with_message(super::format_log(&format!("Permissions {:?} have been given to {}", moderator.get_features(), id))); } \ No newline at end of file diff --git a/src/commands/mods/remove.rs b/src/commands/mods/remove.rs index 0098541..ab3ba75 100644 --- a/src/commands/mods/remove.rs +++ b/src/commands/mods/remove.rs @@ -1,26 +1,25 @@ -use crate::{commands::{expect_token, ask_for_app}, entities::{moderator::Mod, app::App, user::fetch_user}}; -macro_rules! handle_result { - ($v:expr) => { - match $v { - Ok(v) => v, - Err(err) => { - super::err(&err.to_string()); - std::process::exit(1); - } - } - } -} +use spinners::Spinner; + +use crate::{ + commands::{ask_for_app, expect_token}, + entities::{app::App, moderator::Mod} +}; +use crate::handle_result; +#[tracing::instrument] pub fn remove(id: u128) { let token = expect_token(); let app_id = handle_result!(ask_for_app(token.clone(), "remove the moderator")); - let app = handle_result!(App::fetch(token.clone(), app_id)); - let user = handle_result!(fetch_user(token.clone())); - let moderator = handle_result!(Mod::fetch_mod(token.clone(), &user, &app)); + let mut spinner = Spinner::new(spinners::Spinners::Moon, format!("Sending {} to the moon", id)); + let app = handle_result!(App::fetch(token.clone(), app_id), spinner); + let moderator = handle_result!(Mod::fetch_mod(token.clone(), id, app_id), spinner); if let Some(moderator) = moderator { - handle_result!(moderator.remove(token.clone())); - super::log(&format!("{} was removed from your app!", id)); + handle_result!(moderator.remove(token.clone()), spinner); + spinner.stop_with_message(super::format_log(&format!("{} was removed from your app!", id))); } else { - super::err(&format!("{} isn't a moderator on {} ({})", id, app.name, app.id)); + spinner.stop_with_message(super::format_err(&format!( + "{} isn't a moderator on {} ({})", + id, app.name, app.id + ))); std::process::exit(1); } -} \ No newline at end of file +} diff --git a/src/commands/remove.rs b/src/commands/remove.rs index 80fc6ee..3d3ca28 100644 --- a/src/commands/remove.rs +++ b/src/commands/remove.rs @@ -1,4 +1,5 @@ use spinners::*; +#[tracing::instrument] pub fn remove() { let token = super::expect_token(); match super::ask_for_app(token.clone(), "delete") { diff --git a/src/commands/restart.rs b/src/commands/restart.rs index 9263b40..359beb9 100644 --- a/src/commands/restart.rs +++ b/src/commands/restart.rs @@ -1,4 +1,5 @@ use spinners::*; +#[tracing::instrument] pub fn restart() { let token = super::expect_token(); match super::ask_for_app(token.clone(), "restart") { diff --git a/src/commands/start.rs b/src/commands/start.rs index 91e40bb..18ef507 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -1,4 +1,5 @@ use spinners::*; +#[tracing::instrument] pub fn start() { let token = super::expect_token(); match super::ask_for_app(token.clone(), "start") { diff --git a/src/commands/stop.rs b/src/commands/stop.rs index a658442..32642fb 100644 --- a/src/commands/stop.rs +++ b/src/commands/stop.rs @@ -1,4 +1,5 @@ use spinners::*; +#[tracing::instrument] pub fn stop() { let token = super::expect_token(); match super::ask_for_app(token.clone(), "shutdown") { diff --git a/src/commands/upload.rs b/src/commands/upload.rs index aea684c..a83e758 100644 --- a/src/commands/upload.rs +++ b/src/commands/upload.rs @@ -15,6 +15,7 @@ fn get_zip_file_path() -> PathBuf { dst_file.push("discloud.zip"); dst_file } +#[tracing::instrument] pub fn upload() { let token = super::expect_token(); let src_dir = "."; @@ -109,11 +110,14 @@ fn upload_zip(token: String) -> Result<(), String> { struct UploadResponse { status: String, message: Option, - logs: Option + logs: Option, } let file_path = get_zip_file_path(); let file_path = file_path.to_str().unwrap(); - let client = reqwest::blocking::Client::builder().timeout(None).build().unwrap(); + let client = reqwest::blocking::Client::builder() + .timeout(None) + .build() + .unwrap(); let form = reqwest::blocking::multipart::Form::new().file("file", file_path); match form { Err(err) => Err(format!("Couldn't open zip file: {}", err)), @@ -130,14 +134,22 @@ fn upload_zip(token: String) -> Result<(), String> { let res: UploadResponse = res.json().unwrap(); if res.status == "error" { if let Some(logs) = res.logs { - Err(format!("Upload failed: API Returned {}: {}\nLogs:\n{}", status.as_u16(), res.message.unwrap(), logs)) + Err(format!( + "Upload failed: API Returned {}: {}\nLogs:\n{}", + status.as_u16(), + res.message.unwrap(), + logs + )) } else { - Err(format!("Upload failed: API Returned {}: {}", status.as_u16(), res.message.unwrap())) + Err(format!( + "Upload failed: API Returned {}: {}", + status.as_u16(), + res.message.unwrap() + )) } } else { Ok(()) } - } } } diff --git a/src/config_dir.rs b/src/config_dir.rs index 6ecd0d3..62486e5 100644 --- a/src/config_dir.rs +++ b/src/config_dir.rs @@ -1,5 +1,6 @@ use directories::ProjectDirs; /// Returns config base paths according to the conventions of the OS +#[tracing::instrument] pub fn get_proj_dir() -> Option { if let Some(proj_dirs) = ProjectDirs::from("com", "Discloud", "Discloud Cli") { Some(proj_dirs.config_dir().to_path_buf()) @@ -7,6 +8,7 @@ pub fn get_proj_dir() -> Option { None } } +#[tracing::instrument] /// Pushes file to the path returned by get_proj_dir() pub fn get_path(file: &str) -> Option { let mut result = get_proj_dir()?; diff --git a/src/entities/app.rs b/src/entities/app.rs index 862a872..1656a48 100644 --- a/src/entities/app.rs +++ b/src/entities/app.rs @@ -1,7 +1,7 @@ use super::*; use serde::Deserialize; -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct App { pub name: String, pub id: String, @@ -14,6 +14,7 @@ pub struct App { pub lang: String, } impl App { + #[tracing::instrument] pub fn fetch_all(token: String) -> Result, FetchError> { #[derive(Deserialize)] struct AppsResponse { @@ -34,11 +35,11 @@ impl App { Err(err) => Err(FetchError::FailedToConnect(err)), } } - + #[tracing::instrument] pub fn fetch(token: String, id: u128) -> Result { #[derive(Deserialize)] struct AppResponse { - pub apps: App + pub apps: App, } let client = reqwest::blocking::Client::new(); let req = client @@ -55,27 +56,34 @@ impl App { Err(err) => Err(FetchError::FailedToConnect(err)), } } + #[tracing::instrument] pub fn get_logs(token: String, id: u128) -> Result { #[derive(Deserialize)] struct Terminal { - big: String + big: String, } #[derive(Deserialize)] struct AppLogs { - terminal: Terminal + terminal: Terminal, } #[derive(Deserialize)] struct LogsResponse { - apps: Option + apps: Option, } let client = reqwest::blocking::Client::new(); let req = client .get(crate::api_url!(format!("/app/{}/logs", id))) .header("api-token", token); - match req.send() { + match req.send() { Ok(res) => { if res.status().is_success() { - Ok(res.json::().unwrap().apps.unwrap().terminal.big) + Ok(res + .json::() + .unwrap() + .apps + .unwrap() + .terminal + .big) } else { Err(FetchError::APIReturnedError(res.status().as_u16())) } @@ -83,12 +91,13 @@ impl App { Err(err) => Err(FetchError::FailedToConnect(err)), } } + #[tracing::instrument] pub fn restart(token: String, id: u128) -> Result<(), FetchError> { let client = reqwest::blocking::Client::new(); let req = client .put(crate::api_url!(format!("/app/{}/restart", id))) .header("api-token", token); - match req.send() { + match req.send() { Ok(res) => { if res.status().is_success() { Ok(()) @@ -99,12 +108,13 @@ impl App { Err(err) => Err(FetchError::FailedToConnect(err)), } } + #[tracing::instrument] pub fn start(token: String, id: u128) -> Result<(), FetchError> { let client = reqwest::blocking::Client::new(); let req = client .put(crate::api_url!(format!("/app/{}/start", id))) .header("api-token", token); - match req.send() { + match req.send() { Ok(res) => { if res.status().is_success() { Ok(()) @@ -115,12 +125,13 @@ impl App { Err(err) => Err(FetchError::FailedToConnect(err)), } } + #[tracing::instrument] pub fn stop(token: String, id: u128) -> Result<(), FetchError> { let client = reqwest::blocking::Client::new(); let req = client .put(crate::api_url!(format!("/app/{}/stop", id))) .header("api-token", token); - match req.send() { + match req.send() { Ok(res) => { if res.status().is_success() { Ok(()) @@ -131,12 +142,13 @@ impl App { Err(err) => Err(FetchError::FailedToConnect(err)), } } + #[tracing::instrument] pub fn delete(token: String, id: u128) -> Result<(), FetchError> { let client = reqwest::blocking::Client::new(); let req = client .delete(crate::api_url!(format!("/app/{}/delete", id))) .header("api-token", token); - match req.send() { + match req.send() { Ok(res) => { if res.status().is_success() { Ok(()) @@ -147,6 +159,4 @@ impl App { Err(err) => Err(FetchError::FailedToConnect(err)), } } - - } diff --git a/src/entities/moderator.rs b/src/entities/moderator.rs index 483b6a4..d459564 100644 --- a/src/entities/moderator.rs +++ b/src/entities/moderator.rs @@ -1,9 +1,8 @@ use std::fmt::Debug; - use serde::{Deserialize, Serialize}; use serde_enum_str::*; +use super::FetchError; -use super::{app::App, user::User, FetchError}; #[derive(Deserialize_enum_str, Serialize_enum_str, Clone)] pub enum Feature { #[serde(rename = "start_app")] @@ -37,44 +36,50 @@ impl Debug for Feature { }) } } -#[derive(Deserialize, Serialize, Clone)] -pub struct Mod<'a> { +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct Mod { #[serde(rename = "modID")] - user_id: u128, + user_id: String, #[serde(rename = "perms")] features: Vec, #[serde(skip)] - app: Option<&'a App>, + app_id: u128, } -impl<'a> Mod<'a> { +impl Mod { + #[tracing::instrument] pub fn new( token: String, - user: &User, - app: &'a App, + user_id: u128, + app_id: u128, features: Vec, - ) -> Result, FetchError> { + ) -> Result { let moderator = Self { - user_id: user.user_id.parse().unwrap(), + user_id: user_id.to_string(), features, - app: Some(app), + app_id, }; moderator.add(token)?; Ok(moderator) } + + pub fn id(&self) -> u128 { + self.user_id.parse().unwrap() + } + #[tracing::instrument] pub fn fetch_mod( token: String, - user: &User, - app: &'a App, - ) -> Result>, FetchError> { + user_id: u128, + app_id: u128, + ) -> Result, FetchError> { #[derive(Deserialize)] - struct Response<'a> { + struct Response { status: String, message: Option, - team: Option>>, + team: Option>, } let client = reqwest::blocking::Client::new(); let req = client - .get(crate::api_url!(format!("/app/{}/team", app.id))) + .get(crate::api_url!(format!("/app/{}/team", app_id))) .header("api-token", token); match req.send() { Ok(res) => match res.json::() { @@ -85,9 +90,9 @@ impl<'a> Mod<'a> { .team .unwrap() .iter() - .find(|m| m.user_id.to_string() == user.user_id) + .find(|m| m.user_id == user_id.to_string()) .map(|m| Self { - app: Some(app), + app_id, ..m.clone() }); Ok(moderator) @@ -99,19 +104,13 @@ impl<'a> Mod<'a> { Err(err) => Err(FetchError::FailedToConnect(err)), } } + #[tracing::instrument] pub fn get_features(&self) -> Vec { self.features.clone() } - /// Returns the app this moderator is at - /// # Panics - /// This will panic if `app` is **None** - pub fn get_app(&'a self) -> &'a App { - self.app.unwrap() - } /// Adds this moderator to the app - /// # Panics - /// This will panic if `app` is **None** - pub fn add(&'a self, token: String) -> Result<(), FetchError> { + #[tracing::instrument] + pub fn add(&self, token: String) -> Result<(), FetchError> { #[derive(Deserialize)] struct Response { status: String, @@ -119,7 +118,7 @@ impl<'a> Mod<'a> { } let client = reqwest::blocking::Client::new(); let req = client - .post(crate::api_url!(format!("/app/{}/team", self.get_app().id))) + .post(crate::api_url!(format!("/app/{}/team", self.app_id))) .header("api-token", token) .json(self); match req.send() { @@ -137,6 +136,7 @@ impl<'a> Mod<'a> { } } + #[tracing::instrument] pub fn remove(self, token: String) -> Result<(), FetchError> { #[derive(Deserialize)] struct Response { @@ -147,7 +147,7 @@ impl<'a> Mod<'a> { let req = client .delete(crate::api_url!(format!( "/app/{}/team/{}", - self.get_app().id, + self.app_id, self.user_id ))) .header("api-token", token); @@ -165,4 +165,4 @@ impl<'a> Mod<'a> { Err(err) => Err(FetchError::FailedToConnect(err)), } } -} +} \ No newline at end of file diff --git a/src/entities/user.rs b/src/entities/user.rs index fc97939..8b97c39 100644 --- a/src/entities/user.rs +++ b/src/entities/user.rs @@ -2,14 +2,14 @@ use serde::Deserialize; use super::FetchError; -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct UserDate { pub days: u32, pub hours: u32, pub minutes: u32, pub seconds: u32, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct User { #[serde(rename = "userID")] pub user_id: String, @@ -27,14 +27,15 @@ pub struct User { #[serde(rename = "planDataEnd")] pub plan_data_end: chrono::DateTime, #[serde(rename = "lastDataLeft")] - pub last_data_left: UserDate + pub last_data_left: UserDate, } +#[tracing::instrument] pub fn fetch_user(token: String) -> Result { #[derive(Deserialize)] struct UserResponse { user: User, status: String, - message: String + message: String, } let client = reqwest::blocking::Client::new(); let req = client @@ -45,13 +46,9 @@ pub fn fetch_user(token: String) -> Result { if res.status().is_success() { let res = res.json::().unwrap(); match res.status.as_str() { - "ok" => { - Ok(res.user) - } - "error" => { - Err(FetchError::FailedWithMessage(res.message)) - } - _ => unreachable!() + "ok" => Ok(res.user), + "error" => Err(FetchError::FailedWithMessage(res.message)), + _ => unreachable!(), } } else { Err(FetchError::APIReturnedError(res.status().as_u16())) diff --git a/src/main.rs b/src/main.rs index fb68d7d..16ff6fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod commands; pub mod config_dir; pub mod entities; use clap::*; +use tracing_subscriber::prelude::*; #[macro_export] macro_rules! api_url { () => { @@ -16,19 +17,26 @@ macro_rules! api_url { }; } fn main() -> std::io::Result<()> { - if let Some(dir) = config_dir::get_proj_dir() { - std::fs::create_dir_all(dir)?; - } else { - eprintln!("ERROR: Couldn't find a directory for config files."); - return Ok(()); - } + tracing_subscriber::Registry::default() + .with(sentry::integrations::tracing::layer()) + .init(); + let _guard = sentry::init(( "https://0512a7bb28624cfc848cdad08f2186a7@sentry.discloudbot.com/3", sentry::ClientOptions { release: sentry::release_name!(), + environment: Some("development".into()), + traces_sample_rate: 1.0, + ..Default::default() }, )); + if let Some(dir) = config_dir::get_proj_dir() { + std::fs::create_dir_all(dir)?; + } else { + eprintln!("ERROR: Couldn't find a directory for config files."); + return Ok(()); + } let cmd = Command::new("discloud") .about("Blazingly Fast CLI for discloud") .subcommand_required(true)