mirror of
https://github.com/Xevion/xevion.dev.git
synced 2026-01-31 00:26:31 -06:00
feat: add tag deletion endpoint with CLI support
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM tags WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dd0d0e3fd03f130aab947d13580796eee9a786e2ca01d339fd0e8356f8ad3824"
|
||||
}
|
||||
@@ -35,6 +35,7 @@ pub async fn run(
|
||||
icon,
|
||||
color,
|
||||
} => update(client, &slug, name, new_slug, icon, color, json).await,
|
||||
TagsCommand::Delete { reference } => delete(client, &reference, json).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,3 +172,24 @@ async fn update(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a tag
|
||||
async fn delete(
|
||||
client: ApiClient,
|
||||
reference: &str,
|
||||
json: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let response = client
|
||||
.delete_auth(&format!("/api/tags/{}", reference))
|
||||
.await?;
|
||||
let response = check_response(response).await?;
|
||||
let deleted: ApiTag = response.json().await?;
|
||||
|
||||
if json {
|
||||
println!("{}", serde_json::to_string_pretty(&deleted)?);
|
||||
} else {
|
||||
output::success(&format!("Deleted tag: {}", deleted.name));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -229,6 +229,13 @@ pub enum TagsCommand {
|
||||
#[arg(long)]
|
||||
color: Option<String>,
|
||||
},
|
||||
|
||||
/// Delete a tag
|
||||
Delete {
|
||||
/// Tag slug or UUID
|
||||
#[arg(name = "ref")]
|
||||
reference: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
|
||||
@@ -120,6 +120,13 @@ pub async fn get_tag_by_ref(pool: &PgPool, ref_str: &str) -> Result<Option<DbTag
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_tag(pool: &PgPool, id: Uuid) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM tags WHERE id = $1", id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_all_tags_with_counts(pool: &PgPool) -> Result<Vec<(DbTag, i32)>, sqlx::Error> {
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
|
||||
@@ -252,6 +252,66 @@ pub async fn update_tag_handler(
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a tag (requires authentication)
|
||||
pub async fn delete_tag_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
axum::extract::Path(ref_str): axum::extract::Path<String>,
|
||||
jar: axum_extra::extract::CookieJar,
|
||||
) -> impl IntoResponse {
|
||||
if auth::check_session(&state, &jar).is_none() {
|
||||
return auth::require_auth_response().into_response();
|
||||
}
|
||||
|
||||
// Fetch tag before deletion to return it
|
||||
let tag = match db::get_tag_by_ref(&state.pool, &ref_str).await {
|
||||
Ok(Some(tag)) => tag,
|
||||
Ok(None) => {
|
||||
return (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(serde_json::json!({
|
||||
"error": "Not found",
|
||||
"message": "Tag not found"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(error = %err, "Failed to fetch tag before deletion");
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({
|
||||
"error": "Internal server error",
|
||||
"message": "Failed to delete tag"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
// Delete tag (CASCADE handles project_tags and tag_cooccurrence)
|
||||
match db::delete_tag(&state.pool, tag.id).await {
|
||||
Ok(()) => {
|
||||
tracing::info!(tag_id = %tag.id, tag_name = %tag.name, "Tag deleted");
|
||||
|
||||
// Invalidate cached pages - tags appear on project pages
|
||||
state.isr_cache.invalidate("/").await;
|
||||
|
||||
Json(tag.to_api_tag()).into_response()
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(error = %err, "Failed to delete tag");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({
|
||||
"error": "Internal server error",
|
||||
"message": "Failed to delete tag"
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get related tags by cooccurrence
|
||||
pub async fn get_related_tags_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
|
||||
+3
-1
@@ -65,7 +65,9 @@ pub fn api_routes() -> Router<Arc<AppState>> {
|
||||
)
|
||||
.route(
|
||||
"/tags/{ref}",
|
||||
get(handlers::get_tag_handler).put(handlers::update_tag_handler),
|
||||
get(handlers::get_tag_handler)
|
||||
.put(handlers::update_tag_handler)
|
||||
.delete(handlers::delete_tag_handler),
|
||||
)
|
||||
.route(
|
||||
"/tags/{ref}/related",
|
||||
|
||||
Reference in New Issue
Block a user