feat: add the ability to resync props
This commit is contained in:
parent
8447fdef5d
commit
e57323ff3f
5 changed files with 180 additions and 36 deletions
|
|
@ -1,8 +1,9 @@
|
|||
//! Props management API handlers for admin UI.
|
||||
|
||||
use axum::extract::State;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Json;
|
||||
use axum_extra::extract::Multipart;
|
||||
use serde::Deserialize;
|
||||
use chattyness_db::{
|
||||
models::{CreateServerPropRequest, ServerProp, ServerPropSummary},
|
||||
queries::props,
|
||||
|
|
@ -18,6 +19,14 @@ use uuid::Uuid;
|
|||
// API Types
|
||||
// =============================================================================
|
||||
|
||||
/// Query parameters for prop creation.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreatePropQuery {
|
||||
/// If true, update existing prop with same slug instead of returning 409 Conflict.
|
||||
#[serde(default)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
/// Response for prop creation.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CreatePropResponse {
|
||||
|
|
@ -102,8 +111,12 @@ pub async fn list_props(State(pool): State<PgPool>) -> Result<Json<Vec<ServerPro
|
|||
/// Expects multipart form with:
|
||||
/// - `metadata`: JSON object with prop details (CreateServerPropRequest)
|
||||
/// - `file`: Binary SVG or PNG file
|
||||
///
|
||||
/// Query parameters:
|
||||
/// - `force`: If true, update existing prop with same slug instead of returning 409 Conflict.
|
||||
pub async fn create_prop(
|
||||
State(pool): State<PgPool>,
|
||||
Query(query): Query<CreatePropQuery>,
|
||||
mut multipart: Multipart,
|
||||
) -> Result<Json<CreatePropResponse>, AppError> {
|
||||
let mut metadata: Option<CreateServerPropRequest> = None;
|
||||
|
|
@ -165,23 +178,27 @@ pub async fn create_prop(
|
|||
// Validate the request
|
||||
metadata.validate()?;
|
||||
|
||||
// Check slug availability
|
||||
let slug = metadata.slug_or_generate();
|
||||
let available = props::is_prop_slug_available(&pool, &slug).await?;
|
||||
if !available {
|
||||
return Err(AppError::Conflict(format!(
|
||||
"Prop slug '{}' is already taken",
|
||||
slug
|
||||
)));
|
||||
}
|
||||
|
||||
// Store the file
|
||||
// Store the file first (SHA256-based, safe to run even if prop exists)
|
||||
let asset_path = store_prop_file(&file_bytes, &extension).await?;
|
||||
|
||||
// Create the prop in database
|
||||
let prop = props::create_server_prop(&pool, &metadata, &asset_path, None).await?;
|
||||
let prop = if query.force {
|
||||
// Force mode: upsert (insert or update)
|
||||
props::upsert_server_prop(&pool, &metadata, &asset_path, None).await?
|
||||
} else {
|
||||
// Normal mode: check availability first
|
||||
let slug = metadata.slug_or_generate();
|
||||
let available = props::is_prop_slug_available(&pool, &slug).await?;
|
||||
if !available {
|
||||
return Err(AppError::Conflict(format!(
|
||||
"Prop slug '{}' is already taken",
|
||||
slug
|
||||
)));
|
||||
}
|
||||
props::create_server_prop(&pool, &metadata, &asset_path, None).await?
|
||||
};
|
||||
|
||||
tracing::info!("Created server prop: {} ({})", prop.name, prop.id);
|
||||
let action = if query.force { "Upserted" } else { "Created" };
|
||||
tracing::info!("{} server prop: {} ({})", action, prop.name, prop.id);
|
||||
|
||||
Ok(Json(CreatePropResponse::from(prop)))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue