Este é o mapa do sistema como ele roda em produção: cada superfície de cliente, a API por trás delas, o banco de dados, como código novo chega à produção e como descobrimos quando algo quebra. O diagrama acima mostra o quadro completo; as seções abaixo percorrem cada peça.
| Camada | Tecnologias |
|---|---|
| Web app | React 18, TypeScript, Vite, Redux Toolkit, Axios, react-hook-form + Zod, i18next (en/pt), Tailwind CSS 3. O caderno de estudos acrescenta o BlockNote sobre o Mantine 8 para o editor e o React Flow para o quadro de roadmap. O quadro de Ideias também é React Flow |
| App mobile | React Native + Expo (Android primeiro), NativeWind, TypeScript. Divide os pacotes de estado, cliente de API e i18n com o web app pelo monorepo |
| Backend | Spring Boot 4.1, Java 25 (virtual threads), Spring Security, JWT (auth0 java-jwt), Spring AOP, Spring AI para o chat do agente, as sugestões de onboarding e a IA do caderno de estudos (cadeia de fallback de LLMs), PDFBox e jsoup para as fontes do caderno |
| Banco de dados | PostgreSQL 17 em produção (um cluster CloudNativePG; os testes e as stacks locais rodam o 15), schema controlado pelo Flyway (o Hibernate valida, nunca escreve), chaves primárias UUID, cache Caffeine na frente das leituras quentes |
| Entrega | GitHub Actions constrói a imagem do backend para o GHCR; o Flux (GitOps) faz o rollout dela num cluster k3s de um nó; o CI faz o deploy dos builds do web e da documentação no Cloudflare Workers Static Assets com o wrangler; Docker Compose para desenvolvimento local e e2e |
| Monitoramento | kube-prometheus-stack (Prometheus, Grafana), Loki + Alloy, GlitchTip (compatível com Sentry) |
A produção roda num cluster k3s de um nó cujo estado inteiro vive no Git e é aplicado pelo Flux, com a Cloudflare servindo os dois sites estáticos. O lado da aplicação são três peças, todas construídas pelo CI:
/api/v1, rodando no namespace beyou a partir de uma imagem no GitHub Container Registry. Pública em api.beyouweb.com, que o web app e as páginas da documentação chamam direto do navegador. Uma réplica, com os uploads num volume persistente.O banco é o beyou, num cluster PostgreSQL 17 compartilhado, operado pelo CloudNativePG no namespace data. Fazer merge na main é o deploy. No backend, o CI publica uma imagem :latest nova, a automação de imagens do Flux registra o digest dela no Git e faz o rollout, e voltar atrás é um git revert. Os dois sites estáticos vão do próprio CI para a Cloudflare com wrangler deploy, e é lá também que voltam atrás. Nada no servidor escuta numa porta pública. Um Cloudflare Tunnel disca para fora a partir do servidor e entrega os hostnames públicos do cluster ao Traefik, o único ingress, que roteia por host para cada Ingress; todo namespace fica atrás de uma network policy default-deny. O tópico de infraestrutura cobre a máquina, o cluster e os backups. Mais duas peças vivem fora do cluster: o site de marketing em beyouweb.com (HTML estático no Cloudflare Pages) e o app mobile, que é instalado no celular e fala com a mesma API do web app.
O conteúdo deste site de documentação tem seu próprio pipeline: o markdown e as specs OpenAPI vivem no repositório beyou-arch-design, o backend os importa para o PostgreSQL, e uma mudança de conteúdo reconstrói o site de documentação pré-renderizado e faz o deploy dele na Cloudflare.
Em produção a stack de observabilidade roda dentro do cluster como kube-prometheus-stack mais Loki, Alloy e GlitchTip; localmente um overlay do Compose responde as mesmas perguntas. Cada componente responde uma pergunta diferente:
O Grafana fica em cima dos três, com dashboards carregados do Git: saúde do serviço do backend, o agente de IA, uso do produto, logs, o host e o ingress. Ele também manda por e-mail os alertas sobre a máquina e o cluster (memória, disco, pods reiniciando ou travados, alvos fora do ar), enquanto o GlitchTip fica com os da aplicação. Ele vive em obs.beyouweb.com, atrás do próprio login. Esses dois são as únicas superfícies públicas de monitoramento; Prometheus, Loki e os exporters não têm hostname e só são alcançáveis pelo Grafana.
erDiagram
USER ||--o{ CATEGORY : possui
USER ||--o{ HABIT : possui
USER ||--o{ TASK : possui
USER ||--o{ GOAL : possui
USER ||--o{ ROUTINE : possui
USER ||--o{ NOTEBOOK_PAGE : possui
USER ||--o{ CALENDAR_TASK : possui
USER ||--o{ QUICK_NOTE : possui
CATEGORY }o--o{ HABIT : marcado
CATEGORY }o--o{ TASK : marcado
CATEGORY }o--o{ GOAL : marcado
ROUTINE ||--o{ ROUTINE_SECTION : contem
ROUTINE ||--|| SCHEDULE : "agendada por"
ROUTINE_SECTION ||--o{ HABIT_GROUP : agrupa
ROUTINE_SECTION ||--o{ TASK_GROUP : agrupa
HABIT_GROUP }o--|| HABIT : referencia
TASK_GROUP }o--|| TASK : referencia
HABIT_GROUP ||--o{ HABIT_GROUP_CHECK : registra
TASK_GROUP ||--o{ TASK_GROUP_CHECK : registra
NOTEBOOK_PAGE ||--o{ NOTEBOOK_PAGE : "pai de"
NOTEBOOK_PAGE ||--o{ BOARD_NODE : "quadro de"
BOARD_NODE }o--|| NOTEBOOK_PAGE : abre
NOTEBOOK_PAGE ||--o{ FLASHCARD : guarda
NOTEBOOK_PAGE ||--o{ SOURCE : lê
QUICK_NOTE |o--o| TASK : "virou"
sequenceDiagram
participant U as Usuário
participant FE as Frontend
participant BE as Backend
participant GO as Google
rect rgba(59, 130, 246, 0.25)
U->>FE: 🔑 Login com email + senha
FE->>BE: POST /auth/login
BE-->>FE: JWT (header) + Refresh Token (cookie HttpOnly)
FE->>FE: Guarda o JWT em memória
end
rect rgba(234, 88, 12, 0.25)
U->>FE: 🔐 Login com Google OAuth
FE->>GO: Redirecionamento de autorização
GO-->>FE: Código de autorização
FE->>BE: GET /auth/google?code=...
BE->>GO: Troca o código por access token
GO-->>BE: Perfil do usuário
BE-->>FE: JWT + Refresh Token
end
rect rgba(16, 185, 129, 0.25)
FE->>BE: 🔄 Refresh: requisição com JWT expirado
BE-->>FE: 401 Unauthorized
FE->>BE: POST /auth/refresh (cookie)
BE-->>FE: Novo JWT + novo Refresh Token
FE->>BE: Repete a requisição original
end
Authorization: Bearer; o backend entrega um novo no header de resposta X-Access-Token.beyou.oidc.providers ganha um botão no login web. O cliente envia o ID token que verificou pelo PKCE, e a conta é achada pelo emissor e pelo subject do token, nunca pelo e-mail, então um provedor novo só chega a uma conta existente por um vínculo feito de dentro dela. Desligado quando nada está configurado. O tópico de segurança tem as regras.37 controllers REST organizados por domínio, todos sob /api/v1:
| Grupo | Controllers | Caminhos base |
|---|---|---|
| Auth | Authentication, AuthVerification | /auth/* |
| Entidades principais | Category, Habit, Task, Goal | /category, /habit, /task, /goal |
| Rotinas | Routine, Schedule, Snapshot | /routine, /schedule, /routine/snapshot |
| Histórico | CheckHistory, XpHistory | /check-history, /xp |
| Modo Foco | Focus | /focus/cycles, /focus/micro-tasks, /focus/day |
| Diário | Mood | /mood |
| Calendário | Calendar | /calendar, /calendar/tasks |
| Quadro de Ideias | QuickNote | /quick-notes |
| Caderno de estudos | Notebook, NotebookBoard, NotebookCard, NotebookSource, NotebookStudy, NotebookAi, StudyCycle | /notebook/* |
| Resumo do Dia | DailyBriefing | /daily-briefing |
| Usuário | User, UserPhoto, UserExport | /user, /user/photo |
| IA | AiAgent, Onboarding | /ai/agent, /onboarding |
| Feedback | Feedback, FeedbackAdmin | /feedback, /feedback/admin |
| Notificações | NotificationPreferences | /notification/preferences, /notification/unsubscribe |
| Docs | Architecture, Blog, Api, Project, Search, Import | /docs/* |
flowchart LR
REQ["📥 Requisição"] --> FILT["🛡️ Filtro de segurança<br/>Validação do JWT"]
FILT --> CTRL["🎯 Controller<br/>Validação de DTO"]
CTRL --> SVC["⚙️ Service<br/>Lógica de negócio"]
SVC --> REPO["💾 Repository<br/>Consultas JPA"]
REPO --> DB[("🐘 PostgreSQL")]
DB --> REPO
REPO --> SVC
SVC --> MAP["🔄 Mapper<br/>Entidade → DTO"]
MAP --> CTRL
CTRL --> RES["📤 Resposta"]
flowchart TD
AX["Axios + Interceptor"]
ST["Redux Store<br/>packages/state compartilhado"]
PS["redux-persist<br/>localStorage (web)"]
UI["Componentes<br/>React / React Native"]
UI -->|"dispatch de actions"| ST
ST -->|"selectors"| UI
ST <-->|"hidrata / persiste"| PS
UI -->|"chamadas de API"| AX
AX -->|"dispatch no sucesso"| ST
AX -->|"401 → refresh automático"| AX
Os slices do Redux vivem em um pacote compartilhado do workspace (packages/state, 22 slices), então o web e o mobile rodam a mesma lógica de estado. O web app o envolve com redux-persist e exclui de propósito os slices de perfil, snapshot, celebration, mood, notebook, studyCycles e quickNotes, para que nenhum dado pessoal, texto de diário, anotação de estudo ou ideia caia no localStorage. O app mobile guarda a store em memória e rebusca ao abrir.
flowchart LR
ACT["✅ Check de hábito/tarefa<br/>na rotina"] --> XP["🎮 Calculadora de XP"]
XP --> UXP["👤 XP do usuário<br/>+ level up"]
XP --> HXP["💪 XP do hábito<br/>+ level up"]
XP --> CXP["📂 XP da categoria<br/>+ level up"]
XP --> RXP["📋 XP da rotina<br/>+ level up"]
ACT --> STR["🔥 Constância<br/>rastreio de streak"]