Move CLI subcommands to cli module

This commit is contained in:
silverpill 2022-08-12 22:38:41 +00:00
parent 29402f0113
commit 5497f50977
3 changed files with 301 additions and 239 deletions

View file

@ -1,178 +1,10 @@
use anyhow::Error;
use chrono::{Duration, Utc};
use clap::Parser;
use tokio_postgres::GenericClient;
use uuid::Uuid;
use mitra::activitypub::builders::delete_note::prepare_delete_note;
use mitra::activitypub::builders::delete_person::prepare_delete_person;
use mitra::activitypub::fetcher::fetchers::fetch_actor;
use mitra::activitypub::handlers::update_person::update_remote_profile;
use mitra::config::{parse_config, Config};
use mitra::cli::{Opts, SubCommand};
use mitra::config::parse_config;
use mitra::database::create_database_client;
use mitra::database::migrate::apply_migrations;
use mitra::ethereum::signatures::generate_ecdsa_key;
use mitra::ethereum::sync::save_current_block_number;
use mitra::ethereum::utils::key_to_ethereum_address;
use mitra::logger::configure_logger;
use mitra::models::attachments::queries::delete_unused_attachments;
use mitra::models::cleanup::find_orphaned_files;
use mitra::models::posts::queries::{delete_post, find_extraneous_posts, get_post_by_id};
use mitra::models::profiles::queries::{
delete_profile,
get_profile_by_actor_id,
get_profile_by_id,
reset_subscriptions,
};
use mitra::models::users::queries::{
create_invite_code,
get_invite_codes,
get_user_by_id,
};
use mitra::utils::crypto::{generate_private_key, serialize_private_key};
use mitra::utils::files::remove_files;
/// Admin CLI tool
#[derive(Parser)]
struct Opts {
#[clap(subcommand)]
subcmd: SubCommand,
}
#[derive(Parser)]
enum SubCommand {
GenerateRsaKey(GenerateRsaKey),
GenerateEthereumAddress(GenerateEthereumAddress),
GenerateInviteCode(GenerateInviteCode),
ListInviteCodes(ListInviteCodes),
RefetchActor(RefetchActor),
DeleteProfile(DeleteProfile),
DeletePost(DeletePost),
DeleteExtraneousPosts(DeleteExtraneousPosts),
DeleteUnusedAttachments(DeleteUnusedAttachments),
UpdateCurrentBlock(UpdateCurrentBlock),
DeleteOrphanedFiles(DeleteOrphanedFiles),
}
/// Generate RSA private key
#[derive(Parser)]
struct GenerateRsaKey;
impl GenerateRsaKey {
fn execute(&self) -> () {
let private_key = generate_private_key().unwrap();
let private_key_str = serialize_private_key(&private_key).unwrap();
println!("{}", private_key_str);
}
}
/// Generate ethereum address
#[derive(Parser)]
struct GenerateEthereumAddress;
/// Generate invite code
#[derive(Parser)]
struct GenerateInviteCode;
/// List invite codes
#[derive(Parser)]
struct ListInviteCodes;
/// Re-fetch actor profile by actor ID
#[derive(Parser)]
struct RefetchActor {
id: String,
}
impl RefetchActor {
async fn execute(
&self,
config: &Config,
db_client: &impl GenericClient,
) -> Result<(), Error> {
let profile = get_profile_by_actor_id(db_client, &self.id).await?;
let actor = fetch_actor(&config.instance(), &self.id).await?;
update_remote_profile(db_client, &config.media_dir(), profile, actor).await?;
println!("profile updated");
Ok(())
}
}
/// Delete profile
#[derive(Parser)]
struct DeleteProfile {
id: Uuid,
}
/// Delete post
#[derive(Parser)]
struct DeletePost {
id: Uuid,
}
/// Delete old remote posts
#[derive(Parser)]
struct DeleteExtraneousPosts {
days: i64,
}
/// Delete attachments that don't belong to any post
#[derive(Parser)]
struct DeleteUnusedAttachments {
days: i64,
}
/// Find and delete orphaned files
#[derive(Parser)]
struct DeleteOrphanedFiles;
impl DeleteOrphanedFiles {
async fn execute(
&self,
config: &Config,
db_client: &impl GenericClient,
) -> Result<(), Error> {
let media_dir = config.media_dir();
let mut files = vec![];
for maybe_path in std::fs::read_dir(&media_dir)? {
let file_name = maybe_path?.file_name()
.to_string_lossy().to_string();
files.push(file_name);
};
println!("found {} files", files.len());
let orphaned = find_orphaned_files(db_client, files).await?;
if !orphaned.is_empty() {
remove_files(orphaned, &media_dir);
println!("orphaned files deleted");
};
Ok(())
}
}
/// Update blockchain synchronization starting block
#[derive(Parser)]
struct UpdateCurrentBlock {
number: u64,
#[clap(long)]
reset_db: bool,
}
impl UpdateCurrentBlock {
async fn execute(
&self,
config: &Config,
db_client: &impl GenericClient,
) -> Result<(), Error> {
save_current_block_number(&config.storage_dir, self.number)?;
if self.reset_db {
reset_subscriptions(db_client).await?;
};
println!("current block updated");
Ok(())
}
}
#[tokio::main]
async fn main() {
@ -180,14 +12,7 @@ async fn main() {
match opts.subcmd {
SubCommand::GenerateRsaKey(cmd) => cmd.execute(),
SubCommand::GenerateEthereumAddress(_) => {
let private_key = generate_ecdsa_key();
let address = key_to_ethereum_address(&private_key);
println!(
"address {:?}; private key {}",
address, private_key.display_secret(),
);
},
SubCommand::GenerateEthereumAddress(cmd) => cmd.execute(),
subcmd => {
// Other commands require initialized app
let config = parse_config();
@ -198,68 +23,13 @@ async fn main() {
apply_migrations(db_client).await;
match subcmd {
SubCommand::GenerateInviteCode(_) => {
let invite_code = create_invite_code(db_client).await.unwrap();
println!("generated invite code: {}", invite_code);
},
SubCommand::ListInviteCodes(_) => {
let invite_codes = get_invite_codes(db_client).await.unwrap();
if invite_codes.is_empty() {
println!("no invite codes found");
return;
};
for code in invite_codes {
println!("{}", code);
};
},
SubCommand::GenerateInviteCode(cmd) => cmd.execute(db_client).await.unwrap(),
SubCommand::ListInviteCodes(cmd) => cmd.execute(db_client).await.unwrap(),
SubCommand::RefetchActor(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeleteProfile(subopts) => {
let profile = get_profile_by_id(db_client, &subopts.id).await.unwrap();
let mut maybe_delete_person = None;
if profile.is_local() {
let user = get_user_by_id(db_client, &profile.id).await.unwrap();
let activity = prepare_delete_person(db_client, config.instance(), &user)
.await.unwrap();
maybe_delete_person = Some(activity);
};
let deletion_queue = delete_profile(db_client, &profile.id).await.unwrap();
deletion_queue.process(&config).await;
// Send Delete(Person) activities
if let Some(activity) = maybe_delete_person {
activity.deliver().await.unwrap();
};
println!("profile deleted");
},
SubCommand::DeletePost(subopts) => {
let post = get_post_by_id(db_client, &subopts.id).await.unwrap();
let deletion_queue = delete_post(db_client, &post.id).await.unwrap();
deletion_queue.process(&config).await;
if post.author.is_local() {
// Send Delete(Note) activity
let author = get_user_by_id(db_client, &post.author.id).await.unwrap();
prepare_delete_note(db_client, config.instance(), &author, &post).await.unwrap()
.deliver().await.unwrap();
};
println!("post deleted");
},
SubCommand::DeleteExtraneousPosts(subopts) => {
let created_before = Utc::now() - Duration::days(subopts.days);
let posts = find_extraneous_posts(db_client, &created_before).await.unwrap();
for post_id in posts {
let deletion_queue = delete_post(db_client, &post_id).await.unwrap();
deletion_queue.process(&config).await;
println!("post {} deleted", post_id);
};
},
SubCommand::DeleteUnusedAttachments(subopts) => {
let created_before = Utc::now() - Duration::days(subopts.days);
let deletion_queue = delete_unused_attachments(
db_client,
&created_before,
).await.unwrap();
deletion_queue.process(&config).await;
println!("unused attachments deleted");
},
SubCommand::DeleteProfile(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeletePost(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeleteExtraneousPosts(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeleteUnusedAttachments(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeleteOrphanedFiles(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::UpdateCurrentBlock(cmd) => cmd.execute(&config, db_client).await.unwrap(),
_ => panic!(),

291
src/cli.rs Normal file
View file

@ -0,0 +1,291 @@
use anyhow::Error;
use chrono::{Duration, Utc};
use clap::Parser;
use tokio_postgres::GenericClient;
use uuid::Uuid;
use crate::activitypub::builders::delete_note::prepare_delete_note;
use crate::activitypub::builders::delete_person::prepare_delete_person;
use crate::activitypub::fetcher::fetchers::fetch_actor;
use crate::activitypub::handlers::update_person::update_remote_profile;
use crate::config::Config;
use crate::ethereum::signatures::generate_ecdsa_key;
use crate::ethereum::sync::save_current_block_number;
use crate::ethereum::utils::key_to_ethereum_address;
use crate::models::attachments::queries::delete_unused_attachments;
use crate::models::cleanup::find_orphaned_files;
use crate::models::posts::queries::{delete_post, find_extraneous_posts, get_post_by_id};
use crate::models::profiles::queries::{
delete_profile,
get_profile_by_actor_id,
get_profile_by_id,
reset_subscriptions,
};
use crate::models::users::queries::{
create_invite_code,
get_invite_codes,
get_user_by_id,
};
use crate::utils::crypto::{generate_private_key, serialize_private_key};
use crate::utils::files::remove_files;
/// Admin CLI tool
#[derive(Parser)]
pub struct Opts {
#[clap(subcommand)]
pub subcmd: SubCommand,
}
#[derive(Parser)]
pub enum SubCommand {
GenerateRsaKey(GenerateRsaKey),
GenerateEthereumAddress(GenerateEthereumAddress),
GenerateInviteCode(GenerateInviteCode),
ListInviteCodes(ListInviteCodes),
RefetchActor(RefetchActor),
DeleteProfile(DeleteProfile),
DeletePost(DeletePost),
DeleteExtraneousPosts(DeleteExtraneousPosts),
DeleteUnusedAttachments(DeleteUnusedAttachments),
UpdateCurrentBlock(UpdateCurrentBlock),
DeleteOrphanedFiles(DeleteOrphanedFiles),
}
/// Generate RSA private key
#[derive(Parser)]
pub struct GenerateRsaKey;
impl GenerateRsaKey {
pub fn execute(&self) -> () {
let private_key = generate_private_key().unwrap();
let private_key_str = serialize_private_key(&private_key).unwrap();
println!("{}", private_key_str);
}
}
/// Generate ethereum address
#[derive(Parser)]
pub struct GenerateEthereumAddress;
impl GenerateEthereumAddress {
pub fn execute(&self) -> () {
let private_key = generate_ecdsa_key();
let address = key_to_ethereum_address(&private_key);
println!(
"address {:?}; private key {}",
address, private_key.display_secret(),
);
}
}
/// Generate invite code
#[derive(Parser)]
pub struct GenerateInviteCode;
impl GenerateInviteCode {
pub async fn execute(
&self,
db_client: &impl GenericClient,
) -> Result<(), Error> {
let invite_code = create_invite_code(db_client).await?;
println!("generated invite code: {}", invite_code);
Ok(())
}
}
/// List invite codes
#[derive(Parser)]
pub struct ListInviteCodes;
impl ListInviteCodes {
pub async fn execute(
&self,
db_client: &impl GenericClient,
) -> Result<(), Error> {
let invite_codes = get_invite_codes(db_client).await?;
if invite_codes.is_empty() {
println!("no invite codes found");
return Ok(());
};
for code in invite_codes {
println!("{}", code);
};
Ok(())
}
}
/// Re-fetch actor profile by actor ID
#[derive(Parser)]
pub struct RefetchActor {
id: String,
}
impl RefetchActor {
pub async fn execute(
&self,
config: &Config,
db_client: &impl GenericClient,
) -> Result<(), Error> {
let profile = get_profile_by_actor_id(db_client, &self.id).await?;
let actor = fetch_actor(&config.instance(), &self.id).await?;
update_remote_profile(db_client, &config.media_dir(), profile, actor).await?;
println!("profile updated");
Ok(())
}
}
/// Delete profile
#[derive(Parser)]
pub struct DeleteProfile {
id: Uuid,
}
impl DeleteProfile {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl GenericClient,
) -> Result<(), Error> {
let profile = get_profile_by_id(db_client, &self.id).await?;
let mut maybe_delete_person = None;
if profile.is_local() {
let user = get_user_by_id(db_client, &profile.id).await?;
let activity =
prepare_delete_person(db_client, config.instance(), &user).await?;
maybe_delete_person = Some(activity);
};
let deletion_queue = delete_profile(db_client, &profile.id).await?;
deletion_queue.process(config).await;
// Send Delete(Person) activities
if let Some(activity) = maybe_delete_person {
activity.deliver().await?;
};
println!("profile deleted");
Ok(())
}
}
/// Delete post
#[derive(Parser)]
pub struct DeletePost {
id: Uuid,
}
impl DeletePost {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl GenericClient,
) -> Result<(), Error> {
let post = get_post_by_id(db_client, &self.id).await?;
let deletion_queue = delete_post(db_client, &post.id).await?;
deletion_queue.process(config).await;
if post.author.is_local() {
// Send Delete(Note) activity
let author = get_user_by_id(db_client, &post.author.id).await?;
prepare_delete_note(db_client, config.instance(), &author, &post)
.await?
.deliver().await?;
};
println!("post deleted");
Ok(())
}
}
/// Delete old remote posts
#[derive(Parser)]
pub struct DeleteExtraneousPosts {
days: i64,
}
impl DeleteExtraneousPosts {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl GenericClient,
) -> Result<(), Error> {
let created_before = Utc::now() - Duration::days(self.days);
let posts = find_extraneous_posts(db_client, &created_before).await?;
for post_id in posts {
let deletion_queue = delete_post(db_client, &post_id).await?;
deletion_queue.process(config).await;
println!("post {} deleted", post_id);
};
Ok(())
}
}
/// Delete attachments that don't belong to any post
#[derive(Parser)]
pub struct DeleteUnusedAttachments {
days: i64,
}
impl DeleteUnusedAttachments {
pub async fn execute(
&self,
config: &Config,
db_client: &impl GenericClient,
) -> Result<(), Error> {
let created_before = Utc::now() - Duration::days(self.days);
let deletion_queue = delete_unused_attachments(
db_client,
&created_before,
).await?;
deletion_queue.process(config).await;
println!("unused attachments deleted");
Ok(())
}
}
/// Find and delete orphaned files
#[derive(Parser)]
pub struct DeleteOrphanedFiles;
impl DeleteOrphanedFiles {
pub async fn execute(
&self,
config: &Config,
db_client: &impl GenericClient,
) -> Result<(), Error> {
let media_dir = config.media_dir();
let mut files = vec![];
for maybe_path in std::fs::read_dir(&media_dir)? {
let file_name = maybe_path?.file_name()
.to_string_lossy().to_string();
files.push(file_name);
};
println!("found {} files", files.len());
let orphaned = find_orphaned_files(db_client, files).await?;
if !orphaned.is_empty() {
remove_files(orphaned, &media_dir);
println!("orphaned files deleted");
};
Ok(())
}
}
/// Update blockchain synchronization starting block
#[derive(Parser)]
pub struct UpdateCurrentBlock {
number: u64,
#[clap(long)]
reset_db: bool,
}
impl UpdateCurrentBlock {
pub async fn execute(
&self,
config: &Config,
db_client: &impl GenericClient,
) -> Result<(), Error> {
save_current_block_number(&config.storage_dir, self.number)?;
if self.reset_db {
reset_subscriptions(db_client).await?;
};
println!("current block updated");
Ok(())
}
}

View file

@ -1,5 +1,6 @@
pub mod activitypub;
pub mod atom;
pub mod cli;
pub mod config;
pub mod database;
mod errors;