KuroCMS DevLog Vol.5: Implementação do tipo Receita
Implementamos um novo recurso de cartão de receita no KuroCMS com base no feedback. Conheça a arquitetura técnica, geração JSON-LD e integração do editor.
No "KuroCMS" e "KuroEditor", desenvolvidos como um CMS headless de próxima geração funcionando no edge, um novo "cartão de receita" foi implementado oficialmente na versão mais recente. Enquanto as versões anteriores do KuroCMS focavam na expressão geral de documentos usando markdown e HTML padrão, a introdução deste cartão de receita permite o tratamento rigoroso de metadados como tempo de cozimento, listas de ingredientes, porções e etapas, garantindo uma saída consistente como dados estruturados que motores de busca como o Google podem entender facilmente (Schema.org Recipe). Neste artigo, detalhamos o design técnico deste cartão de receita e mostramos como a validação é aplicada nas camadas do editor, da API e do frontend.
Filosofia de Design e Design Base: Modelo de dados inteligente limitado a 1 documento = 1 receita
Ao implementar o cartão de receita, o maior desafio enfrentado pela equipe de desenvolvimento foi a "consistência de dados e eliminação de redundância". Enquanto sites de receitas gerais frequentemente têm múltiplos cartões de receitas espalhados num único artigo, isso tende a gerar uma estrutura quebrada sob a perspectiva de dados estruturados de motores de busca (JSON-LD). Se múltiplos pratos ou etapas diferentes são exibidos em paralelo para uma única URL (artigo), a correspondência entre propriedades comuns (como nome do prato, imagem finalizada, descrição e data de publicação) e cartões de receitas individuais torna-se ambígua.
Portanto, o KuroCMS impõe uma restrição de "1 documento = 1 receita". O RecipeCard é responsável apenas pelas "partes variáveis específicas da receita" (porções, tempo de cozimento, ingredientes e etapas), enquanto informações comuns como nome do prato, descrição, imagem finalizada e data de publicação são reutilizadas a partir das propriedades do documento (título, resumo, imagem de capa, data de criação, etc.). Isto evita entradas redundantes e previne problemas onde nomes de receitas idênticos se repetem num formato quebrado dentro dos mesmos dados estruturados.
Sinceramente, sob a perspectiva de design do KuroEditor, não queríamos aceitar a restrição de 1 documento = 1 receita, pois limita a flexibilidade de layout. No entanto, porque as páginas de receitas dos utilizadores obtêm avaliações SEO mais altas e são mais fáceis de ler e referenciar, aplicamos estritamente a regra de um cartão de receita por artigo na versão atual.
Garantia de consistência por funções puras partilhadas
Num CMS headless, executar uma validação de estrutura idêntica no editor rico (KuroEditor), na API de salvamento e no processo de compilação estática de páginas públicas não é fácil. Se escrever a lógica de validação separadamente usando linguagens ou frameworks diferentes em cada camada, as diferenças de implementação causarão inevitavelmente problemas, como "salvo no editor mas falhou no servidor API", ou "salvo no banco de dados mas falhou na compilação estática do frontend, quebrando o site web".
Para resolver isso fundamentalmente, o KuroCMS adotou um padrão de design de "função pura partilhada" (Shared Pure Function). Especificamente, integrámos "dist/kuro-recipe.js" (construído no repositório a montante do KuroEditor) no núcleo do KuroCMS como "src/kuro-recipe.js", juntamente com o arquivo de definição de tipo "kuro-recipe.d.ts". Isso garante que o mesmo código JavaScript inspecione os dados da receita no corpo HTML durante a edição, chamadas API de salvamento e compilações estáticas. Este design de fonte única de verdade (Single Source of Truth) minimiza os erros.
*Dado que tanto o KuroEditor como o KuroCMS são "software de código aberto" desenvolvidos pela Kuroboo, consultar o histórico do GitHub facilitará a compreensão.
Validação estrita na API de salvamento: Especificações de recipe-guard.ts
Quando um artigo é salvo (PUT) no KuroCMS, a API do lado do servidor não apenas armazena a string. Ela inspeciona estritamente se os dados da receita estão corretamente integrados no corpo HTML enviado. Este papel é tratado pela função "checkRecipeCards(bodyHtml, isRecipeType)" em "src/recipe-guard.ts" no backend.
O aspeto mais crítico desta validação é o momento. O KuroCMS considera a edição colaborativa multiutilizador, e os dados finais salvos são gravados no banco de dados após uma "fusão de 3 vias" (3-way merge) de várias versões. Se a validação for executada antes da fusão, não podemos evitar casos em que a fusão duplica acidentalmente os cartões de receitas ou insere dados quebrados. Portanto, a verificação é executada sempre no texto final após a fusão. Se a validação falhar, a API devolve um erro claro ao cliente, bloqueando o processo de salvamento.
| Item de inspeção | Detalhes e medidas de segurança |
|---|---|
| Número de cartões | Confirme que existe exatamente 1 cartão num artigo de receita, e exatamente 0 cartões nos artigos que não são receitas. |
| Decodificação de data-recipe | Decodifique os dados JSON de receita codificados e integrados na tag HTML e verifique se cumprem com o esquema. |
| Allowlist de atributos | Verifique estritamente os atributos HTML desconhecidos. Isto elimina as vulnerabilidades onde utilizadores maliciosos injetam scripts XSS como "onclick" através do texto do corpo HTML. |
| Verificação de versão | Verifique a compatibilidade de versão do esquema de receita para evitar guardar versões antigas de cartões ou formatos danificados. |
Saída JSON-LD: Geração automática da estrutura Recipe para o SEO
Este é o valor fundamental de adicionar o cartão de receita. Normalmente, o KuroCMS gera "Article" como dados estruturados para a página, mas quando um documento contém um cartão de receita, ele o substitui automaticamente e gera "Recipe" como dados estruturados principais.
Lê o atributo "data-recipe" do HTML salvo e transmite os dados analisados sob as variáveis "article.recipe" and "page.isRecipe" para o modelo HTML. Para evitar "a verificação de tipo por comparação de strings" no modelo, utiliza uma simples ramificação booleana (#if page.isRecipe) para mudar de modelo. Os dados temporais são convertidos automaticamente internamente para o formato internacional ISO-8601 duration (por exemplo, "PT25M" para 25 minutos). Dado que a exibição HTML e os dados estruturados JSON-LD são gerados a partir da mesma fonte de dados única, não existe risco de informações erradas para os motores de busca.
Integração de administração: Lógica de recreação dinâmica do editor
A lógica de integração também foi incorporada na área de edição do painel de administração. No KuroEditor, a ativação ou desativação do "botão Panela" para a criação de receitas é controlada pela opção "recipeUi" passada na inicialização. Dado que o comportamento padrão do KuroEditor é mostrar o botão (true), o KuroCMS não necessita de o especificar normalmente, mas se for definido como false, parece inalterado para os utilizadores que não utilizam o KuroEditor para edição de receitas, minimizando o impacto da alteração.
Além disso, ao nível do KuroEditor, os cartões de receitas estão agora bloqueados a 1 receita por artigo (o segundo botão está desativado e não pode ser clicado). Isto preserva a regra principal dos artigos de receitas. O KuroCMS também realiza uma dupla verificação ao guardar para assegurar que as verificações específicas de receitas se executem corretamente para os artigos de receitas.
Com base nisso, foi adicionada uma suite de testes relacionada com receitas "npm run test:recipe" (src/recipe-guard.test.ts, 12 casos de teste no total), que valida tudo, desde o número de cartões até ao escape de caracteres em atributos.
Problemas restantes e roteiro futuro
Com esta atualização, o "sistema central" do artigo de receita e a "validação de salvamento" estão completamente operacionais. No entanto, o KuroCMS não oferece ainda um layout de página de receita completo como os grandes portais. O nosso objetivo era permitir a publicação fácil de receitas em sites web pessoais, gerando automaticamente dados SEO sem que o utilizador tenha de escrevê-los. Agora qualquer pessoa pode lançar um site de culinária.
Além disso, dado que o KuroCMS tem um sistema de modelos, os utilizadores podem continuar a aperfeiçoar o design por si mesmos (ver a Fonte 3 para mais detalhes). Para tornar o KuroCMS ainda mais fácil de usar, a equipa de desenvolvimento continuará a implementar atualizações com base nos seus comentários, obrigado pelo seu apoio!