Primeira jornada

Uma pessoa abre uma página, executa uma ação e vê o resultado ou um erro útil — com endpoint privado, permissão e tratamento de erro.

Comece sempre por uma jornada pequena e completa: página + navegação + endpoint + permissão. Este exemplo é uma ação sem persistência; para cadastro, veja Models e CRUD.

1. Declare a permissão

Em central.app.json, adicione demo.execute a rbac.permissions. Mantenha o admin existente (que já tem "*").

{
  "rbac": {
    "permissions": ["demo.execute"],
    "roles": [
      { "code": "admin", "name": "Administrador", "admin": true, "permissions": ["*"] },
      { "code": "operator", "name": "Operador", "permissions": ["demo.execute"] },
      { "code": "viewer", "name": "Consulta", "permissions": [] }
    ]
  }
}

2. Crie a rota privada

const { defineRoutes, GenericError } = require("@oondemand/oon-core-back");

defineRoutes("/demo", (router) => {
  router.private.post("/saudacao", { permission: "demo.execute" }, async (req, res) => {
    const nome = typeof req.body?.nome === "string" ? req.body.nome.trim() : "";
    if (!nome || nome.length > 80) {
      throw new GenericError("Informe um nome de até 80 caracteres.", {
        statusCode: 422, code: "DEMO_NAME_INVALID",
      });
    }
    res.json({ message: `Olá, ${nome}!` });
  });
});

A assinatura é router.private[método](caminho, opções, handler). O arquivo em src/routes é autocarregado — não é preciso exportar nada.

3. Crie a página

A rota / e o item Início já existem no scaffold. Substitua a página:

import { useState, type FormEvent } from "react";
import { useOonApi } from "@oondemand/oon-core-front";

export function HomePage() {
  const { http } = useOonApi();
  const [nome, setNome] = useState("");
  const [resultado, setResultado] = useState("");
  const [busy, setBusy] = useState(false);

  async function submit(event: FormEvent) {
    event.preventDefault();
    setBusy(true);
    try {
      const response = await http.post("/demo/saudacao", { nome });
      setResultado(response.data.message);
    } catch (error: unknown) {
      const response = (error as { response?: { status?: number; data?: {
        message?: string; error?: { code?: string; message?: string };
      } } }).response;
      setResultado(response?.data?.error?.message || response?.data?.message
        || `Não foi possível executar a ação (HTTP ${response?.status ?? "indisponível"}).`);
    } finally {
      setBusy(false);
    }
  }

  return (
    <form onSubmit={submit}>
      <h1>Primeira jornada</h1>
      <label>Nome <input value={nome} onChange={(e) => setNome(e.target.value)} /></label>
      <button type="submit" disabled={busy}>Executar</button>
      <p role="status">{resultado}</p>
    </form>
  );
}

4. Prove a jornada

Reinicie npm run dev após incluir o módulo e verifique:

Cenário Resultado esperado
Perfil admin, nome válido 200 com Olá, Fulano!
Perfil admin, nome vazio 422 com código DEMO_NAME_INVALID
Perfil viewer (sem demo.execute) 403 — recusa no backend

Troque o perfil pelo banner do runtime local. A permissão é recalculada no backend a cada requisição.

Erros que você vai encontrar

  • OON_ROUTE_REGISTRATION_INVALID — você passou um middleware (como requirePermission(...)) na posição das opções. Use { permission } no segundo argumento.
  • 404 em HTML — o módulo não está em backend/src/routes, o processo não foi reiniciado ou você duplicou /api na rota. Não é um erro de domínio.
  • 403 CSRF local — o frontend não está passando pelo proxy /api do Vite.

Próximo passo