rok-cli 0.3.6

Developer CLI for rok-based Axum applications
use super::{write_file, Scaffold, ScaffoldArgs, ScaffoldResult};
use anyhow::Result;

pub struct SearchScaffold;

impl Scaffold for SearchScaffold {
    fn name(&self) -> &'static str {
        "search"
    }
    fn description(&self) -> &'static str {
        "Search endpoint: FTS, filters, facets, autocomplete, rok-search driver integration"
    }

    fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult> {
        let mut r = ScaffoldResult::default();
        let d = args.dry_run;
        write_file(
            &mut r,
            "src/app/controllers/search_controller.rs",
            CONTROLLER,
            d,
        )?;
        write_file(
            &mut r,
            "src/app/observers/search_index_observer.rs",
            OBSERVER,
            d,
        )?;
        r.warnings
            .push("Set SEARCH_DRIVER=meilisearch|typesense|postgres in .env".into());
        r.warnings
            .push("Register GET /search, GET /search/autocomplete routes".into());
        Ok(r)
    }
}

const CONTROLLER: &str = r#"use axum::{extract::Query, response::IntoResponse};
use rok_auth::axum::Response;
use serde::Deserialize;

#[derive(Deserialize)]
pub struct SearchQuery {
    pub q: Option<String>,
    pub page: Option<i64>,
    pub per_page: Option<i64>,
    pub filter: Option<String>,
    pub facets: Option<String>,
}

pub async fn search(Query(q): Query<SearchQuery>) -> impl IntoResponse {
    let query = q.q.unwrap_or_default();
    // TODO: call rok-search driver with query + filters
    Response::json(serde_json::json!({
        "data": [],
        "meta": { "query": query, "total": 0 },
        "facets": {}
    }))
}

pub async fn autocomplete(Query(q): Query<SearchQuery>) -> impl IntoResponse {
    let query = q.q.unwrap_or_default();
    // TODO: return top-N suggestions from search engine
    Response::json(serde_json::json!({ "suggestions": [] }))
}
"#;

const OBSERVER: &str = r#"use rok_events::Listener;

pub struct SearchIndexObserver;

#[async_trait::async_trait]
impl Listener for SearchIndexObserver {
    async fn on_created(&self, model: &serde_json::Value) -> anyhow::Result<()> {
        // TODO: index model in search engine via rok-search
        Ok(())
    }

    async fn on_updated(&self, model: &serde_json::Value) -> anyhow::Result<()> {
        // TODO: update document in search engine
        Ok(())
    }

    async fn on_deleted(&self, id: i64) -> anyhow::Result<()> {
        // TODO: remove document from search engine
        Ok(())
    }
}
"#;