Estudo de caso · Código aberto

Bridopen

Aplicação de anotações escrita em Go e renderizada no servidor, construída como um monolito modular em camadas, sem framework web e sem SPA. Publicada sob licença MIT para que a arquitetura, as decisões de segurança e a suíte de testes possam ser lidas e questionadas.

Papel
Autor único
Stack
Go 1.27 · PostgreSQL
Licença
MIT
Código
Público no GitHub

Visão geral

Anotações rápidas acabam em ficheiros soltos, conversas de chat e aplicações de notas em que não se confia, sem proteção de conta e sem nada que possa ser auditado. O Bridopen é um único lugar, auto-hospedado, para escrever, etiquetar, anexar ficheiros e arquivar notas, pensado para pessoas e equipas pequenas que preferem manter a própria instância a entregar o conteúdo a terceiros. É um projeto pessoal de engenharia, publicado em código aberto para que as decisões por trás dele possam ser inspecionadas, e não apenas afirmadas.

A minha contribuição

Sou o autor único do repositório. Desenhei as camadas e escrevi os handlers HTTP, os serviços, os modelos de domínio e os repositórios, as migrations SQL, os templates HTML e o JavaScript do browser, os middlewares de segurança e a suíte de testes. Também escrevi as configurações de Docker Compose, os alvos do Makefile e a documentação.

O projeto existe para praticar decisões de engenharia de ponta a ponta — fronteiras entre camadas, tratamento de sessão e autorização, segurança de uploads, migrations e versionamento de releases — num código que controlo por completo e que, por isso, posso publicar na íntegra.

Arquitetura

Um monolito modular em camadas. O cmd/http é a composition root: carrega a configuração, abre o pool do banco de dados, liga o gestor de sessões, o mailer, o storage, o scanner, os repositórios, os serviços e os handlers, e então regista as rotas. Os módulos de negócio ficam em internal/handlers (notes, users, legal); a infraestrutura transversal fica em internal/platform.

Fluxo de um pedido no Bridopen Um pedido do browser atravessa a cadeia de middlewares — cabeçalhos de segurança, limite de pedidos por IP, proteção CSRF, gestor de sessões e middleware de autenticação — e chega a um handler. O handler chama um serviço, que recorre a um repositório para o PostgreSQL, ao armazenamento de anexos em disco (opcionalmente verificado pelo ClamAV) ou ao mailer SMTP. O handler devolve ao browser uma resposta renderizada com html/template. Browser formulário HTML / GET CADEIA DE MIDDLEWARES Cabeçalhos de segurança CSP, X-Frame-Options Limite por IP janela deslizante por IP CSRF gorilla/csrf Sessão scs + pgxstore Autenticação + log exige utilizador autenticado Tratamento de erros Handler HTTP, DTO, render Serviço casos de uso, validação Repositório SQL, scans, transações Mailer SMTP confirmação, recuperação Armazenamento de anexos volume local, fora do banco verificação ClamAV opcional PostgreSQL notas, utilizadores, sessões, auditoria Tracejado: resposta html/template devolvida ao browser
Fluxo do pedido, desenhado a partir de cmd/http/routes.go, cmd/http/security.go e internal/platform/middleware.

Regras das camadas

  • Os handlers tratam apenas de HTTP: sessão, leitura do pedido, resposta, renderização e JSON.
  • Os serviços concentram casos de uso, validação de fluxo e orquestração.
  • Os modelos guardam entidades, estados, constantes e regras puras de domínio.
  • Os repositórios isolam SQL, leitura de linhas, transações e mapeamento de persistência.
  • Os templates recebem DTOs e page models, nunca regras de negócio.

Stack

  • Backend: Go 1.27 com o router padrão do net/http; sem framework web.
  • Dados: PostgreSQL via pgx/v5; esquema gerido pelo golang-migrate.
  • Sessões: alexedwards/scs com store em PostgreSQL.
  • Views: html/template renderizado no servidor, Tailwind CSS e JavaScript puro dividido por responsabilidade.
  • Entrega: Docker Compose para desenvolvimento e produção, Caddy à frente e tags Git como fonte oficial da versão.

Decisões de engenharia

Três decisões que moldaram o código, cada uma com a alternativa considerada e o que custa.

1. HTTP da biblioteca padrão em vez de um framework web

Decisão:
o roteamento, os middlewares e o tratamento de pedidos assentam apenas no net/http, usando os padrões de método e caminho disponíveis no ServeMux padrão.
Alternativa:
Gin, Echo ou Chi, que trariam grupos de rotas, binding e uma pilha de middlewares prontos.
Motivo:
superfície de dependências pequena, sem idiomas de framework a infiltrarem-se nos handlers, e fronteiras entre camadas que têm de ser declaradas em vez de herdadas.
Limitação:
a cadeia de middlewares, o mapeamento de erros e os helpers de pedido são escritos à mão, portanto há mais código meu para manter e testar do que um framework exigiria.

