nest-rs-cli 1.0.0

Scaffolding CLI for NestRS — new projects, feature generators, and project health checks.
//! **Resource** templates — a DB-backed CRUD slice (`g resource`).
//!
//! Self-contained on purpose: an `#[expose]` entity, a `CrudService`, and an
//! HTTP adapter with explicit thin handlers that delegate to the service. No
//! authz coupling, so the slice compiles in any workspace; the next-steps
//! output points at `users/`/`orgs/` for hardening with `#[crud]` + guards.

pub const MOD: &str = r#"mod entity;
mod module;
mod service;

pub mod http;

pub use entity::*;
pub use module::{{module}};
pub use service::{{service}};

pub use http::{{{controller}}, {{http_module}}};
"#;

pub const ENTITY: &str = r#"use nest_rs_resource::expose;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

#[expose(name = "{{entity}}", service = super::service::{{service}})]
#[sea_orm::model]
#[derive(Clone, Debug, DeriveEntityModel)]
#[sea_orm(
    table_name = "{{table}}",
    model_attrs(derive(PartialEq, Serialize, Deserialize))
)]
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    #[expose]
    pub id: Uuid,
    #[expose(input(create, update), validate(length(min = 1)))]
    pub name: String,
}

impl ActiveModelBehavior for ActiveModel {}
"#;

pub const SERVICE: &str = r#"use nest_rs_core::injectable;
use nest_rs_seaorm::{Creatable, CrudService, Deletable, Updatable};

use super::entity::{{{create_op}}, Entity as {{pascal}}, {{update_op}}};

#[injectable]
#[derive(Default)]
pub struct {{service}};

// Read API + the audited ORM choke point.
impl CrudService for {{service}} {
    type Entity = {{pascal}};
}

// Opt-in write capabilities — drop any your resource does not offer (and the
// matching #[crud] op), instead of declaring a placeholder input type.
impl Creatable for {{service}} {
    type Create = {{create_op}};
}

impl Updatable for {{service}} {
    type Update = {{update_op}};
}

impl Deletable for {{service}} {}
"#;

pub const MODULE: &str = r#"use nest_rs_core::module;

use super::service::{{service}};

#[module(providers = [{{service}}])]
pub struct {{module}};
"#;

pub const HTTP_MOD: &str = r#"mod controller;
mod module;

pub use controller::{{controller}};
pub use module::{{http_module}};
"#;

pub const HTTP_MODULE: &str = r#"use nest_rs_core::module;

use super::controller::{{controller}};
use crate::{{snake}}::{{module}};

#[module(
    imports = [{{module}}],
    providers = [{{controller}}],
)]
pub struct {{http_module}};
"#;

pub const HTTP_CONTROLLER: &str = r#"use std::sync::Arc;

use nest_rs_http::{Valid, controller, routes};
use nest_rs_seaorm::{Creatable, CrudService, ServiceError};
use poem::Result;
use poem::web::Json;

use crate::{{snake}}::{{{create_op}}, {{entity}}, {{service}}};

// SECURITY: scaffolded without guards so the slice compiles in any workspace.
// Before exposing real data, bind #[use_guards(AuthnGuard, AuthzGuard)] on this
// struct, import AuthzHttpModule in http/module.rs, and declare
// #[authorize(Action, Entity)] per handler (the posture — it arms the class gate
// and response masking) — see crates/features/src/users/http/.
#[controller(path = "/{{kebab}}")]
pub struct {{controller}} {
    #[inject]
    svc: Arc<{{service}}>,
}

#[routes]
impl {{controller}} {
    #[get("/")]
    async fn list(&self) -> Result<Json<Vec<{{entity}}>>> {
        let rows = self.svc.list().await.map_err(ServiceError::from)?;
        Ok(Json(rows.iter().map({{entity}}::from).collect()))
    }

    #[post("/")]
    async fn create(&self, body: Valid<Json<{{create_op}}>>) -> Result<Json<{{entity}}>> {
        let model = self
            .svc
            .create(body.into_inner())
            .await
            .map_err(ServiceError::from)?;
        Ok(Json({{entity}}::from(&model)))
    }
}
"#;

/// The `--guarded` controller: the hardened `#[crud]` + guards form, mirroring
/// `demo/crates/features/src/orgs/http/controller.rs`. Assumes the workspace
/// provides `AuthnGuard` / `AuthzGuard` (as `crate::authn` / `crate::authz`).
pub const HTTP_CONTROLLER_GUARDED: &str = r#"use std::sync::Arc;

use nest_rs_http::{controller, crud};

use crate::authn::AuthnGuard;
use crate::authz::AuthzGuard;
use crate::{{snake}}::{{{create_op}}, Entity as {{entity}}Entity, {{entity}}, {{service}}, {{update_op}}};

#[controller(path = "/{{kebab}}")]
#[use_guards(AuthnGuard, AuthzGuard)]
pub struct {{controller}} {
    #[inject]
    svc: Arc<{{service}}>,
}

// Every operation is authenticated, ability-filtered, transactional, and
// field-masked — declared by the two guards, generated by #[crud]. Add the
// ability rules for {{entity}} in your AppAbility.
#[crud(
    service = svc,
    entity = {{entity}}Entity,
    output = {{entity}},
    create = {{create_op}},
    update = {{update_op}},
)]
impl {{controller}} {}
"#;

/// The `--guarded` HTTP module: imports `AuthzHttpModule` alongside the port,
/// so the ability guard is reachable (the access graph requires it).
pub const HTTP_MODULE_GUARDED: &str = r#"use nest_rs_core::module;

use super::controller::{{controller}};
use crate::authz::AuthzHttpModule;
use crate::{{snake}}::{{module}};

#[module(
    imports = [{{module}}, AuthzHttpModule],
    providers = [{{controller}}],
)]
pub struct {{http_module}};
"#;