heartwood every commit a ring
3.3 KB raw
use axum::{
    extract::{Form, Path as AxumPath, Query, State},
    response::{IntoResponse, Json, Redirect, Response},
    routing::{get, post},
    Router,
};
use serde::Deserialize;
use serde_json::{json, Value};
use tower_cookies::Cookies;
use uuid::Uuid;

use crate::models;
use crate::render::render;
use crate::routes::auth::is_authenticated;
use crate::routes::dashboard::build_property_context;
use crate::AppState;

pub fn router() -> Router<AppState> {
    Router::new()
        .route("/properties", get(properties).post(properties_create))
        .route("/properties/{id}/delete", post(property_delete))
        .route("/properties/{id}/public", post(property_public_toggle))
}

#[derive(Debug, Deserialize)]
pub struct ListQuery {
    pub q: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreateForm {
    pub url: String,
}

pub async fn properties(
    State(state): State<AppState>,
    cookies: Cookies,
    Query(query): Query<ListQuery>,
) -> Response {
    if !is_authenticated(&cookies, &state) {
        return Redirect::to("/").into_response();
    }
    let search = query.q.as_deref().filter(|s| !s.is_empty());
    let rows = models::list_properties(&state.pool, search)
        .await
        .unwrap_or_default();
    let mut props: Vec<Value> = Vec::with_capacity(rows.len());
    for row in &rows {
        match build_property_context(&state, row).await {
            Ok(ctx) => props.push(serde_json::to_value(&ctx).unwrap_or(Value::Null)),
            Err(e) => tracing::warn!("[properties] property context: {e:#}"),
        }
    }
    let extra = minijinja::context! {
        page => minijinja::context! {
            title => "Properties",
            description => "Manage your properties.",
        },
        title => "Properties",
        description => "Manage your properties.",
        properties => props,
        q => query.q,
    };
    render(&state, "properties/properties.html", "/properties", true, extra)
}

pub async fn properties_create(
    State(state): State<AppState>,
    cookies: Cookies,
    Form(form): Form<CreateForm>,
) -> Response {
    if !is_authenticated(&cookies, &state) {
        return Redirect::to("/").into_response();
    }
    let url = form.url.trim();
    if url.is_empty() || !(url.starts_with("http://") || url.starts_with("https://")) {
        return Redirect::to("/properties").into_response();
    }
    if let Err(e) = models::create_property(&state.pool, url).await {
        tracing::warn!("[properties] create: {e:#}");
    }
    Redirect::to("/properties").into_response()
}

pub async fn property_delete(
    State(state): State<AppState>,
    cookies: Cookies,
    AxumPath(id): AxumPath<Uuid>,
) -> Response {
    if !is_authenticated(&cookies, &state) {
        return Redirect::to("/").into_response();
    }
    if let Err(e) = models::delete_property(&state.pool, id).await {
        tracing::warn!("[properties] delete: {e:#}");
    }
    Redirect::to("/properties").into_response()
}

pub async fn property_public_toggle(
    State(state): State<AppState>,
    cookies: Cookies,
    AxumPath(id): AxumPath<Uuid>,
) -> Response {
    if !is_authenticated(&cookies, &state) {
        return Redirect::to("/").into_response();
    }
    let res = models::toggle_public(&state.pool, id).await.unwrap_or(false);
    Json(json!({"success": true, "is_public": res})).into_response()
}