Skip to content

Commit

Permalink
Merge pull request #47 from Asone/42-feature-add-profile-command
Browse files Browse the repository at this point in the history
feat(cli): key validation with tests
  • Loading branch information
Asone authored May 19, 2023
2 parents cf68d87 + fd5f1fa commit b0722b3
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 135 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion nostrss-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ tonic = "0.9.2"
tabled = "0.12.0"
url = "2.3.1"
cron = "0.12.0"

secp256k1 = "0.27.0"
bech32 = "0.9.1"
[dependencies.nostrss_grpc]
path = "../nostrss-grpc"

Expand Down
4 changes: 2 additions & 2 deletions nostrss-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Please, do note that any operation performed through CLI will be currently lost
| Command | Description |
|-|-|
| nostrss-cli profile list | Lists the profiles |
| nostrss-cli profile add | Add a new profile (not implemented yet) |
| nostrss-cli profile add | Add a new profile |
| nostrss-cli profile delete | Remove a profile |
| nostrss-cli profile info | Get info of a specific profile |

Expand All @@ -25,6 +25,6 @@ Please, do note that any operation performed through CLI will be currently lost
| Command | Description |
|-|-|
| nostrss-cli feed list | Lists the feeds |
| nostrss-cli feed add | Add a new feed (not implemented yet) |
| nostrss-cli feed add | Add a new feed |
| nostrss-cli feed delete | Remove a feed |
| nostrss-cli feed info | Get info of a specific feed |
5 changes: 1 addition & 4 deletions nostrss-cli/src/commands/feed.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
#![allow(dead_code)]

use std::str::FromStr;

use clap::{Parser, ValueEnum};
use nostrss_grpc::grpc::{
nostrss_grpc_client::NostrssGrpcClient, AddFeedRequest, DeleteFeedRequest, FeedInfoRequest,
FeedItem, FeedsListRequest,
};
use tabled::{Table, Tabled};
use tabled::Tabled;
use tonic::{async_trait, transport::Channel};
use url::Url;

use crate::input::{formatter::InputFormatter, input::InputValidators};

