rok-cli 0.1.2

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

pub struct RatingsScaffold;

impl Scaffold for RatingsScaffold {
    fn name(&self) -> &'static str {
        "ratings"
    }
    fn description(&self) -> &'static str {
        "Ratings: polymorphic, star/boolean/like types, average aggregation, distribution"
    }

    fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult> {
        let mut r = ScaffoldResult::default();
        let d = args.dry_run;
        write_file(
            &mut r,
            "src/app/controllers/rating_controller.rs",
            CONTROLLER,
            d,
        )?;
        write_file(&mut r, "migrations/create_ratings_table.sql", MIGRATION, d)?;
        r.warnings.push("Register /ratings 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 RatingsQuery { pub rateable_type: String, pub rateable_id: i64 }

pub async fn summary(Query(q): Query<RatingsQuery>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    // TODO: avg, count, distribution
    Response::json(serde_json::json!({
        "average": null,
        "count": 0,
        "distribution": { "1": 0, "2": 0, "3": 0, "4": 0, "5": 0 }
    }))
}

pub async fn store(ctx: Ctx, Json(body): Json<serde_json::Value>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    // TODO: upsert rating (one per user per rateable)
    Response::created(serde_json::json!({ "message": "Rating saved" }))
}

pub async fn destroy(ctx: Ctx, Path(id): Path<i64>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    Response::no_content()
}
"#;

const MIGRATION: &str = r#"CREATE TABLE ratings (
    id            BIGSERIAL PRIMARY KEY,
    rateable_type TEXT NOT NULL,
    rateable_id   BIGINT NOT NULL,
    user_id       BIGINT NOT NULL,
    score         SMALLINT NOT NULL CHECK (score BETWEEN 1 AND 5),
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (rateable_type, rateable_id, user_id)
);
CREATE INDEX ON ratings (rateable_type, rateable_id);
"#;