Add delete-post CLI command
This commit is contained in:
parent
c605185bb5
commit
283c426c49
6 changed files with 101 additions and 7 deletions
|
@ -96,6 +96,12 @@ Delete profile:
|
||||||
mitractl delete-profile -i 55a3005f-f293-4168-ab70-6ab09a879679
|
mitractl delete-profile -i 55a3005f-f293-4168-ab70-6ab09a879679
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Delete post:
|
||||||
|
|
||||||
|
```
|
||||||
|
mitractl delete-post -i 55a3005f-f293-4168-ab70-6ab09a879679
|
||||||
|
```
|
||||||
|
|
||||||
Generate invite code:
|
Generate invite code:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -7,11 +7,13 @@ use mitra::database::{create_pool, get_database_client};
|
||||||
use mitra::database::migrate::apply_migrations;
|
use mitra::database::migrate::apply_migrations;
|
||||||
use mitra::ethereum::utils::generate_ethereum_address;
|
use mitra::ethereum::utils::generate_ethereum_address;
|
||||||
use mitra::logger::configure_logger;
|
use mitra::logger::configure_logger;
|
||||||
|
use mitra::models::posts::queries::delete_post;
|
||||||
use mitra::models::profiles::queries as profiles;
|
use mitra::models::profiles::queries as profiles;
|
||||||
use mitra::models::users::queries::{
|
use mitra::models::users::queries::{
|
||||||
generate_invite_code,
|
generate_invite_code,
|
||||||
get_invite_codes,
|
get_invite_codes,
|
||||||
};
|
};
|
||||||
|
use mitra::utils::files::remove_files;
|
||||||
|
|
||||||
/// Admin CLI tool
|
/// Admin CLI tool
|
||||||
#[derive(Clap)]
|
#[derive(Clap)]
|
||||||
|
@ -23,6 +25,7 @@ struct Opts {
|
||||||
#[derive(Clap)]
|
#[derive(Clap)]
|
||||||
enum SubCommand {
|
enum SubCommand {
|
||||||
DeleteProfile(DeleteProfile),
|
DeleteProfile(DeleteProfile),
|
||||||
|
DeletePost(DeletePost),
|
||||||
GenerateInviteCode(GenerateInviteCode),
|
GenerateInviteCode(GenerateInviteCode),
|
||||||
ListInviteCodes(ListInviteCodes),
|
ListInviteCodes(ListInviteCodes),
|
||||||
GenerateEthereumAddress(GenerateEthereumAddress),
|
GenerateEthereumAddress(GenerateEthereumAddress),
|
||||||
|
@ -31,7 +34,13 @@ enum SubCommand {
|
||||||
/// Delete profile
|
/// Delete profile
|
||||||
#[derive(Clap)]
|
#[derive(Clap)]
|
||||||
struct DeleteProfile {
|
struct DeleteProfile {
|
||||||
/// Print debug info
|
#[clap(short)]
|
||||||
|
id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete post
|
||||||
|
#[derive(Clap)]
|
||||||
|
struct DeletePost {
|
||||||
#[clap(short)]
|
#[clap(short)]
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
}
|
}
|
||||||
|
@ -54,20 +63,25 @@ async fn main() {
|
||||||
configure_logger();
|
configure_logger();
|
||||||
let db_pool = create_pool(&config.database_url);
|
let db_pool = create_pool(&config.database_url);
|
||||||
apply_migrations(&db_pool).await;
|
apply_migrations(&db_pool).await;
|
||||||
let db_client = get_database_client(&db_pool).await.unwrap();
|
let db_client = &mut **get_database_client(&db_pool).await.unwrap();
|
||||||
let opts: Opts = Opts::parse();
|
let opts: Opts = Opts::parse();
|
||||||
|
|
||||||
match opts.subcmd {
|
match opts.subcmd {
|
||||||
SubCommand::DeleteProfile(subopts) => {
|
SubCommand::DeleteProfile(subopts) => {
|
||||||
profiles::delete_profile(&**db_client, &subopts.id).await.unwrap();
|
profiles::delete_profile(db_client, &subopts.id).await.unwrap();
|
||||||
println!("profile deleted");
|
println!("profile deleted");
|
||||||
},
|
},
|
||||||
|
SubCommand::DeletePost(subopts) => {
|
||||||
|
let orphaned_files = delete_post(db_client, &subopts.id).await.unwrap();
|
||||||
|
remove_files(orphaned_files, &config.media_dir());
|
||||||
|
println!("post deleted");
|
||||||
|
},
|
||||||
SubCommand::GenerateInviteCode(_) => {
|
SubCommand::GenerateInviteCode(_) => {
|
||||||
let invite_code = generate_invite_code(&**db_client).await.unwrap();
|
let invite_code = generate_invite_code(db_client).await.unwrap();
|
||||||
println!("generated invite code: {}", invite_code);
|
println!("generated invite code: {}", invite_code);
|
||||||
},
|
},
|
||||||
SubCommand::ListInviteCodes(_) => {
|
SubCommand::ListInviteCodes(_) => {
|
||||||
let invite_codes = get_invite_codes(&**db_client).await.unwrap();
|
let invite_codes = get_invite_codes(db_client).await.unwrap();
|
||||||
if invite_codes.len() == 0 {
|
if invite_codes.len() == 0 {
|
||||||
println!("no invite codes found");
|
println!("no invite codes found");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -10,5 +10,5 @@ pub mod mastodon_api;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod nodeinfo;
|
pub mod nodeinfo;
|
||||||
pub mod scheduler;
|
pub mod scheduler;
|
||||||
mod utils;
|
pub mod utils;
|
||||||
pub mod webfinger;
|
pub mod webfinger;
|
||||||
|
|
|
@ -22,3 +22,28 @@ pub async fn create_attachment(
|
||||||
let db_attachment: DbMediaAttachment = inserted_row.try_get("media_attachment")?;
|
let db_attachment: DbMediaAttachment = inserted_row.try_get("media_attachment")?;
|
||||||
Ok(db_attachment)
|
Ok(db_attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_orphaned_files(
|
||||||
|
db_client: &impl GenericClient,
|
||||||
|
files: Vec<String>,
|
||||||
|
) -> Result<Vec<String>, DatabaseError> {
|
||||||
|
let rows = db_client.query(
|
||||||
|
"
|
||||||
|
SELECT fname
|
||||||
|
FROM unnest($1::text[]) AS fname
|
||||||
|
WHERE
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1 FROM media_attachment WHERE file_name = fname
|
||||||
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM actor_profile
|
||||||
|
WHERE avatar_file_name = fname OR banner_file_name = fname
|
||||||
|
)
|
||||||
|
",
|
||||||
|
&[&files],
|
||||||
|
).await?;
|
||||||
|
let orphaned_files = rows.iter()
|
||||||
|
.map(|row| row.try_get("fname"))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
Ok(orphaned_files)
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use tokio_postgres::GenericClient;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::errors::DatabaseError;
|
use crate::errors::DatabaseError;
|
||||||
|
use crate::models::attachments::queries::find_orphaned_files;
|
||||||
use crate::models::attachments::types::DbMediaAttachment;
|
use crate::models::attachments::types::DbMediaAttachment;
|
||||||
use crate::models::profiles::queries::update_post_count;
|
use crate::models::profiles::queries::update_post_count;
|
||||||
use super::types::{DbPost, Post, PostCreateData};
|
use super::types::{DbPost, Post, PostCreateData};
|
||||||
|
@ -288,3 +289,40 @@ pub async fn is_waiting_for_token(
|
||||||
let is_waiting: bool = row.try_get("is_waiting")?;
|
let is_waiting: bool = row.try_get("is_waiting")?;
|
||||||
Ok(is_waiting)
|
Ok(is_waiting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes post from database and returns list of orphaned files.
|
||||||
|
pub async fn delete_post(
|
||||||
|
db_client: &mut impl GenericClient,
|
||||||
|
post_id: &Uuid,
|
||||||
|
) -> Result<Vec<String>, DatabaseError> {
|
||||||
|
let transaction = db_client.transaction().await?;
|
||||||
|
// Get list of attached files
|
||||||
|
let attachment_rows = transaction.query(
|
||||||
|
"
|
||||||
|
SELECT file_name
|
||||||
|
FROM media_attachment WHERE post_id = $1
|
||||||
|
",
|
||||||
|
&[&post_id],
|
||||||
|
).await?;
|
||||||
|
let files: Vec<String> = attachment_rows.iter()
|
||||||
|
.map(|row| row.try_get("file_name"))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
// Delete post
|
||||||
|
let maybe_post_row = transaction.query_opt(
|
||||||
|
"
|
||||||
|
DELETE FROM post WHERE id = $1
|
||||||
|
RETURNING post
|
||||||
|
",
|
||||||
|
&[&post_id],
|
||||||
|
).await?;
|
||||||
|
let post_row = maybe_post_row.ok_or(DatabaseError::NotFound("post"))?;
|
||||||
|
let db_post: DbPost = post_row.try_get("post")?;
|
||||||
|
// Update counters
|
||||||
|
if let Some(parent_id) = &db_post.in_reply_to_id {
|
||||||
|
update_reply_count(&transaction, parent_id, -1).await?;
|
||||||
|
}
|
||||||
|
update_post_count(&transaction, &db_post.author_id, -1).await?;
|
||||||
|
let orphaned_files = find_orphaned_files(&transaction, files).await?;
|
||||||
|
transaction.commit().await?;
|
||||||
|
Ok(orphaned_files)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::fs::File;
|
use std::fs::{remove_file, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -66,3 +66,14 @@ pub fn save_validated_b64_file(
|
||||||
pub fn get_file_url(instance_url: &str, file_name: &str) -> String {
|
pub fn get_file_url(instance_url: &str, file_name: &str) -> String {
|
||||||
format!("{}/media/{}", instance_url, file_name)
|
format!("{}/media/{}", instance_url, file_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_files(files: Vec<String>, from_dir: &PathBuf) -> () {
|
||||||
|
for file_name in files {
|
||||||
|
let file_path = from_dir.join(&file_name);
|
||||||
|
let file_path_str = file_path.to_string_lossy();
|
||||||
|
match remove_file(&file_path) {
|
||||||
|
Ok(_) => log::info!("removed file {}", file_path_str),
|
||||||
|
Err(_) => log::warn!("failed to remove file {}", file_path_str),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue