use axum::{Json, extract::{Path, Query, State}, http::StatusCode, response::IntoResponse}; use serde::Deserialize; use std::collections::HashMap; use sqlx::types::Json as SqlxJson; use uuid::Uuid; use validator::Validate; use crate::model::Page; use crate::util::response::ApiErr; use crate::AppState; #[derive(Deserialize, Validate)] pub struct GetPageListQuery { #[validate(length(min = 1, max = 100))] pub name: Option, } pub async fn get_page_list( State(state): State, Query(query): Query, ) -> Result { query.validate()?; let pool = &state.pool; let pages: Vec = if let Some(name) = query.name { sqlx::query_as::<_, Page>( r#" SELECT * FROM page WHERE name ILIKE $1 ORDER BY created_at "#, ) .bind(format!("%{}%", name)) .fetch_all(pool) .await? } else { sqlx::query_as::<_, Page>( r#"SELECT * FROM page ORDER BY created_at"#, ) .fetch_all(pool) .await? }; Ok(Json(pages)) } pub async fn get_page( State(state): State, Path(page_id): Path, ) -> Result { let page = sqlx::query_as::<_, Page>("SELECT * FROM page WHERE id = $1") .bind(page_id) .fetch_optional(&state.pool) .await?; match page { Some(p) => Ok(Json(p)), None => Err(ApiErr::NotFound("Page not found".to_string(), None)), } } #[derive(Debug, Deserialize, Validate)] pub struct CreatePageReq { #[validate(length(min = 1, max = 100))] pub name: String, pub data: HashMap, } #[derive(Debug, Deserialize, Validate)] pub struct UpdatePageReq { #[validate(length(min = 1, max = 100))] pub name: Option, pub data: Option>, } pub async fn create_page( State(state): State, Json(payload): Json, ) -> Result { payload.validate()?; let page_id = sqlx::query_scalar::<_, Uuid>( r#" INSERT INTO page (name, data) VALUES ($1, $2) RETURNING id "#, ) .bind(&payload.name) .bind(SqlxJson(payload.data)) .fetch_one(&state.pool) .await?; Ok((StatusCode::CREATED, Json(serde_json::json!({ "id": page_id, "ok_msg": "Page created successfully" })))) } pub async fn update_page( State(state): State, Path(page_id): Path, Json(payload): Json, ) -> Result { payload.validate()?; let exists = sqlx::query("SELECT 1 FROM page WHERE id = $1") .bind(page_id) .fetch_optional(&state.pool) .await?; if exists.is_none() { return Err(ApiErr::NotFound("Page not found".to_string(), None)); } if payload.name.is_none() && payload.data.is_none() { return Ok(Json(serde_json::json!({"ok_msg": "No fields to update"}))); } let mut updates = Vec::new(); let mut param_count = 1; if payload.name.is_some() { updates.push(format!("name = ${}", param_count)); param_count += 1; } if payload.data.is_some() { updates.push(format!("data = ${}", param_count)); param_count += 1; } updates.push("updated_at = NOW()".to_string()); let sql = format!( r#"UPDATE page SET {} WHERE id = ${}"#, updates.join(", "), param_count ); let mut query = sqlx::query(&sql); if let Some(name) = payload.name { query = query.bind(name); } if let Some(data) = payload.data { query = query.bind(SqlxJson(data)); } query = query.bind(page_id); query.execute(&state.pool).await?; Ok(Json(serde_json::json!({ "ok_msg": "Page updated successfully" }))) } pub async fn delete_page( State(state): State, Path(page_id): Path, ) -> Result { let result = sqlx::query("DELETE FROM page WHERE id = $1") .bind(page_id) .execute(&state.pool) .await?; if result.rows_affected() == 0 { return Err(ApiErr::NotFound("Page not found".to_string(), None)); } Ok(StatusCode::NO_CONTENT) }