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 ao servidor 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 |
| 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 e as sugestões de onboarding (cadeia de fallback de LLMs) |
| Banco de dados | PostgreSQL 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 as imagens para o GHCR, o Watchtower as reimplanta; Docker Compose; nginx serve os builds do web e da documentação |
| Monitoramento | Prometheus, Grafana, Loki + Alloy, GlitchTip (compatível com Sentry) |
O lado da aplicação são quatro containers, todos construídos pelo CI e publicados no GitHub Container Registry:
/api/v1.Toda porta publicada escuta em 127.0.0.1. Os hostnames públicos chegam aos containers por um proxy reverso com TLS, então nada fica exposto diretamente. Duas peças vivem fora da stack do Compose: 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 dispara a reconstrução da imagem pré-renderizada.
Um único overlay do Compose carrega toda a stack de observabilidade, e é o mesmo arquivo em desenvolvimento e em produção. Cada componente responde uma pergunta diferente:
O Grafana fica em cima dos três, com quatro dashboards provisionados automaticamente: saúde da frota, internos da JVM do backend, o agente de IA e os logs. Ele vive em obs.beyouweb.com, atrás do próprio login. Esses dois são as únicas superfícies públicas de monitoramento; o resto do overlay (Prometheus, Loki, os exporters) fica em loopback e só é alcançável pelo Grafana.
erDiagram
USER ||--o{ CATEGORY : possui
USER ||--o{ HABIT : possui
USER ||--o{ TASK : possui
USER ||--o{ GOAL : possui
USER ||--o{ ROUTINE : 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
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.24 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 |
| Usuário | User, UserPhoto, UserExport | /user, /user/photo |
| IA | AiAgent, Onboarding | /ai/agent, /onboarding |
| Feedback | Feedback, FeedbackAdmin | /feedback, /feedback/admin |
| 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, 17 slices), então o web e o mobile rodam a mesma lógica de estado. O web app o envolve com redux-persist, excluindo de propósito os slices de perfil e snapshot para que nenhum dado pessoal caia no localStorage. O app mobile adiciona uma camada offline (packages/offline) para leituras e escritas enfileiradas.
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"]