Expand Down
6 changes: 3 additions & 3 deletions nostrss-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ pub trait CommandsHandler {
_ = stdin().read_line(&mut data);

match validator {
Some(validator) => match validator(data.clone()) {
true => data,
Some(validator) => match validator(data.clone().trim().to_string()) {
true => data.trim().to_string(),
false => {
println!("Invalid value provided.");
self.get_input(label, Some(validator))
}
},
None => data,
None => data.trim().to_string(),
}
}

Expand Down
74 changes: 70 additions & 4 deletions nostrss-cli/src/commands/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

use clap::{Parser, ValueEnum};
use nostrss_grpc::grpc::{
nostrss_grpc_client::NostrssGrpcClient, DeleteProfileRequest, ProfileInfoRequest, ProfileItem,
ProfilesListRequest,
nostrss_grpc_client::NostrssGrpcClient, AddProfileRequest, DeleteProfileRequest,
NewProfileItem, ProfileInfoRequest, ProfileItem, ProfilesListRequest,
};
use tabled::{Table, Tabled};
use tonic::{async_trait, transport::Channel};

use crate::input::{formatter::InputFormatter, input::InputValidators};

use super::CommandsHandler;

#[derive(Clone, PartialEq, Parser, Debug, ValueEnum)]
Expand Down Expand Up @@ -148,7 +150,7 @@ impl CommandsHandler for ProfileCommandsHandler {}
impl ProfileCommandsHandler {
pub async fn handle(&mut self, action: ProfileActions) {
match action {
ProfileActions::Add => self.add(),
ProfileActions::Add => self.add().await,
ProfileActions::Delete => self.delete().await,
ProfileActions::List => self.list().await,
ProfileActions::Info => self.info().await,
Expand Down Expand Up @@ -178,7 +180,71 @@ impl ProfileCommandsHandler {
}
}

fn add(&self) {}
async fn add(&mut self) {
println!("=== Add a profile ===");
let id = self.get_input("Id: ", Some(InputValidators::required_input_validator));
let private_key: String = self
.get_input(
"Private key (hex or bech32): ",
Some(InputValidators::key_validator),
)
.trim()
.to_string();
let name: Option<String> =
InputFormatter::string_nullifier(self.get_input("(optional) Name: ", None));
let relays = InputFormatter::input_to_vec(
self.get_input("(optional) Relays ids (separated with coma):", None),
);
let display_name: Option<String> =
InputFormatter::string_nullifier(self.get_input("(optional) Display name: ", None));
let description: Option<String> =
InputFormatter::string_nullifier(self.get_input("(optional) Description: ", None));
let picture: Option<String> = InputFormatter::string_nullifier(
self.get_input("(optional) Profile picture URL: ", None),
);
let banner: Option<String> = InputFormatter::string_nullifier(
self.get_input("(optional) Banner picture URL: ", None),
);
let nip05: Option<String> =
InputFormatter::string_nullifier(self.get_input("(optional) NIP-05: ", None));
let lud16: Option<String> =
InputFormatter::string_nullifier(self.get_input("(optional) Lud16: ", None));
let pow_level: String = self.get_input("(optional) Publishing PoW level: ", None);
let pow_level = pow_level.parse().unwrap_or(0);

let recommended_relays: Vec<String> = InputFormatter::input_to_vec(self.get_input(
"(optional) Recommended relays ids (seperated with coma): ",
None,
));

let request = tonic::Request::new(AddProfileRequest {
profile: NewProfileItem {
id,
private_key,
name,
relays,
display_name,
description,
picture,
banner,
nip05,
lud16,
pow_level: Some(pow_level),
recommended_relays,
},
});

let response = self.client.add_profile(request).await;

match response {
Ok(_) => {
println!("Profile successfuly added");
}
Err(e) => {
println!("Error: {}: {}", e.code(), e.message());
}
}
}

async fn delete(&mut self) {
let id = self.get_input("Id: ", None);
Expand Down
7 changes: 7 additions & 0 deletions nostrss-cli/src/input/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ impl InputFormatter {
pub fn input_to_vec(value: String) -> Vec<String> {
value.split(',').map(|e| e.trim().to_string()).collect()
}

pub fn string_nullifier(value: String) -> Option<String> {
match value.len() > 0 {
true => Some(value.trim().to_string()),
false => None,
}
}
}

#[cfg(test)]
Expand Down
66 changes: 66 additions & 0 deletions nostrss-cli/src/input/input.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::str::FromStr;

use bech32::FromBase32;
use url::Url;

pub struct InputValidators {}
Expand Down Expand Up @@ -30,6 +31,38 @@ impl InputValidators {
Err(_) => false,
}
}

pub fn key_validator(value: String) -> bool {
let decoded = bech32::decode(value.trim());

match decoded {
Ok(result) => {
if let Ok(bytes) = Vec::<u8>::from_base32(&result.1) {
// Check if the decoded bytes have the expected length
if bytes.len() != 32 {
return false;
}
}
}
Err(_) => {
let key_bytes = value.trim().as_bytes();

// Validate key length
if key_bytes.len() != 64 {
return false;
}

// Validate key contains only hexadecimal characters
for &byte in key_bytes.iter() {
if !byte.is_ascii_hexdigit() {
return false;
}
}
}
};

true
}
}

#[cfg(test)]
Expand Down Expand Up @@ -79,4 +112,37 @@ mod tests {

assert_eq!(result, false);
}

#[test]
fn key_validator_test() {
let value = "6789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345".to_string();

let result = InputValidators::key_validator(value);

assert_eq!(result, true);

let value = "6789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string();

let result = InputValidators::key_validator(value);

assert_eq!(result, false);

let value = "6789abcdef0123456789abcdef0123456789abcdef0123456789abkdef012345".to_string();

let result = InputValidators::key_validator(value);

assert_eq!(result, false);

let value = "nsec14uuscmj9ac0f3lqfq33cuq6mu8q7sscvpyyhsjn5r8q9w5pdafgq0qrj8a".to_string();

let result = InputValidators::key_validator(value);

assert_eq!(result, true);

let value = "nsec14uuscmj9ac0f3lqfq33cuq6mu8q7sscvpyyhsjn5r8q9w5pdafgq0qrj8d".to_string();

let result = InputValidators::key_validator(value);

assert_eq!(result, false);
}
}
70 changes: 35 additions & 35 deletions nostrss-core/src/grpc/feed_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use nostrss_grpc::grpc::{
self, AddFeedRequest, AddFeedResponse, DeleteFeedRequest, DeleteFeedResponse, FeedInfoRequest,
FeedInfoResponse, FeedItem, FeedsListRequest, FeedsListResponse,
};
use std::{ops::Index, sync::Arc};
use std::sync::Arc;
use tokio::sync::{Mutex, MutexGuard};
use tonic::{Code, Request, Response, Status};

Expand All @@ -25,6 +25,40 @@ impl FeedRequestHandler {
Ok(Response::new(grpc::FeedsListResponse { feeds }))
}

pub async fn feed_info(
app: MutexGuard<'_, App>,
request: Request<FeedInfoRequest>,
) -> Result<Response<FeedInfoResponse>, Status> {
let id = &request.into_inner().id;
match app.rss.feeds.clone().into_iter().find(|f| &f.id == id) {
Some(feed) => Ok(Response::new(FeedInfoResponse {
feed: FeedItem::from(feed),
})),
None => {
return Err(Status::new(Code::NotFound, "Feed not found"));
}
}
}

pub async fn add_feed(
mut app: MutexGuard<'_, App>,
request: Request<AddFeedRequest>,
) -> Result<Response<AddFeedResponse>, Status> {
let data = request.into_inner();
let feed = Feed::from(data);
let map = Arc::new(Mutex::new(app.feeds_map.clone()));
let clients = Arc::new(Mutex::new(app.clients.clone()));

app.rss.feeds.push(feed.clone());

let job = schedule(feed.schedule.clone().as_str(), feed.clone(), map, clients).await;

_ = app.rss.feeds_jobs.insert(feed.id.clone(), job.guid());
_ = app.rss.scheduler.add(job).await;

Ok(Response::new(AddFeedResponse {}))
}

// Interface to delete a feed on instance
pub async fn delete_feed(
mut app: MutexGuard<'_, App>,
Expand Down Expand Up @@ -56,40 +90,6 @@ impl FeedRequestHandler {
_ = app.scheduler.remove(job_uuid.unwrap()).await;
Ok(Response::new(grpc::DeleteFeedResponse {}))
}

pub async fn add_feed(
mut app: MutexGuard<'_, App>,
request: Request<AddFeedRequest>,
) -> Result<Response<AddFeedResponse>, Status> {
let data = request.into_inner();
let feed = Feed::from(data);
let map = Arc::new(Mutex::new(app.feeds_map.clone()));
let clients = Arc::new(Mutex::new(app.clients.clone()));

app.rss.feeds.push(feed.clone());

let job = schedule(feed.schedule.clone().as_str(), feed.clone(), map, clients).await;

_ = app.rss.feeds_jobs.insert(feed.id.clone(), job.guid());
_ = app.rss.scheduler.add(job).await;

Ok(Response::new(AddFeedResponse {}))
}

pub async fn feed_info(
app: MutexGuard<'_, App>,
request: Request<FeedInfoRequest>,
) -> Result<Response<FeedInfoResponse>, Status> {
let id = &request.into_inner().id;
match app.rss.feeds.clone().into_iter().find(|f| &f.id == id) {
Some(feed) => Ok(Response::new(FeedInfoResponse {
feed: FeedItem::from(feed),
})),
None => {
return Err(Status::new(Code::NotFound, "Feed not found"));
}
}
}
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit b0722b3

Please sign in to comment.