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
|
||||
```
|
||||
|
||||
Delete post:
|
||||
|
||||
```
|
||||
mitractl delete-post -i 55a3005f-f293-4168-ab70-6ab09a879679
|
||||
```
|
||||
|
||||
Generate invite code:
|
||||
|
||||
```
|
||||
|
|
|
@ -7,11 +7,13 @@ use mitra::database::{create_pool, get_database_client};
|
|||
use mitra::database::migrate::apply_migrations;
|
||||
use mitra::ethereum::utils::generate_ethereum_address;
|
||||
use mitra::logger::configure_logger;
|
||||
use mitra::models::posts::queries::delete_post;
|
||||
use mitra::models::profiles::queries as profiles;
|
||||
use mitra::models::users::queries::{
|
||||
generate_invite_code,
|
||||
get_invite_codes,
|
||||
};
|
||||
use mitra::utils::files::remove_files;
|
||||
|
||||
/// Admin CLI tool
|
||||
#[derive(Clap)]
|
||||
|
@ -23,6 +25,7 @@ struct Opts {
|
|||
#[derive(Clap)]
|
||||
enum SubCommand {
|
||||
DeleteProfile(DeleteProfile),
|
||||
DeletePost(DeletePost),
|
||||
GenerateInviteCode(GenerateInviteCode),
|
||||
ListInviteCodes(ListInviteCodes),
|
||||
GenerateEthereumAddress(GenerateEthereumAddress),
|
||||
|
@ -31,7 +34,13 @@ enum SubCommand {
|
|||
/// Delete profile
|
||||
#[derive(Clap)]
|
||||
struct DeleteProfile {
|
||||
/// Print debug info
|
||||
#[clap(short)]
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
/// Delete post
|
||||
#[derive(Clap)]
|
||||
struct DeletePost {
|
||||
#[clap(short)]
|
||||
id: Uuid,
|
||||
}
|
||||
|
@ -54,20 +63,25 @@ async fn main() {
|
|||
configure_logger();
|
||||
let db_pool = create_pool(&config.database_url);
|
||||
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();
|
||||
|
||||
match opts.subcmd {
|
||||
SubCommand::DeleteProfile(subopts) => {
|
||||
profiles::delete_profile(&**db_client, &subopts.id).await.unwrap();
|
||||
profiles::delete_profile(db_client, &subopts.id).await.unwrap();
|
||||
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(_) => {
|
||||
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);
|
||||
},
|
||||
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 {
|
||||
println!("no invite codes found");
|
||||
return;
|
||||
|
|
|
@ -10,5 +10,5 @@ pub mod mastodon_api;
|
|||
pub mod models;
|
||||
pub mod nodeinfo;
|
||||
pub mod scheduler;
|
||||
mod utils;
|
||||
pub mod utils;
|
||||
pub mod webfinger;
|
||||
|
|
|
@ -22,3 +22,28 @@ pub async fn create_attachment(
|
|||
let db_attachment: DbMediaAttachment = inserted_row.try_get("media_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 crate::errors::DatabaseError;
|
||||
use crate::models::attachments::queries::find_orphaned_files;
|
||||
use crate::models::attachments::types::DbMediaAttachment;
|
||||
use crate::models::profiles::queries::update_post_count;
|
||||
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")?;
|
||||
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::path::PathBuf;
|
||||
|
||||
|
@ -66,3 +66,14 @@ pub fn save_validated_b64_file(
|
|||
pub fn get_file_url(instance_url: &str, file_name: &str) -> String {
|
||||
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