Saltar para o conteúdo
Voltar aos projetos

cardosos

Java · ★ 0 · ⑂ 0 · 06/09/2026

  • Java 60%
  • TypeScript 34.7%
  • Shell 2.1%
  • PLpgSQL 1.7%
  • CSS 0.6%
  • Batchfile 0.4%
  • Dockerfile 0.3%
  • JavaScript 0.1%

CardosOS

Portfólio que se alimenta sozinho. Lê os meus repositórios do GitHub e as horas da WakaTime, guarda tudo numa base de dados própria, e o site serve a partir daí. Tem painel de administração e um chatbot que responde sobre o meu percurso.

Spring Boot 3 e PostgreSQL no back-end, Next.js no front-end, tudo em Docker.

Correr

Precisas de Docker Desktop a correr, Java 21 e Node 20.

No Windows é duplo clique em correr.bat: verifica o Docker, sobe a base de dados e o back-end, espera pelo health check e abre o browser.

À mão:

docker compose up -d --build backend
npm run dev --prefix frontend

Site em http://localhost:3000, API em http://localhost:8080, painel em /admin.

Configuração

Copia o .env.example para .env e preenche. Está comentado variável a variável.

Duas impedem o arranque: JWT_SECRET (gera com openssl rand -base64 32) e DB_PASSWORD. As outras degradam em vez de rebentar. Sem LLM_API_KEY o chat diz que está indisponível, sem GITHUB_TOKEN os projetos vêm vazios mas sem erro.

O REVALIDATE_SECRET tem de ter o mesmo valor no .env da raiz e em frontend/.env.local.

Como está feito

Um só Spring Boot, dividido em módulos com fronteiras verificadas em tempo de teste pelo Spring Modulith: se um módulo importar do interior de outro, o build falha. As setas a cheio são chamadas por interface, as tracejadas são eventos de domínio.

graph TD
    portfolio --> shared
    integrations --> shared
    chat --> shared
    chat --> portfolio
    contact --> shared
    auth --> shared
    notification --> shared
    integrations -. RepoSyncedEvent .-> portfolio
    contact -. ContactSubmittedEvent .-> notification

| Módulo | O que faz | |---|---| | integrations | Fala com o GitHub e a WakaTime e regista cada sincronização | | portfolio | Projetos, percurso e stack | | chat | Assistente DeepSeek, com o contexto construído da base de dados | | contact | Formulário, honeypot e limite por IP | | notification | Email pelo Resend, disparado por eventos | | auth | Login do administrador, JWT e refresh tokens | | shared | Envelope de API e rate limiter |

O site nunca chama o GitHub para servir uma página. A sincronização corre de 6 em 6 horas por cron, ou assim que chega um webhook, escreve na base de dados e avisa o Next para revalidar. Por isso o site continua a servir com o GitHub em baixo, e há um teste que prova isso apagando a resposta da API.

Decisões

Monólito modular em vez de microserviços. A modularidade que o requisito pede é verificada pelo Modulith, e cinco JVMs não cabiam na RAM nem nas horas.

JWT próprio em vez de Keycloak. Há um utilizador, o administrador.

Contexto do chatbot no prompt em vez de RAG. O corpus cabe em 5k tokens; RAG só acrescentava pipeline e latência.

Rate limiting em Postgres em vez de Redis. Dois contadores não pagam outro container, e em memória o limite duplicava com duas instâncias.

Watchtower em vez de deploy por SSH, para não guardar uma chave de produção nos segredos do GitHub.

Falta

O deploy. O GitHub Actions corre os testes em cada push e publica as imagens no GHCR a partir do main, mas não há servidor, e o docker-compose.prod.yml ainda não tem nada a escutar em 80/443.

Se der problemas

  • backend/mvnw tem de ter fim de linha LF. Com CRLF o shebang parte dentro da imagem Alpine. O .gitattributes já força, mas convém confirmar depois de mexer no ficheiro.
  • Alterações que não aparecem no site: apaga frontend/.next/dev/cache e reinicia o servidor. A cache do Next 16 continua a servir o valor antigo.
  • WakaTime a responder 422: falta definir o fuso horário no perfil da conta.
  • npm install a falhar com EALLOWSCRIPTS: é a política do npm nesta máquina. Corre o npm install diretamente em vez do fluxo do create-next-app.