1. Definir o módulo
Neste tutorial você escreve um módulo próprio, avisos: avisos curtos do site, cada um com título, texto, tipo, validade e uma chave que liga ou desliga. São três páginas.
- Definir o módulo (esta página): o manifesto, a coleção, os campos e as permissões.
- Tela, rota e migração: uma tela própria, uma rota pública, uma migração e um gancho, e a instalação no projeto.
- Conformidade e instalação: o kit de conformidade e o que a 1.1.0 não faz.
Você precisa de um projeto blog gerado com slash create meu-blog --template blog. Os módulos de um projeto ficam em src/modules/<id>/; o seu vai em src/modules/avisos/module.ts. Todo exemplo do tutorial é um arquivo completo desse caminho: cada passo substitui o anterior e passa sozinho na checagem de tipos e no kit de conformidade.
Passo 1: o manifesto mínimo
Seção intitulada “Passo 1: o manifesto mínimo”Um módulo é o resultado de defineModule. Ela confere o manifesto e devolve o mesmo objeto; se algo está errado, lança um ManifestError que nomeia o módulo e o caminho do problema.
import { collection, defineModule, field } from "../../panel/contract/index";
/** Who holds each permission of the module, by role. */const permissions = { read: ["admin", "editor"], write: ["admin", "editor"], remove: ["admin", "editor"],} as const;
/** Minimal module: one collection of notices with a title and a text. */export const avisos = defineModule({ id: "avisos", version: "1.0.0", core: "^1.0.0", title: "Avisos", description: "Avisos curtos do site.", permissions, permissionLabels: { read: "Ver avisos", write: "Criar e editar avisos", remove: "Excluir avisos", }, menu: [{ group: "organizacao", label: "Avisos", collection: "avisos", order: 30 }], collections: { avisos: collection({ label: "Avisos", labelSingular: "Aviso", fields: { titulo: field.text({ label: "Título", required: true }), texto: field.longText({ label: "Texto" }), }, list: { columns: ["titulo"], search: ["titulo"], sort: { field: "titulo", direction: "asc" } }, permissions: { read: "read", create: "write", update: "write", delete: "remove" }, }), },});
export { avisos as module };O que cada chave faz:
idé o nome do módulo e o prefixo das suas permissões, telas e rotas. Usa minúsculas, números e hífen entre partes (meu-modulo). O núcleo reserva alguns nomes porque já usa/painel/<nome>para as telas dele:c,entrar,usuarios,midia,backup,api,coree outros.usuarioscomo id dáManifesto do módulo «usuarios», em id: «usuarios» é reservado pelo núcleo.versionéMAJOR.MINOR.PATCH.1.0dáversão «1.0» inválida: use MAJOR.MINOR.PATCH.coreé a faixa de versões do núcleo do painel em que o módulo funciona (^,~ou exata). O projeto blog roda o núcleo1.0.0, então^1.0.0serve. Uma faixa que não cabe derruba o servidor na partida, comexige o núcleo <faixa>, mas este núcleo é 1.0.0.titleedescriptionsão os textos do módulo; o título é obrigatório e não pode ficar em branco.menucoloca uma entrada no menu lateral.groupé um dos grupos do núcleo (conteudo,comunidade,organizacao,site,operacao; aqui, Organização) ou um grupo novo que o módulo declara emmenuGroups.orderposiciona a entrada dentro do grupo; entradas semordervêm depois das que têm. Uma entrada abre uma tela do módulo (screen) ou a lista gerada de uma coleção (collection), nunca as duas.collectionsdeclara os dados.avisosé a chave da coleção; o núcleo gera a lista, o formulário de criação, o de edição e a exclusão.
O arquivo termina com export { avisos as module }. O índice de módulos do projeto importa o nome module de cada pasta, e é por ele que o painel encontra o seu.
Passo 2: campos
Seção intitulada “Passo 2: campos”A coleção do passo 1 guarda título e texto. Agora o aviso ganha um tipo, uma validade e a chave de ligado.
import { collection, defineModule, field } from "../../panel/contract/index";
/** Who holds each permission of the module, by role. */const permissions = { read: ["admin", "editor"], write: ["admin", "editor"], remove: ["admin", "editor"],} as const;
/** The kinds of notice, in the order the select offers them (the first one is the default). */const TIPOS = [ { value: "info", label: "Informação" }, { value: "atencao", label: "Atenção" }, { value: "urgente", label: "Urgente" },];
/** Module with the fields of a notice: its text, its kind, until when it is valid and whether it is on. */export const avisos = defineModule({ id: "avisos", version: "1.0.0", core: "^1.0.0", title: "Avisos", description: "Avisos curtos do site.", permissions, permissionLabels: { read: "Ver avisos", write: "Criar e editar avisos", remove: "Excluir avisos", }, menu: [{ group: "organizacao", label: "Avisos", collection: "avisos", order: 30 }], collections: { avisos: collection({ label: "Avisos", labelSingular: "Aviso", fields: { titulo: field.text({ label: "Título", required: true }), texto: field.longText({ label: "Texto" }), tipo: field.select({ label: "Tipo", options: TIPOS, default: "info", required: true }), validade: field.date({ label: "Vale até" }), ativo: field.boolean({ label: "Ativo", default: true }), }, list: { columns: ["titulo", "tipo", "validade", "ativo"], filters: ["tipo", "ativo"], search: ["titulo"], sort: { field: "createdAt", direction: "desc" }, }, permissions: { read: "read", create: "write", update: "write", delete: "remove" }, }), },});
export { avisos as module };Cada campo nasce de field.<tipo>(...). Os tipos são text, longText, richText, slug, email, url, number, boolean, date, datetime, select, multiSelect, relation, media e json. As opções comuns:
labelé o rótulo no formulário e na lista; sem ele, o formulário usa a chave do campo.required: truerecusa o campo em branco com Campo obrigatório.defaulté o que o controle mostra quando não há valor guardado. No formulário de criação deste módulo, Tipo vem com Informação marcado e Ativo vem ligado.selectpedeoptions: texto puro (valor e rótulo iguais) ou{ value, label }. O valor é o que fica guardado; o rótulo é o que a pessoa lê.
Em list, columns escolhe as colunas da lista, filters os campos que viram filtro, search onde a busca procura e sort a ordem inicial. Todo nome citado precisa ser um campo da coleção (ou um campo de sistema: id, version, createdAt, updatedAt); um nome que não existe é recusado quando o módulo é definido.
Passo 3: permissões
Seção intitulada “Passo 3: permissões”Falta dizer quem pode o quê.
import { collection, defineModule, field } from "../../panel/contract/index";
/** Who holds each permission of the module, by role: authors only read, and only admins delete. */const permissions = { read: ["admin", "editor", "autor"], write: ["admin", "editor"], remove: ["admin"],} as const;
/** The kinds of notice, in the order the select offers them (the first one is the default). */const TIPOS = [ { value: "info", label: "Informação" }, { value: "atencao", label: "Atenção" }, { value: "urgente", label: "Urgente" },];
/** Module whose permissions follow the roles: each action of the collection answers to one of the three keys. */export const avisos = defineModule({ id: "avisos", version: "1.0.0", core: "^1.0.0", title: "Avisos", description: "Avisos curtos do site.", permissions, permissionLabels: { read: "Ver avisos", write: "Criar e editar avisos", remove: "Excluir avisos", }, menu: [{ group: "organizacao", label: "Avisos", collection: "avisos", order: 30 }], collections: { avisos: collection({ label: "Avisos", labelSingular: "Aviso", fields: { titulo: field.text({ label: "Título", required: true }), texto: field.longText({ label: "Texto" }), tipo: field.select({ label: "Tipo", options: TIPOS, default: "info", required: true }), validade: field.date({ label: "Vale até" }), ativo: field.boolean({ label: "Ativo", default: true }), }, list: { columns: ["titulo", "tipo", "validade", "ativo"], filters: ["tipo", "ativo"], search: ["titulo"], sort: { field: "createdAt", direction: "desc" }, }, permissions: { read: "read", create: "write", update: "write", delete: "remove" }, }), },});
export { avisos as module };permissions lista as ações do módulo e os papéis que as têm por padrão. Os papéis são admin, editor e autor. O nome completo de uma permissão é <módulo>.<ação>: aqui, avisos.read, avisos.write e avisos.remove. É esse nome que aparece em grants e na matriz de permissões da tela Usuários; veja Usuários e papéis para o lado de quem opera o painel.
permissionLabels dá a frase em português de cada permissão para essa matriz; sem ela, a matriz mostra a chave.
Na coleção, permissions liga cada ação (read, create, update, delete) a uma das permissões do módulo. Sem esse bloco, só administradores alcançam a coleção. Citar uma permissão que o módulo não declara é um erro de manifesto: collections.avisos.permissions.read: permissão «ver» não declarada em permissions.
Neste passo, autor só lê, editor lê, cria e edita, e só admin exclui. Cada pessoa ainda pode receber permissões a mais ou a menos na tela Usuários.
O que você tem
Seção intitulada “O que você tem”Um módulo com uma coleção, cinco campos, três permissões e um item de menu. Ele ainda não está no projeto: a próxima página acrescenta a tela, a rota, a migração e o gancho, e instala o módulo.