Blog - Publicacao Interacoes e Moderacao
Esta nota explica o fluxo completo do blog:
Leitura pensada para explicar responsabilidades, ordem de execução e trechos reais do código com foco no fluxo da implementação.
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.tssrc/features/blog/server/blogDatabaseStore.tssrc/features/blog/types.tssrc/features/blog/slug.tssrc/app/api/ecommpanel/blog/posts/route.tssrc/app/api/ecommpanel/blog/posts/[postId]/route.tssrc/app/api/blog/posts/[slug]/comments/route.tssrc/app/api/blog/posts/[slug]/reactions/route.ts
Trecho 1 - separacao entre admin e runtime
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
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
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
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
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
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/postsGET/PUT /api/ecommpanel/blog/posts/[postId]POST /api/ecommpanel/blog/posts/[postId]/publishPOST /api/ecommpanel/blog/posts/[postId]/draftPATCH /api/ecommpanel/blog/posts/[postId]/comments/[commentId]
Publico
GET/POST /api/blog/posts/[slug]/commentsGET/POST /api/blog/posts/[slug]/reactions
Ordem de execucao do fluxo completo
- o operador cria ou edita um post no painel;
- a API administrativa salva o documento principal;
- ao publicar, o runtime por slug e o índice público são atualizados;
- o storefront passa a listar o post publicado;
- comentários e reações entram por endpoints públicos separados;
- 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: