rok-cli 0.1.4

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

pub struct CommentsScaffold;

impl Scaffold for CommentsScaffold {
    fn name(&self) -> &'static str {
        "comments"
    }
    fn description(&self) -> &'static str {
        "Comments: polymorphic, nested replies, moderation queue, notifications"
    }

    fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult> {
        let mut r = ScaffoldResult::default();
        let d = args.dry_run;
        write_file(
            &mut r,
            "src/app/controllers/comment_controller.rs",
            CONTROLLER,
            d,
        )?;
        write_file(&mut r, "migrations/create_comments_table.sql", MIGRATION, d)?;
        r.warnings.push("Register /comments routes".into());
        Ok(r)
    }
}

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

#[derive(Deserialize)]
pub struct CommentQuery { pub commentable_type: String, pub commentable_id: i64, pub page: Option<i64> }

pub async fn index(Query(q): Query<CommentQuery>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    // TODO: paginated comments for given commentable
    Response::json(serde_json::json!({ "data": [], "meta": { "total": 0 } }))
}

pub async fn store(ctx: Ctx, Json(body): Json<serde_json::Value>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    // TODO: validate, insert, optionally send reply notification
    Response::created(serde_json::json!({ "message": "Comment created" }))
}

pub async fn destroy(ctx: Ctx, Path(id): Path<i64>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    // TODO: soft-delete (set deleted_at)
    Response::no_content()
}

pub async fn moderate(ctx: Ctx, Path(id): Path<i64>, Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    // TODO: set status = body["status"] ("approved"|"rejected"|"spam")
    Response::json(serde_json::json!({ "message": "Status updated" }))
}
"#;

const MIGRATION: &str = r#"CREATE TABLE comments (
    id               BIGSERIAL PRIMARY KEY,
    commentable_type TEXT NOT NULL,
    commentable_id   BIGINT NOT NULL,
    parent_id        BIGINT REFERENCES comments(id) ON DELETE CASCADE,
    author_id        BIGINT NOT NULL,
    body             TEXT NOT NULL,
    status           TEXT NOT NULL DEFAULT 'pending',
    deleted_at       TIMESTAMPTZ,
    created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at       TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON comments (commentable_type, commentable_id);
CREATE INDEX ON comments (parent_id);
"#;