2. Renderização no servidor em vez de uma SPA

Decisão:
as páginas são renderizadas com html/template e enriquecidas progressivamente com JavaScript puro; não há framework de cliente nem bundler.
Alternativa:
uma API JSON com front end em React ou Vue, implantado em separado.
Motivo:
um único artefacto para implantar, estado mantido no servidor junto da sessão, e nenhuma esteira de build para manter sincronizada com o backend.
Limitação:
interações de cliente mais ricas têm de ser escritas à mão. O Tailwind é hoje carregado de um CDN em tempo de execução, o que obriga a script-src 'unsafe-inline' na Content-Security-Policy; compilar a folha de estilos no build e apertar essa diretiva continua em aberto.

3. Sessões no banco de dados em vez de tokens sem estado

Decisão:
as sessões ficam em PostgreSQL através do scs com o store pgx, e uma tabela separada regista as sessões ativas do utilizador.
Alternativa:
um token assinado e sem estado (JWT) em cookie, validado sem tocar no banco de dados.
Motivo:
o utilizador autenticado pode listar as sessões ativas e revogá-las na página da conta, e terminar sessão invalida-a mesmo no servidor, em vez de esperar que um token expire.
Limitação:
todos os pedidos autenticados fazem uma consulta de sessão ao banco de dados, colocando-o no caminho crítico de todo o tráfego autenticado.

Qualidade e segurança

Tudo o que está abaixo existe no repositório público e pode ser conferido no código. Nada aqui é uma afirmação sobre quão segura é uma instância em produção — isso depende de como for configurada e operada.

Testes e CI

  • 26 ficheiros de teste cobrindo handlers, serviços, repositórios, modelos, DTOs e os pacotes de plataforma.
  • Escritos com testify; os testes de repositório usam pgxmock/v4, exercitando SQL e leitura de linhas sem um banco real.
  • O GitHub Actions executa go vet ./... e go test ./... em cada push para main e em cada pull request.
  • Reproduza localmente com go test -count=1 ./....

Controlos de acesso

  • Palavras-passe com hash bcrypt.
  • Verificação em duas etapas por TOTP (6 dígitos, período de 30 segundos, janela de validação de um período) com registo por QR e códigos de recuperação; o segredo partilhado fica cifrado em repouso.
  • Tokens CSRF via gorilla/csrf, cookies com flags de segurança e captcha nos formulários de autenticação.
  • Limite de pedidos por IP e cabeçalhos de segurança — Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options, Referrer-Policy, Permissions-Policy.

Dados e operação

  • Sete migrations SQL versionadas em db/migrations, aplicadas com golang-migrate.
  • Eventos de negócio registados num esquema de auditoria dedicado.
  • Anexos gravados num volume fora do banco de dados; quando há um endereço ClamAV configurado, os uploads são verificados antes de serem persistidos.
  • Exportação e exclusão de conta disponíveis ao utilizador autenticado na página de perfil.
  • Releases marcadas com vMAJOR.MINOR.PATCH; a build em execução informa versão, commit e data de compilação em GET /version.

Resultados e limitações

O que existe hoje é uma aplicação a funcionar: registo com confirmação por e-mail, entrada com verificação em duas etapas opcional, recuperação de palavra-passe, notas com etiquetas, anexos, arquivo e lixeira com exclusão reversível, tema claro e escuro, exportação e exclusão de conta e páginas legais públicas. Corre a partir de um único docker compose, com as migrations aplicadas por um alvo do Make.

Limitações declaradas

  • Nenhum benchmark de performance foi executado. Não tenho números medidos de latência, débito ou disponibilidade para este projeto, por isso nenhum é alegado.
  • Não há instância oficial hospedada. Não existe demo público para ligar. Quem implanta o Bridopen é responsável pelo serviço, pelos dados dos utilizadores e pelos textos legais das páginas públicas.
  • A verificação de anexos é opcional. Quando BRIDOPEN_CLAMAV_ADDR não está configurado, a aplicação recorre a um scanner que não faz nada e os uploads são guardados sem verificação.
  • As páginas legais são modelos genéricos. Precisam de revisão jurídica antes de qualquer implantação real.
  • A Content-Security-Policy não é tão restritiva quanto podia. Carregar o Tailwind de um CDN em tempo de execução obriga a permitir scripts inline.
  • Não há dados de uso real. Este é um projeto pessoal; não alego utilizadores, adoção ou tráfego em produção.

Explorar

O repositório é público e inclui o README com instruções de instalação, a referência das variáveis de ambiente, os alvos do Make e a documentação de apoio em docs/.

O README do repositório está em português. Não existe instância de demonstração hospedada, por opção.