Mini site de documentaçãoDeveloper Atlas

Entrada rápida para navegar arquitetura, APIs, operação e guias técnicos do projeto sem depender da estrutura do repositório.

Blog - Publicacao Interacoes e Moderacao

Esta nota explica o fluxo completo do blog:

Recorte da seçãoGuia orientado por fluxo

Leitura pensada para explicar responsabilidades, ordem de execução e trechos reais do código com foco no fluxo da implementação.

Atualizado19 de mar. de 2026
Seções20
Tags4
guiablogpublicacaomoderacao

O que você encontra aqui

Esta nota explica o fluxo completo do blog:

  • como o post é salvo;
  • como ele é publicado;
  • onde comentários e reações entram;
  • por que isso já está preparado para migrar depois para banco.

Hoje o domínio já está em transição real para PostgreSQL, mas mantém os arquivos publicados como fallback.

Arquivos principais

  • src/features/blog/server/blogStore.ts
  • src/features/blog/server/blogDatabaseStore.ts
  • src/features/blog/types.ts
  • src/features/blog/slug.ts
  • src/app/api/ecommpanel/blog/posts/route.ts
  • src/app/api/ecommpanel/blog/posts/[postId]/route.ts
  • src/app/api/blog/posts/[slug]/comments/route.ts
  • src/app/api/blog/posts/[slug]/reactions/route.ts

Trecho 1 - separacao entre admin e runtime

ts
const ADMIN_ROOT = path.join(process.cwd(), 'src/data/ecommpanel/blog');
const ADMIN_POSTS_DIR = path.join(ADMIN_ROOT, 'posts');
const ADMIN_INDEX_FILE = path.join(ADMIN_ROOT, 'posts-index.json');

function getRuntimeRoot(): string {
  const envPath = process.env.ECOM_CONTENT_PATH?.trim();
  const base = envPath ? path.resolve(envPath) : path.join(process.cwd(), 'src/data/site-runtime');
  return path.join(base, 'blog');
}

O que isso ensina

O blog não grava tudo num lugar só.

Ele separa:

  • documentos administrativos;
  • runtime publicado.

Essa é a primeira decisão que protege a migração futura.

Na fase atual, o store do blog já faz:

  • leitura preferencial em PostgreSQL;
  • fallback para arquivo quando o banco não está disponível;
  • seed inicial do banco a partir dos arquivos quando o domínio ainda está vazio.

Trecho 2 - publicacao por documento

ts
for (const post of publishedPosts) {
  const filePath = getRuntimePostPath(post.slug);
  activeFileNames.add(path.basename(filePath));
  writeJsonAtomic(filePath, toPublishedPost(post));
}

writeJsonAtomic(getRuntimeIndexPath(), runtimeIndex);

Leitura guiada

Aqui o runtime do blog não vira um único snapshot com todos os posts completos.

Ele vira:

  • um índice publicado;
  • um arquivo publicado por slug.

Na prática isso reduz acoplamento e evita que qualquer mudança editorial invalide um documento gigante.

Trecho 3 - comentarios e reacoes como camada propria

ts
function readRuntimeComments(slug: string): PersistedCommentsDocument {
  const stored = readJsonFile<PersistedCommentsDocument>(getCommentsPath(slug));
  return {
    postSlug: slug,
    comments: stored?.comments || [],
  };
}

function readRuntimeReactions(slug: string): PersistedReactionsDocument {
  const stored = readJsonFile<PersistedReactionsDocument>(getReactionsPath(slug));
  return {
    postSlug: slug,
    entries: stored?.entries || [],
  };
}

O que isso ensina

Post, comentário e reação não são o mesmo agregado.

Isso é importante por dois motivos:

  • leitura pública fica mais leve;
  • depois dá para mover comentários e reações para banco sem redesenhar o post.

Trecho 4 - comentario publico com moderacao

ts
const status: BlogCommentStatus = post.interaction.commentsRequireModeration ? 'pending' : 'approved';

const comment: BlogComment = {
  id: `comment-${randomToken(5)}`,
  postSlug: post.slug,
  authorName,
  content,
  status,
  createdAt,
  updatedAt: createdAt,
  fingerprintHash,
};

Explicacao em linguagem natural

Quando o comentário entra, ele já nasce com status.

Ou seja:

  • se o post exige moderação, o comentário não aparece direto;
  • se não exige, já entra como aprovado.

O storefront sempre filtra só o que estiver aprovado.

Trecho 5 - reacoes publicas

ts
if (input.value === 'clear') {
  if (existingIndex >= 0) {
    document.entries.splice(existingIndex, 1);
  }
} else if (existingIndex >= 0) {
  document.entries[existingIndex] = {
    fingerprintHash,
    value: input.value,
    updatedAt: nowIso(),
  };
}

O que isso ensina

A reação não cria histórico infinito.

Ela trabalha como estado mais recente por fingerprint:

  • curtiu;
  • não curtiu;
  • limpou a reação.

Trecho 6 - moderação no admin

ts
export function moderateBlogComment(
  postId: string,
  commentId: string,
  status: Extract<BlogCommentStatus, 'approved' | 'rejected'>,
  note?: string,
): BlogComment | null {
  const post = getBlogPostById(postId);
  if (!post) return null;

  const document = readRuntimeComments(post.slug);
  const index = document.comments.findIndex((comment) => comment.id === commentId);
  if (index < 0) return null;

Explicando de forma simples

"A moderação não procura comentário por texto solto. Ela resolve o post, abre o documento de comentários daquele slug e altera só o item certo."

No modo com banco ativo, essa mesma operação também sincroniza a tabela de comentários do domínio.

Endpoints principais do modulo

Administração

  • GET/POST /api/ecommpanel/blog/posts
  • GET/PUT /api/ecommpanel/blog/posts/[postId]
  • POST /api/ecommpanel/blog/posts/[postId]/publish
  • POST /api/ecommpanel/blog/posts/[postId]/draft
  • PATCH /api/ecommpanel/blog/posts/[postId]/comments/[commentId]

Publico

  • GET/POST /api/blog/posts/[slug]/comments
  • GET/POST /api/blog/posts/[slug]/reactions

Ordem de execucao do fluxo completo

  1. o operador cria ou edita um post no painel;
  2. a API administrativa salva o documento principal;
  3. ao publicar, o runtime por slug e o índice público são atualizados;
  4. o storefront passa a listar o post publicado;
  5. comentários e reações entram por endpoints públicos separados;
  6. se houver moderação, o painel aprova ou rejeita os comentários.

Como explicar isso em treinamento

"O blog foi desenhado como um mini domínio editorial dentro da plataforma. Conteúdo principal, publicação e interação andam juntos no produto, mas ficam separados na persistência."

Fechamento da trilha

Depois desta nota, o próximo passo natural é estudar: