La base del sistema, ya resuelta
Alterna CMS es un producto para quien construye software. Cargo lo compila junto con tu proyecto y el proceso HTTP arranca con usuarios, empresas y un lugar donde enchufar módulos.
-
Un binario
La plataforma y tu código salen del mismo
cargo build. -
Varios workspaces
El workspace activo sale de la sesión del usuario, no de un id que mande el cliente.
-
Módulos propios
Cada módulo declara rutas, tablas, permisos y eventos. El CMS los registra al arrancar.
Identidad, configuración y datos
Tres áreas listas, y el contrato técnico para extenderlas.
-
Identidad
Usuarios, personas, workspaces, empresas, ubicaciones, sesión y roles.
-
Configuración
Autenticación, marca, correo y su cola, auditoría, mantenimiento y biblioteca de archivos.
-
Datos
PostgreSQL como base principal y un catálogo de bases secundarias. Limpieza de datos operativos por módulo.
-
Stack
Rust, Tokio y Axum. Persistencia con SeaORM. Sesión de workspace en Redis. JWT, Argon2 y TOTP.
-
API
El CMS monta
/api/v1/auth. Cada módulo anida su propio prefijo. -
Eventos
Un módulo publica un hecho y otro lo escucha, sin importar el código del vecino.
-
Contrato
Cada módulo declara rutas, permisos y tablas. El CMS las registra al arrancar.
-
Permisos
El módulo publica sus permisos al boot. Los roles de sistema son owner, admin, member y viewer.
-
Correo y archivos
Plantillas de mail en código, worker de la cola, y archivos servidos por el propio CMS.
Un workspace, dos cajones
Un proyecto nuevo es un workspace de Cargo. Abajo de crates/ hay dos lugares, y cada uno tiene un dueño distinto.
mi-sistema/
- Cargo.toml workspace: lista los miembros
- .cargo/config.toml índice del registry de Alterna CMS
-
crates/
-
modules/
componentes del sistema. Los plugins los usan; un módulo no depende de un plugin.
-
gateway/
el binario. Registra los plugins y sirve HTTP. Acá vive la dependencia
alterna-cms.
-
gateway/
el binario. Registra los plugins y sirve HTTP. Acá vive la dependencia
-
plugins/
lo que definís vos. Una carpeta, un crate, una capacidad. No se importan entre sí.
-
ventas/
ejemplo: implementa
Plugin - stock/ otro plugin. Se entera de ventas por un evento, no importando su crate.
-
ventas/
ejemplo: implementa
-
modules/
componentes del sistema. Los plugins los usan; un módulo no depende de un plugin.
-
1
modules
La capa compartida: el contrato
Plugin, la identidad, el correo, la auditoría y el binario que arranca. Todo plugin puede usarla. Ningún plugin es dueño de esa capa. -
2
plugins
Cada carpeta es un componente independiente. Declara sus rutas y sus permisos, y depende de
modules. No depende de la carpeta de al lado. -
3
El arranque
gatewayes el único que conoce la lista completa. Al boot suma los plugins decrates/pluginsjunto con la identidad del CMS. Si dos plugins tienen que hablar, publican un evento.
Se instala como un crate
Cargo baja el código, lo compila con tu proyecto y el resultado es un solo binario. El índice vive en el registry de Alterna.
1. Índice de Cargo
En la raíz del proyecto que va a compilar, commiteá .cargo/config.toml. Ese archivo no lleva secretos.
[registries.alterna-cms]
index = "sparse+https://crates.alternapy.com/cms/"
2. Dependencia
La raíz lista los miembros. La dependencia de Alterna CMS queda en el workspace y la usa crates/modules/gateway.
[workspace]
resolver = "2"
members = [
"crates/modules/gateway",
"crates/plugins/ventas",
"crates/plugins/stock",
]
[workspace.dependencies]
alterna-cms = { version = "0.2", registry = "alterna-cms" }
3. Token y compilación
El token queda en la máquina, en ~/.cargo/credentials.toml. En CI se usa la variable CARGO_REGISTRIES_ALTERNA_CMS_TOKEN.
cargo login --registry alterna-cms
cargo build
Hace falta PostgreSQL y la variable DATABASE_URL. Al arrancar, el CMS aplica migraciones y asegura los roles de sistema. El primer superadmin se crea una vez con el seed del binario. Redis guarda la sesión del workspace activo; si Redis no responde, la sesión se lee de la base.
Registrar un módulo y hablar con la API
Lo mínimo para arrancar. El resto sale del contrato del plugin.
Plugins del CMS
alterna_cms::cms_plugins() devuelve el plugin de identidad. Tu binario lo suma a la lista y, al boot, el CMS corre migraciones, permisos, settings y rutas.
let plugins = alterna_cms::cms_plugins();
Tu módulo
Implementá el trait Plugin. El nombre es estable: con eso el CMS sabe qué permisos, tablas y rutas te pertenecen.
use async_trait::async_trait;
use axum::Router;
use alterna_cms::platform_core::{AppContext, Plugin};
struct MiPlugin;
#[async_trait]
impl Plugin for MiPlugin {
fn name(&self) -> &'static str {
"mi_modulo"
}
fn register_routes(&self, router: Router<AppContext>) -> Router<AppContext> {
router.nest("/api/v1/mi-modulo", crate::api::router())
}
}
Hooks que podés implementar cuando hagan falta: migrations, permissions, tables, settings_defaults, mail_templates, spawn_workers y on_event.
Eventos entre módulos
Publicás un nombre y un payload. Quien escucha lo hace en on_event.
ctx.events.publish(PluginEvent::new("mi_modulo.listo", payload));
Respuesta HTTP
Todas las rutas usan el mismo sobre. El campo error es un código estable; el cliente traduce. message queda en inglés para cuando no hay traducción.
{ "success": true, "data": { }, "message": "Saved" }
{ "success": false, "error": "invalid_code", "message": "Invalid or expired code" }
Workspace activo
El aislamiento entre workspaces sale de la sesión del usuario. El cliente manda workspace_uuid solo para listar o cambiar de workspace (PUT /api/v1/auth/session/workspace). Las consultas de negocio leen el workspace de esa sesión. El JWT no trae el workspace adentro.
¿Lo armamos sobre Alterna CMS?
Agendá una charla. Vemos el proyecto y cómo encaja la plataforma.
Agendar demo