prest 0.5.1

Progressive RESTful framework
Documentation
use host::admin::{DELETE_SVG, DONE_SVG, EDIT_SVG};

use crate::*;

pub(crate) async fn db_page() -> Markup {
    let tables = html! {
        @for table in DB.custom_tables() {
            $"font-bold text-lg" {(table.name())}
            a get=(table.full_path()) trigger="load" swap-this {}
        }
    };
    html!((tables))
}

pub(crate) async fn db_routes() -> Router {
    let mut router = route("/", get(db_page));
    for table in DB.custom_tables() {
        let get_by_id_path = format!("{}/:id", table.relative_path());
        router = router
            .route(
                table.relative_path(),
                get(|| async {
                    let rows = table
                        .get_all()
                        .await?
                        .into_iter()
                        .map(|row| view_row(table, row));
                    ok(html!(
                        table #(table.name()) .table-editor $"w-full font-mono text-[0.5rem] lg:text-sm" {
                            @let columns = table.columns().iter().map(|c| (c.name, c.rust_type));
                            @for (name, rust_type) in columns {th {(name)" ("(rust_type)")"}}
                            th #actions $"w-[70px]" {}
                            (create_form(table))
                            @for row in rows {(row)}
                        }
                    ))
                }),
            )
            .route(
                &get_by_id_path,
                get(|Path(id): Path<String>| async {
                    let row = table.get_row_by_id(id).await?;
                    ok(edit_row(table, row))
                }),
            )
            .route(table.relative_path(), put(|req: Request| async {
                let id = table.save(req).await?;
                let row = table.get_row_by_id(id).await?;
                ok(view_row(table, row))
            }))
            .route(
                table.relative_path(),
                patch(|req: Request| async {
                    let id = table.save(req).await?;
                    let row = table.get_row_by_id(id).await?;
                    ok(view_row(table, row))
                }),
            )
            .route(
                table.relative_path(),
                delete(|req: Request| async { table.remove(req).await }),
            );
    }
    router
}

fn create_form(table: TableSchema) -> Markup {
    let columns = table.columns();
    let key_selector = key_selector(table, None);

    let cells = columns.iter().map(|schema| {
        html!(
            td .create { (column_input(schema, None, &key_selector)) }
        )
    });

    html!(tr #(key_selector) {
        @for cell in cells {(cell)}
        td .actions put-after={"#"(key_selector)} include={"."(key_selector)} { div {
            button put=(table.full_path()) after-request={"reset('."(key_selector)"')"} {(DONE_SVG)}
        }}
    })
}

fn view_row(table: TableSchema, values: Vec<String>) -> Markup {
    let columns = table.columns();
    let key_selector = key_selector(table, Some(&values));

    let cells = values.iter().map(|value| html! {td ."view" {(value)}});

    let id = std::iter::zip(columns, &values)
        .find(|(col, _)| col.pkey)
        .map(|(_, v)| v)
        .expect("Some column must be primary key");
    let edit_url = format!("{}/{id}", table.full_path());

    html!(tr #(key_selector) {
        @for cell in cells {(cell)}
        td .actions { div {
            button get=(edit_url) target={"#"(key_selector)} {(EDIT_SVG)}
        }}
    })
}

fn edit_row(table: TableSchema, values: Vec<String>) -> Markup {
    let columns = table.columns();
    let key_selector = key_selector(table, Some(&values));

    let cells = std::iter::zip(columns, &values).map(|(schema, value)| {
        html!(
            td .edit {
                (column_input(schema, Some(value), &key_selector))
                @if schema.pkey {
                    (value)
                }
            }
        )
    });

    html!(tr #(key_selector) {
        @for cell in cells {(cell)}
        td .actions target={"#"(key_selector)} include={"."(key_selector)} { div {
            button patch=(table.full_path()) {(DONE_SVG)}
            button hx-confirm="are you sure you want to delete?" delete=(table.full_path()) {(DELETE_SVG)}
        }}
    })
}

fn column_input(schema: &ColumnSchema, value: Option<&str>, key_selector: &String) -> Markup {
    let input_type = if value.is_some() && schema.pkey {
        "hidden"
    } else {
        column_input_type(schema)
    };

    let checked = value.filter(|v| *v == "true").is_some();

    html! {
        input
            .(key_selector)
            type=(input_type)
            name=(schema.name)
            value=[value]
            checked[checked] {}
    }
}

fn key_selector(table: TableSchema, values: Option<&Vec<String>>) -> String {
    if let Some(values) = values {
        let pkey_index = table
            .columns()
            .iter()
            .position(|c| c.pkey)
            .expect("Some column must be the primary key");
        format!("key{}", values[pkey_index].clone())
    } else {
        format!("new_{}", table.name())
    }
}

fn column_input_type(column: &ColumnSchema) -> &str {
    let singular = !column.list && !column.optional;
    match column.sql_type {
        "BOOLEAN" if singular => "checkbox",
        _ if column.numeric && singular => "number",
        "TEXT" | _ => "text",
    }
}