Compare commits

..

254 Commits

Author SHA1 Message Date
Felipe Coutinho
01f161f011 docs: documenta fluxo completo de publicacao 2026-06-27 18:24:16 -03:00
Felipe Coutinho
4741087feb chore: prepara versao 2.7.10 2026-06-27 14:24:47 -03:00
Felipe Coutinho
32b190ab4e feat(lancamentos): oculta parcelas antecipadas por preferencia 2026-06-27 14:24:47 -03:00
Felipe Coutinho
d06bac5624 chore(deps): atualiza dependencias 2026-06-27 14:24:47 -03:00
Felipe Coutinho
be6fa6dcfc chore: prepara versao 2.7.9 2026-06-21 12:20:12 -03:00
Felipe Coutinho
954fdc148e docs: versiona instrucoes dos agentes 2026-06-21 12:20:01 -03:00
Felipe Coutinho
fb1759c2ee ci: publica releases somente por tags semver 2026-06-21 12:19:53 -03:00
Felipe Coutinho
b1b2f5fe0d chore: prepara versão 2.7.8 2026-06-21 11:54:27 -03:00
Felipe Coutinho
4d62abfc6b feat(dashboard): vincula tendências às categorias 2026-06-21 11:54:19 -03:00
Felipe Coutinho
1660f68a4b feat(anexos): adiciona filtro por pessoa 2026-06-21 11:54:15 -03:00
Felipe Coutinho
d363662548 feat(anotações): permite anexos em notas 2026-06-21 11:54:05 -03:00
Felipe Coutinho
129295d2e2 chore: prepara versao 2.7.7 2026-06-20 14:57:18 -03:00
Yuri Argolo
4b5cdf81b8 feat(lançamentos): ações de converter transações existentes em parceladas e recorrentes 2026-06-20 11:56:54 -03:00
Yuri Argolo
558197e870 feat: mapeamento automatico de categoria na importacao de planilhas 2026-06-20 11:56:54 -03:00
Felipe Coutinho
2fd6e3c323 Prepara versao 2.7.5 2026-06-13 11:05:00 -03:00
Felipe Coutinho
833845b5cf Ajusta widgets do dashboard 2026-06-13 11:04:54 -03:00
Felipe Coutinho
4cbdddb12e corrige importacao de lancamentos compartilhados 2026-06-09 20:41:41 -03:00
Felipe Coutinho
c81584095b docs: informa manutencao reduzida do projeto 2026-06-06 17:06:18 -03:00
Felipe Coutinho
8ccc4479be docs: registra release 2.7.3 2026-06-06 16:31:57 -03:00
Felipe Coutinho
2cead626ab chore: atualiza dependencias e build de PDF 2026-06-06 16:31:42 -03:00
Felipe Coutinho
811a035cb0 refactor: remove exports internos sem uso 2026-06-06 16:31:38 -03:00
Felipe Coutinho
356801324c feat: destaca fatura paga nos cartoes 2026-06-06 16:31:33 -03:00
Felipe Coutinho
b443fb010a config: adiciona origins confiaveis do Better Auth 2026-06-06 16:31:27 -03:00
Felipe Coutinho
026dff5399 Merge pull request #78 from felipegcoutinho/imgbot
[ImgBot] Optimize images
2026-05-31 18:16:42 -03:00
ImgBotApp
18b6a6a470 [ImgBot] Optimize images
*Total -- 1,696.54kb -> 1,060.14kb (37.51%)

/public/logos/dinheiro.png -- 72.04kb -> 17.17kb (76.17%)
/public/images/dashboard-preview-dark.png -- 597.68kb -> 350.27kb (41.4%)
/public/images/dashboard-preview-light.png -- 589.20kb -> 355.11kb (39.73%)
/public/avatars/default_icon.png -- 5.41kb -> 3.37kb (37.69%)
/public/logos/bipa.png -- 47.72kb -> 33.57kb (29.64%)
/public/images/pwa-preview-dark.png -- 181.92kb -> 138.71kb (23.75%)
/public/images/pwa-preview-light.png -- 177.18kb -> 136.59kb (22.91%)
/public/images/logo_small.svg -- 0.39kb -> 0.38kb (1.99%)
/public/images/logo_text.svg -- 6.74kb -> 6.73kb (0.12%)
/public/providers/minimax.svg -- 1.52kb -> 1.52kb (0.06%)
/public/providers/ollama_dark.svg -- 8.36kb -> 8.36kb (0.06%)
/public/providers/ollama_light.svg -- 8.36kb -> 8.36kb (0.06%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2026-05-31 21:11:31 +00:00
Felipe Coutinho
78e778311d chore(release): prepara versao 2.7.2 2026-05-31 18:06:04 -03:00
Felipe Coutinho
78c3ed5995 chore(release): prepara versao 2.7.1 2026-05-31 15:19:15 -03:00
Felipe Coutinho
c34adba587 style(inbox): padroniza acao de lancar notificacoes 2026-05-31 15:19:07 -03:00
Felipe Coutinho
99bc049cf4 fix(boletos): diferencia pagamentos e recebimentos 2026-05-31 15:18:55 -03:00
Felipe Coutinho
35abe1b0bf feat(dashboard): refina experiencia dos widgets 2026-05-31 15:18:43 -03:00
Felipe Coutinho
402f0072af feat(lancamentos): melhora painel de filtros ativos 2026-05-31 15:18:23 -03:00
Felipe Coutinho
02ee5bb758 feat(navegacao): adiciona atalhos financeiros e seletor mensal 2026-05-31 15:18:19 -03:00
Felipe Coutinho
41eecc2538 feat(preferencias): permite ocultar resumo do lancamento 2026-05-31 15:18:07 -03:00
Felipe Coutinho
cdcc677787 style(ui): atualiza identidade visual 2026-05-31 15:17:57 -03:00
Felipe Coutinho
e50eeba36e feat(insights): adiciona suporte ao Ollama 2026-05-28 20:01:44 -03:00
Felipe Coutinho
26cb18a9ad chore(release): prepara versao 2.7.0 2026-05-28 11:00:27 -03:00
Felipe Coutinho
382727a96d chore(deps): atualiza dependencias do workspace 2026-05-28 11:00:20 -03:00
Felipe Coutinho
0df648c7f3 chore(config): atualiza ambiente e setup 2026-05-28 11:00:14 -03:00
Felipe Coutinho
27f361923c style(ui): ajusta tipografia e descricoes 2026-05-28 10:59:46 -03:00
Felipe Coutinho
60b2612e8a feat(relatorios): refina indicadores e filtros 2026-05-28 10:59:36 -03:00
Felipe Coutinho
0171b0ce2f feat(lancamentos): refina filtros e tabela responsiva 2026-05-28 10:59:24 -03:00
Felipe Coutinho
311369f81b feat(lancamentos): amplia divisao e resumo do modal 2026-05-28 10:59:13 -03:00
Felipe Coutinho
ef2c8c50e8 feat(contas): adiciona rendimento pelo extrato 2026-05-28 10:58:59 -03:00
Felipe Coutinho
5319d8a5a6 feat(insights): adiciona suporte ao MiniMax 2026-05-28 10:58:52 -03:00
Felipe Coutinho
37247e319c feat(auth): adiciona sessao persistente no login 2026-05-28 10:58:43 -03:00
Felipe Coutinho
766af2b347 fix(ci): atualiza actions para checkout em Node 24 2026-05-23 13:27:22 -03:00
Felipe Coutinho
5dcd30010e fix(lancamentos): corrige tipo do filtro selecionado 2026-05-23 13:22:32 -03:00
Felipe Coutinho
d589df6993 chore(release): prepara versao 2.6.4 2026-05-23 13:18:15 -03:00
Felipe Coutinho
8a19f0f311 feat(lancamentos): aprimora antecipacao de parcelas 2026-05-23 13:17:55 -03:00
Felipe Coutinho
887885cd98 feat(relatorios): refina analise de parcelas 2026-05-23 13:17:49 -03:00
Felipe Coutinho
7a0e33efd8 feat(lancamentos): adiciona filtro por intervalo de datas 2026-05-23 13:17:42 -03:00
Felipe Coutinho
b9557961e5 style(logos): formata dicionario de nomes 2026-05-23 13:03:27 -03:00
Felipe Coutinho
53c8e47981 Merge branch 'pr-69' 2026-05-23 12:57:48 -03:00
Felipe Coutinho
adc9292cd8 Merge branch 'pr-72' 2026-05-23 12:57:47 -03:00
Felipe Coutinho
b95d6f6752 Merge branch 'pr-70' 2026-05-23 12:57:43 -03:00
lucas
c9f667a065 fix(transactions): restaura scroll no container de anexos do TransactionDialog
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 12:08:30 -03:00
lucas
01d9c6ea05 (HEAD) fix(transactions): remove overflow styles do container de rolagem do TransactionDialog 2026-05-23 11:58:53 -03:00
lucas
d383d2db91 fix(transactions): usa data da última transação ao adicionar nova linha no MassAddDialog
Quando o usuário adiciona uma nova linha de transação no dialog de múltiplos lançamentos,
a data agora é pré-preenchida com o valor da transação anterior em vez da data atual.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 11:27:43 -03:00
lucas
7a8d01debe feat(logos): adiciona nomes de exibicao via dicionario e busca sem acentos
- Adiciona arquivo display-names.ts com 433 nomes legiveis (ex: bb.png → "Banco do Brasil")
- Adiciona getLogoDisplayName() que consulta dicionario primeiro, com fallback para deriveNameFromLogo
- Adiciona normalizeForSearch() para busca accent-insensitive
- Atualiza account-dialog, card-dialog, logo-picker e use-logo-selection para usar a nova API

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 23:35:11 -03:00
lucas
3be15d3b15 feat(transactions): adiciona dialogo de confirmacao ao descartar lancamentos massivos
Quando o usuario tenta fechar ou cancelar o dialogo de multiplos lancamentos
com dados ainda nao salvos, agora exibe um ConfirmActionDialog pedindo
confirmacao. Evita perda acidental de dados preenchidos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:30:48 -03:00
Felipe Coutinho
fea9cf81d8 fix(docker): aplica configuracao do pnpm no build 2026-05-21 14:15:24 +00:00
Felipe Coutinho
7a10d431ab chore(release): prepara versao 2.6.1 2026-05-21 14:06:08 +00:00
Felipe Coutinho
b7343eb235 fix(ci): usa pnpm do packageManager no workflow 2026-05-21 13:59:14 +00:00
Felipe Coutinho
3bcc392f38 chore(release): prepara versao 2.6.0 2026-05-21 13:48:19 +00:00
Felipe Coutinho
5241de44af chore(deps): atualiza dependencias e pnpm 2026-05-21 13:48:07 +00:00
Felipe Coutinho
1a75662120 style(ui): refina indicadores e componentes visuais 2026-05-21 13:47:53 +00:00
Felipe Coutinho
7ca3f92467 feat(logos): adiciona logo da Bipa 2026-05-21 13:47:40 +00:00
Felipe Coutinho
6b044f3bc5 feat(cartoes): exibe fatura atual e ajusta indicadores 2026-05-21 13:47:30 +00:00
Felipe Coutinho
4e8f9cc5fa feat(lancamentos): aprimora parcelamentos e protecoes 2026-05-21 13:47:14 +00:00
Felipe Coutinho
b6659ef66e feat(importacao): melhora revisao de extratos 2026-05-21 13:46:42 +00:00
Felipe Coutinho
21d7396c80 feat(auth): permite bloquear novos cadastros 2026-05-21 13:46:26 +00:00
Felipe Coutinho
3a768bc8ba chore: bump para v2.5.7 e polimento geral 2026-05-14 19:13:33 +00:00
Felipe Coutinho
8a03a50132 feat(notes): edição inline de tarefas no modal de anotações 2026-05-14 19:13:29 +00:00
Felipe Coutinho
246bb14a00 feat(transactions): suporte a Ctrl+V em anexos e melhorias de UX no modal 2026-05-14 19:13:26 +00:00
Felipe Coutinho
86bcffec66 feat(reports): polimento visual e prefetch de logos na análise de parcelas 2026-05-14 19:13:09 +00:00
Felipe Coutinho
81e7151876 fix: corrige props depreciadas do react-day-picker v10
`table` renomeado para `month_grid` e `fromYear`/`toYear` substituídos
por `startMonth`/`endMonth`, quebrando o build do Docker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 14:24:35 +00:00
Felipe Coutinho
0bb664884a chore: prepara versão 2.5.6, atualiza dependências e polimento do changelog
Bump de dependências: next 16.2.6, react/react-dom 19.2.6, react-day-picker 10
(major), tailwindcss/postcss 4.3.0, tailwind-merge 3.6.0, better-auth 1.6.10,
ai-sdk (anthropic/google/openai), aws-sdk S3 3.1045, resend 6.12.3,
biome 2.4.15, knip 6.12.2, @types/node 25.6.2.

Changelog: número de versão em text-lg e padding do card de resumo aumentado
para p-6 para melhor leitura na linha do tempo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:51:58 +00:00
Felipe Coutinho
f02958df1d fix: ajusta display da calculadora para evitar overflow com valores longos
Adicionado min-w-0 nos containers flex para que truncate funcione corretamente
(flex items têm min-width: auto por padrão, impedindo o ellipsis). Fonte
adaptativa via getExpressionSizeClass: escala de text-3xl a text-sm conforme
o comprimento da expressão, com thresholds distintos para modo compacto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:51:50 +00:00
Felipe Coutinho
c4c52c02ab feat: divisão por porcentagem e indicador de orçamento no modal de lançamento
Toggle compacto R$/% no card 'Dividir lançamento' usando ToggleGroup do shadcn.
No modo %, cada input exibe o valor convertido em R$ logo abaixo (mesmo padrão
do InlinePeriodPicker). Helpers amountToPercent/percentToAmount reutilizam
safeToNumber, normalizeDecimalInput e formatDecimalForDbRequired.

Indicador de orçamento ao lado do nome da categoria selecionada: mostra
'R$ gasto de R$ orçado (%)' com cores semânticas (verde/âmbar/vermelho).
Busca assíncrona via getCategoryBudgetSummaryAction com cache por instância
(useRef<Map>) e cancelamento de race condition. Suprimido quando o input divide
a linha com o campo de tipo de transação (caso pré-lançamentos).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:51:44 +00:00
Felipe Coutinho
c9239c4f3c feat: filtro por faixa de valor e botão limpar em lançamentos
Novo filtro mín/máx de valor no sheet de filtros, com debounce (400ms) e
persistência via query string (amountMin/amountMax). Constantes AMOUNT_MIN_PARAM
e AMOUNT_MAX_PARAM extraídas para constants.ts; parsePositiveAmount exportado de
page-helpers e reutilizado pelo useDebouncedAmountFilter. A comparação do
debounce usa o valor normalizado para evitar roundtrips RSC desnecessários.
Botão 'Limpar' discreto ao lado do botão 'Filtros', visível apenas quando
há filtros ativos.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:51:30 +00:00
Felipe Coutinho
7128cc0ae7 fix: exclui transações de contas fora do saldo nos totais por pessoa e orçamentos
Adicionado leftJoin(financialAccounts) + excludeTransactionsFromExcludedAccounts()
em 6 queries de payers/details.ts (totais do mês, histórico, uso de cartões, etc.)
e em fetchBudgetsForUser/fetchCategoryBudgetSummary de budgets/queries.ts.
Contas marcadas como excludeFromBalance (ex: Ajuste de saldo) não entram mais
nos cálculos de gasto, alinhando a tela de Pessoas, Orçamentos e o badge do modal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:51:13 +00:00
Felipe Coutinho
467f71493d fix: ajusta label da opção 'period' no BulkActionDialog para recorrência
Em recorrência, currentInstallment é undefined e o label usava 'parcela',
gerando 'Todas as pessoas desta parcela (undefined/3)'. Adiciona helpers
getPeriodLabel/getPeriodDescription que adaptam o texto para installment
vs recurring, seguindo o padrão das outras opções.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:12:35 +00:00
Felipe Coutinho
0cec10ede3 fix: corrige formatação no bulk-action-dialog para passar no biome
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:27:38 +00:00
Felipe Coutinho
a6fba5f953 chore: prepara versão 2.5.5
Filtros multi-seleção em lançamentos (condição, forma de pagamento, pessoa,
categoria, conta/cartão), changelog redesenhado como timeline colapsável com
detecção de bump e resumo, e diálogos migrados para as animações utilitárias
do tw-animate-css. Inclui ajustes de label no BulkActionDialog, refinamentos
visuais na landing page e atualização da navbar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:11:59 +00:00
Felipe Coutinho
18893bfe02 docs: atualiza árvore de diretórios em README e CLAUDE com estrutura pós-refatoração
- Adiciona subpastas novas em shared/components/ (brand, widgets, feedback)
- Documenta o padrão interno de feature: actions.ts, queries.ts, actions/,
  components/, hooks/, lib/
- Atualiza shared/lib/ com pastas que já existiam mas faltavam listar
  (import, notifications, storage, version)
- Atualiza shared/utils/ com fetch-json.ts (movido do shared/lib) e id.ts
- Inclui lib/ no checklist de criação de nova feature

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 18:49:44 +00:00
Felipe Coutinho
7fdf9e2876 chore: prepara versão 2.5.4
Bump de patch refletindo a refatoração estrutural do commit anterior.
Sem mudanças funcionais — usuário não percebe nenhuma diferença.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 18:43:01 +00:00
Felipe Coutinho
7d0781b035 refactor: faxina arquitetural — código morto, identificadores em inglês e estrutura padronizada
Refatoração estrutural sem mudanças funcionais. Saldo líquido: −428 linhas.

Removido:
- 14 funções/constantes mortas verificadas via grep no repo todo: validateCategoriaOwnership,
  getInstallmentAnticipationsAction, getAnticipationDetailsAction, formatDecimalForDb,
  currencyFormatterNoCents, optionalDecimalSchema, formatMonthLabel,
  getGoalProgressStatusColorClass, MONTH_PERIOD_PARAM, calculateRemainingInstallments,
  e 5 funções fetch* não usadas em inbox/queries.ts.
- 1 tipo morto (ImportRow) + 2 órfãos consequentes (InstallmentAnticipationWithRelations,
  GoalProgressStatus convertido em interno).
- ~30 export keywords desnecessários (símbolos usados apenas no próprio arquivo).
- Re-exports mortos em barrels: EstablishmentLogoPicker, CategoryReportSkeleton,
  WidgetSkeleton, toNameKey.
- Arquivo features/reports/types.ts (barrel inteiro era órfão).

Padronizado (PT-BR→EN em identificadores expostos):
- 4 constantes globais (LANCAMENTOS_* → TRANSACTIONS_*).
- 12 tipos/interfaces (Lancamento*/Pagador*/Estabelecimento* → equivalentes EN).
- 13 funções/components exportados (fetchPagador*, EstabelecimentoInput, PagadorInfoCard, etc.).
- 5 props cross-file (preLancamentosCount → inboxPendingCount, pagadorAvatarUrl → payerAvatarUrl, etc.).
- Mantidas em PT-BR conforme exceção do CLAUDE.md: variáveis locais (pagador, categoria,
  lancamento), accessor key pagadorName (persistida em preferências), strings de UI.

Reorganizado:
- transactions/: 14 helpers soltos na raiz movidos para lib/; barrel actions.ts reduzido
  de 76 linhas de wrappers para 14 linhas de re-exports puros; anticipation-actions.ts
  movido para actions/anticipation.ts.
- dashboard/: 8 helpers soltos consolidados em dashboard/lib/.
- reports/: 5 query files na raiz consolidados em reports/lib/.
- payers/: detail-actions.ts (21KB) e detail-queries.ts movidos para payers/lib/.
- shared/components/: 9 dos 16 componentes soltos agrupados em brand/, widgets/, feedback/.
- shared/lib/fetch-json.ts movido para shared/utils/fetch-json.ts.

Validação: pnpm exec tsc --noEmit (0 erros), biome check (0 issues), knip (sem unused).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 18:42:54 +00:00
Felipe Coutinho
b9b843b9db Revise README for .env setup and remove acknowledgments
Updated instructions for creating a .env file and removed acknowledgments section.
2026-05-05 14:51:36 -03:00
Felipe Coutinho
01215b3124 chore: prepara versao 2.5.3 2026-05-05 17:17:33 +00:00
Felipe Coutinho
d70223e7b3 chore: renova massa de dados mock 2026-05-05 17:17:26 +00:00
Felipe Coutinho
6ea064e1bd style: ajusta espacamento de telas do dashboard 2026-05-05 17:17:19 +00:00
Felipe Coutinho
9c0669a152 feat: padroniza contas e cartoes inativos 2026-05-05 17:17:13 +00:00
Felipe Coutinho
b2d4b29cb5 feat: aprimora detalhes de lancamentos 2026-05-05 17:17:06 +00:00
Felipe Coutinho
1df2ba787d chore: bump versão para 2.5.2 e atualizar changelog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:42:51 +00:00
Felipe Coutinho
e5d9b66cca feat(dashboard): complementar texto de recorrências com "mensais"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:42:48 +00:00
Felipe Coutinho
37edb1b76d feat(notes): substituir ícone de tarefa pendente por RiSubtractLine em contextos read-only
No card e no modal de detalhes de anotações, onde não há interação
de marcação, tarefas não concluídas exibem RiSubtractLine em vez
do quadrado com borda. Locais interativos mantêm o comportamento atual.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:42:44 +00:00
Felipe Coutinho
6288f5f8d4 feat(budgets, cards): progress bar em cor destructive quando limite excedido
Adiciona prop indicatorClassName ao componente Progress. Orçamentos
estourados e cartões com 100% do limite utilizado exibem a barra
com indicador e fundo na cor destructive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:42:39 +00:00
Felipe Coutinho
57ac326c2a feat(transactions): filtro de contas por tipo dinheiro e sinal + em transferências recebidas
Ao selecionar "Dinheiro" como forma de pagamento, exibe apenas contas
do tipo "Dinheiro". Transferências recebidas (amount > 0) passam a
exibir sinal + mantendo a cor azul.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:42:35 +00:00
Felipe Coutinho
dccc18b1c1 fix(transfers): corrigir forma de pagamento de pix para transferência bancária
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:42:31 +00:00
Felipe Coutinho
0cb01a1d4c feat(accounts): adicionar tipos de conta dinheiro e outros com ícones no seletor
Adiciona "Dinheiro" (issue #50) e "Outros" à lista de tipos de conta.
Implementa AccountTypeSelectContent com ícones distintos por tipo via
getAccountTypeIcon em shared/utils/icons.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:42:27 +00:00
Felipe Coutinho
51652da4f8 fix(invoices): exibir ícone de anexo na fatura do cartão (2.5.1)
fetchCardTransactions não preenchia hasAttachments, então o ícone não
aparecia em /cards/[cardId]/invoice. Agora delega para
fetchTransactionsWithRelations, que já calcula o flag via EXISTS.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 01:44:08 +00:00
Felipe Coutinho
7a74f9405e fix(lint): corrigir formatação do snapshot de migration e imports
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:28:27 +00:00
Felipe Coutinho
94bf93194f chore: ajustes de componentes, estilos, dependências e métricas do dashboard
- dashboard: melhorias em métricas, filtros de transações e overview de período
- transactions: colunas, tabela e página com novos campos e ajustes de exibição
- ui: card, table, navigation-menu, navbar, month-picker, logo-picker, theme-toggler
- calculator: ajustes de display, keypad e estado
- calendar: melhorias de grid e day-cell
- insights: atualização de constantes
- settings: pequenos ajustes
- pnpm-lock: atualização de dependências
- pdf.worker: atualização do worker

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:08:53 +00:00
Felipe Coutinho
d55173e8c1 refactor(transactions): remover exports mortos dateFormatter e monthFormatter
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:08:45 +00:00
Felipe Coutinho
4a73088c09 chore(assets): substituir dashboard-preview de webp para png
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:08:41 +00:00
Felipe Coutinho
eaa20448a8 chore(landing): remover seção de galeria de telas
Remove a seção "Veja o que você pode fazer" com o componente ScreenshotTabs,
as 14 imagens preview-*.webp, o link #telas do nav e o export pwaCompatList sem uso.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:08:37 +00:00
Felipe Coutinho
367d78d43d fix(dashboard/anotacoes): corrigir divergência de fuso no formatter de datas
Intl.DateTimeFormat sem timeZone usava o fuso do servidor (UTC) no SSR
e o fuso do browser (BRT) no cliente, causando erro de hidratação.
Ambos os formatters passam a usar timeZone: "America/Sao_Paulo" explicitamente.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:08:29 +00:00
Felipe Coutinho
2fc6d11d78 feat(dashboard/boletos): nome do boleto como link para lançamentos do período
- nome do boleto virou link para /transactions?q=<nome>
- quando o período selecionado não é o atual, inclui ?periodo=<mes-ano> na URL
- ícone RiExternalLinkLine ao lado do nome, mesmo padrão do widget de faturas

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:08:23 +00:00
Felipe Coutinho
0f5c735be0 feat(dashboard): confirmação de pagamento com conta e data para faturas e boletos
- widget de faturas abre modal com seleção de conta de origem e data antes de pagar
- widget de boletos ganha a mesma paridade: modal com conta de pagamento e data
- toggleTransactionSettlementAction aceita paymentAccountId e paymentDate opcionais
- DashboardBill expõe accountId para inicializar o modal com a conta já vinculada

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:08:16 +00:00
Felipe Coutinho
4bea6330bf feat(faturas/extrato): ajuste de fatura, reembolso e ajuste de saldo da conta
- botão "Ajustar fatura" na página da fatura abre dialog com input do valor real
  e preview da diferença; action faz upsert/delete idempotente do lançamento de ajuste
- opção "Reembolso" no dropdown de ações de despesas à vista cria receita espelhada
  no extrato ou fatura correta, vinculada ao lançamento original
- botão "Ajustar saldo" no extrato da conta compara saldo real informado e gera
  lançamento de ajuste por (accountId, period) via upsert/delete idempotente
- constantes INVOICE_ADJUSTMENT_NAME, ACCOUNT_BALANCE_ADJUSTMENT_NAME,
  REFUND_NOTE_PREFIX e buildRefundNote() centralizadas em shared/lib/accounts/constants.ts
- extrato agora contabiliza transferências internas em Entradas e Saídas corretamente

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:08:07 +00:00
Felipe Coutinho
8389752172 feat(cartoes): exigir limite e bloquear lançamentos acima do disponível
- campo limite passa a ser NOT NULL DEFAULT 0 no schema (migration 0029)
- validação Zod com requiredDecimalSchema garante valor positivo no formulário
- validateCardLimit() em transactions/actions/core.ts bloqueia criação e edição
  de despesas em cartão que ultrapassem o limite disponível, retornando mensagem
  com o valor exato restante
- tipos Card.limit e Card.limitAvailable deixam de ser nullable
- branch "sem limite registrado" removido de card-item.tsx

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:07:56 +00:00
Felipe Coutinho
19b5aa00ee docs(changelog): registrar vetorização dos logos em v2.4.4
Adiciona ao bloco da v2.4.4 as mudanças de logo (split em LogoIcon/
LogoText, SVGs inline, troca dos PNGs por SVGs no public/ e
rasterização em alta resolução nos PDFs) e o fix do baseUrl no
tsconfig. Também atualiza a data da release para 2026-04-27.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 00:13:54 +00:00
Felipe Coutinho
863ccc0fd2 refactor(exports): renderizar logos SVG em alta resolução no PDF
Atualiza loadExportLogoDataUrl para carregar SVGs e rasterizar no canvas
a 4× a resolução natural antes de retornar o data URL — preserva nitidez
quando o PDF amplia a imagem. Default do path mudou para
/images/logo_text.svg.

Os exports de categorias e lançamentos agora apontam para os arquivos
.svg em vez dos .png removidos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 00:11:21 +00:00
Felipe Coutinho
29d99cbedb chore(assets): trocar PNGs do logo por SVGs vetorizados
- adiciona public/images/logo_small.svg e logo_text.svg com width/height
  explícitos (necessário para naturalWidth/Height funcionar via <img>)
- remove os PNGs antigos (logo_small.png e logo_text.png)
- atualiza referência no README.md (header) para logo_small.svg

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 00:11:15 +00:00
Felipe Coutinho
dbeb98bbe4 refactor(logo): vetorizar e separar LogoIcon/LogoText em arquivos próprios
Substitui as PNGs raster do componente Logo por SVGs inline e quebra
em dois subcomponentes reutilizáveis:

- LogoIcon (src/shared/components/logo-icon.tsx): SVG do ícone laranja
  (viewBox 0 0 200 200), aceita SVGProps via spread
- LogoText (src/shared/components/logo-text.tsx): SVG do wordmark
  (viewBox 0 0 574.201 89.6), fill #000 + dark:invert para alternar
  preto/branco conforme o tema
- Logo (orquestrador): mantém a API atual (variants full/compact/small,
  invertTextOnDark, colorIcon, iconClassName, textClassName) e agora
  renderiza os SVGs em vez de next/image

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 00:11:10 +00:00
Felipe Coutinho
c0436dc2ac fix(tsconfig): remover baseUrl para evitar erro de deprecação no TS 7
A remoção de "ignoreDeprecations": "6.0" no commit anterior reabriu o
erro TS5101 sobre baseUrl. Como moduleResolution: bundler resolve os
paths relativos ao próprio tsconfig.json, baseUrl é redundante e pode
ser removido.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 22:58:09 +00:00
Felipe Coutinho
e1e76fadc0 chore(release): v2.4.4
Versão dedicada a remover a dependência de pgcrypto e a enxugar os
backups. CHANGELOG, badge do README e fluxo de restore atualizados;
script pnpm db:extensions removido do package.json.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 22:52:54 +00:00
Felipe Coutinho
9b2c15ef7d chore: ajustes de configuração diversos
- tsconfig: target ES2017 → ES2022, remove ignoreDeprecations 6.0
- gitignore: ignora pasta .codex
- next.config: remove linha em branco supérflua

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 22:52:50 +00:00
Felipe Coutinho
fbe3fceb9f chore(backup): escopar dumps aos schemas public e drizzle
Adiciona --schema=public --schema=drizzle aos pg_dump (modos remote e
docker), descartando os schemas internos do Supabase (auth, realtime,
storage, vault, graphql, etc.). Restaurações em PostgreSQL padrão
deixam de produzir os ~148 erros de role/extension does not exist.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 22:52:42 +00:00
Felipe Coutinho
39f3cd8b20 feat(payers): gerar share_code na aplicação e remover pgcrypto
Move a geração do share_code do PostgreSQL para a camada de aplicação,
eliminando a dependência da extensão pgcrypto no setup do banco.

- schema: drop default substr(encode(gen_random_bytes(24), 'base64'), 1, 24)
  da coluna share_code em pagadores (continua NOT NULL)
- nova util generateShareCode() em shared/lib/payers/share-code.ts
  (server-only, usa crypto.randomBytes do Node)
- chamadas explícitas em createPayerAction, ensureDefaultPagadorForUser,
  resetUserAppData e mock-data ao inserir pagadores
- migration 0028_fancy_reaper renumerada (0027 já estava ocupado por
  arquivo órfão); journal e snapshot atualizados
- remove etapa de habilitação de pgcrypto do docker-entrypoint.sh
- remove scripts/postgres/ (init.sql e enable-extensions.ts)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 22:52:36 +00:00
Felipe Coutinho
791fec7751 chore(release): v2.4.3
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 14:47:05 +00:00
Felipe Coutinho
114e2b4011 chore(deps): bump dependencies e schema do biome
- @aws-sdk/client-s3 + s3-request-presigner: 3.1032 → 3.1037
- @better-auth/passkey: 1.6.5 → 1.6.9
- better-auth: 1.6.5 → 1.6.9
- @tanstack/react-query: 5.99.2 → 5.100.3
- @biomejs/biome: 2.4.12 → 2.4.13 (com $schema atualizado)
- @tailwindcss/postcss + tailwindcss: 4.2.2 → 4.2.4
- resend: 6.12.0 → 6.12.2
- knip: 6.4.1 → 6.7.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 14:46:58 +00:00
Felipe Coutinho
f15a003cef fix(docker): healthcheck usar 127.0.0.1 para hosts com IPv6 (#44)
Containers em hosts com IPv6 habilitado tentavam conectar via ::1
e falhavam por timeout antes de cair no fallback IPv4. Fixar
127.0.0.1 elimina a ambiguidade.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 14:46:18 +00:00
Felipe Coutinho
7f07a9cbf6 fix(i18n): corrigir rótulos pt-br em categorias e dialog de antecipação
- updateCategoryAction: mensagem de sucesso "Category atualizada com
  sucesso." → "Categoria atualizada com sucesso."
- AnticipateInstallmentsDialog: rótulos "Period" → "Fatura" e
  "Category" → "Categoria"

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 14:46:12 +00:00
Felipe Coutinho
5fa234884e style(ui): refresh em badges de tipo, radio buttons e antecipação
- TransactionTypeBadge: substitui StatusDot por ícones direcionais
  (RiArrowRightDownLine receita, RiArrowRightUpLine despesa,
  RiArrowLeftRightLine transferência), adiciona borda e shadow sutil
  e dessaturação no dark mode; rótulo "Transferência" abreviado
  para "Transf."
- RadioGroup: indicador trocado de RiCircleLine por RiCheckLine com
  fundo sólido primary no estado selecionado
- Tabela de seleção de parcelas no dialog de antecipação reduzida
  para três colunas (estabelecimento, fatura, valor); coluna de
  vencimento removida e nome do estabelecimento absorve a parcela
- Inter agora carrega explicitamente os pesos 500, 600 e 700

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 14:46:05 +00:00
Felipe Coutinho
b453b432ed perf(logos): pré-resolver mapeamentos Logo.dev no servidor
Cada EstablishmentLogo dispara um GET para /api/logo/mapping por
nome único (deduplicado pelo React Query, mas ainda N requests por
página). Em /dashboard, /transactions e /payers/[payerId] agora
fazemos uma única query SQL em batch (fetchEstablishmentLogoMap) e
semeamos o cache do React Query antes do primeiro render via novo
LogoPrefetchProvider — eliminando os requests da rede.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 14:45:54 +00:00
Felipe Coutinho
7f05d2a681 fix(attachments): limpar arquivos órfãos no S3 em deleções e reset
Três caminhos de deleção não chamavam o cleanup de storage, deixando
arquivos órfãos no S3:

- deleteTransactionBulkAction: deleções por escopo de série (período,
  futuras, todas) agora coletam attachments vinculados antes do delete
  e disparam cleanupAttachmentsAfterTransactionDelete
- deleteMultipleTransactionsAction: mesma correção para seleção
  múltipla de lançamentos
- resetUserAppData: reset de conta em Ajustes coleta os fileKeys
  antes de truncar e remove os objetos do S3 em paralelo

Também ajusta deleteS3Object para ignorar NoSuchKey silenciosamente,
necessário para providers S3-compatíveis como Cloudflare R2 que não
são idempotentes nessa operação.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 14:45:45 +00:00
Felipe Coutinho
b14f487824 feat(transactions): edição cooperativa e visibilidade de divisões
Adiciona splitGroupId para vincular as duas shares de um lançamento
dividido (schema + índice + migration 0026). Habilita:

- Edição de par dividido com escolha de escopo (apenas este lado ou
  ambos) via novo SplitPairDialog e updateTransactionSplitPairAction
- Filtro "Somente divididos" (isDivided) na tabela de lançamentos
- Visibilidade de anexos para pessoas com acesso compartilhado via
  payerShares; upload e detach em massa expandem para shares irmãs
- Cópia independente de anexos no fluxo "Importar para Minha Conta"
  (novo fileKey, novo userId, S3 CopyObject) com seção read-only
  "Anexos que serão copiados" no dialog de importação
- Ícone de clipe na tabela de lançamentos da página da pessoa via
  EXISTS em fetchPagadorLancamentos

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 14:45:35 +00:00
Felipe Coutinho
5b03824a72 fix(security): remover header CSP de respostas de API
CSP não tem efeito em respostas JSON e expunha domínios
internos (Umami, Supabase, logo.dev) em endpoints públicos.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 14:44:18 +00:00
Felipe Coutinho
74dda549f5 style(format): corrigir ordenação de imports em 3 arquivos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 19:57:05 +00:00
Felipe Coutinho
137b63f256 style(format): corrigir formatação Biome em 5 arquivos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 19:54:56 +00:00
Felipe Coutinho
f747405264 feat(calendar): agrupar parcelas da mesma série em evento único
Lançamentos parcelados com o mesmo seriesId agora são consolidados em
um único evento do tipo 'installment' no calendário, exibindo 'Nx de
R$ X' em vez de repetir o mesmo item N vezes. Legenda e modal de
detalhes atualizados para refletir o novo tipo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 19:50:02 +00:00
Felipe Coutinho
cbc17c8513 style(notes): polimento visual nas tarefas e modal de detalhes
Ícone de tarefa concluída em card e detalhes simplificado para
RiCheckLine verde sem caixa. Checkbox no modal de edição usa bg/border
success com texto success-foreground (claro no light, escuro no dark).
Footer do modal de detalhes reordenado: Cancelar à esquerda, Alterar
primário à direita.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 19:23:36 +00:00
Felipe Coutinho
c41fafc319 style(assets): atualizar previews de lançamentos e pwa
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:41:48 +00:00
Felipe Coutinho
0bc3f06b77 refactor(ui): renomear "Pagador/Pagadores" para "Pessoa/Pessoas" na interface
Todas as strings visíveis ao usuário (labels, títulos, toasts, mensagens
de erro, cabeçalhos de tabela, exportações) foram atualizadas. Acordos
de gênero em português corrigidos. Código, rotas (/payers) e schema do
banco (pagadores) permanecem inalterados — divergência intencional
documentada em CLAUDE.md e CHANGELOG.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:29:55 +00:00
Felipe Coutinho
2f68bcf039 style(changelog): destacar resumo de versão com borda e itálico discretos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:56:52 +00:00
Felipe Coutinho
41dcd5cec9 docs(claude): exigir parágrafo humano em cada versão do CHANGELOG
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:56:26 +00:00
Felipe Coutinho
6391f07eb6 fix(changelog): renderizar parágrafo de resumo por versão
Parser ignorava texto livre entre o cabeçalho ## [versão] e a primeira
seção ###. Adicionado campo `summary` em ChangelogVersion e captura das
linhas de texto antes da primeira seção. ChangelogTab renderiza o resumo
logo abaixo do cabeçalho, antes das entradas técnicas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:55:09 +00:00
Felipe Coutinho
ae9dd364c4 chore(release): v2.4.2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:52:26 +00:00
Felipe Coutinho
e005add233 feat(logo): migrar token Logo.dev para runtime server-side
NEXT_PUBLIC_LOGO_DEV_TOKEN renomeado para LOGO_DEV_TOKEN — lido apenas
em runtime no servidor. URL construída nos endpoints /api/logo/mapping e
/api/logo/search; cliente nunca recebe o token. Novo server.ts com
isLogoDevEnabled() e buildLogoDevUrl(). LogoDevProvider (Context) propaga
flag `enabled` para Client Components. Build arg removido do Dockerfile
e do workflow docker-publish.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:52:24 +00:00
Felipe Coutinho
6d81ff8b53 style(ui): polimento visual — tema, cards, dark mode e landing page
Raio de borda global 0.625rem → 0.7rem; ajustes finos em --card e --border.
DotPattern removido do layout, tela de auth e landing page.
Account-card redesenhado (cores de saldo, tooltip de flags de exclusão).
Budget-card, card-item, calendário (day-cell, event-modal) com layout revisado.
Auth-card-shell simplificado (sem glassmorphism/blob). Landing page com
mainFeatures + extraFeatures em grid único e dark mode nos botões de CTA.
Imagens de preview da landing atualizadas. CSS --data-7..10 removidas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:52:17 +00:00
Felipe Coutinho
5d84ae928a refactor(nav): remover sidebar, unificar navegação na navbar
Componentes da sidebar lateral (app-sidebar, nav-main, nav-secondary,
nav-user, nav-link), sidebar.tsx e use-mobile.ts removidos.
Barrel exports órfãos de shared/hooks, shared/components/providers,
shared/lib/schemas e shared/lib/types também removidos.
Navbar recebe ajustes menores de markup e acessibilidade.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:52:07 +00:00
Felipe Coutinho
ba05985725 refactor(dashboard): reorganizar módulos em subdiretórios e nova arquitetura de widgets
Arquivos de queries, helpers e controllers dispersos na raiz de dashboard/
foram movidos para subdiretórios temáticos (bills/, invoices/, notes/,
notifications/, overview/, payments/, goals-progress/, categories/).
~25 widgets monolíticos obsoletos removidos em favor de nova arquitetura
baseada em widget-registry com components/widgets/. Novos componentes:
category-breakdown-chart/list, goals-progress-item, percentage-change-indicator.
Imports atualizados em fetch-dashboard-data e transaction-filters limpos.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:51:56 +00:00
Felipe Coutinho
3e80d5995b chore(release): v2.4.1
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 21:16:44 +00:00
Felipe Coutinho
68daae7926 fix(docker): fixar PGDATA para compatibilidade com postgres:18-alpine (#41)
Container do PostgreSQL falhava ao iniciar em instalações existentes
após atualização da imagem: o entrypoint passou a recusar dados no
caminho legado. Variável PGDATA fixa o caminho e preserva dados de
quem já tinha o volume populado.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 21:16:40 +00:00
Felipe Coutinho
9413c470a8 style(ui): restaurar indentação tabs no dashboard layout
PR #42 trocou tabs por spaces no arquivo inteiro, quebrando o Biome.
Revertido pelo lint:fix para manter consistência com o resto do projeto.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 21:16:35 +00:00
Felipe Coutinho
ad1b0aa979 refactor(settings): remover tab órfã de Integrações
A tab foi introduzida no PR #42 mas não tinha TabsContent correspondente
e o value tinha typo ("intergrations") — UI vazia.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 21:16:31 +00:00
Felipe Coutinho
4d9a1c0a35 perf(db): otimizar índices — remover 7 sem uso, adicionar 17 em FKs
Baseado em análise do pg_stat_user_indexes (187 dias de estatísticas):
removidos 7 índices com 0 scans e adicionados 17 índices em foreign
keys que antes geravam sequential scans durante deletes nas tabelas pai.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 21:16:26 +00:00
Felipe Coutinho
5635705c56 feat(ui): layout animado auth, capitalização navbar (PR #42) 2026-04-16 15:19:53 +00:00
Alexsandro
4c97ed569d Merge branch 'main' into feat/fix-ui 2026-04-16 11:52:37 -03:00
Alexsandro
22a88de993 style(ui): update auth pages layout and navigation capitalization
This commit improves the visual design of the auth pages by adding a new layout wrapper with an animated blob background effect and updating the auth card shell with a glassmorphism style. It also updates the navigation items to use capitalized labels instead of lowercase for better readability.
2026-04-15 14:35:44 -03:00
Felipe Coutinho
9456aa98bc fix(ci): passar NEXT_PUBLIC_LOGO_DEV_TOKEN como build arg no Docker
NEXT_PUBLIC_* é inlined pelo Next.js em build time — a variável precisa
ser injetada via ARG no Dockerfile e build-args no workflow do CI.
Sem isso, o token fica undefined e os logos nunca são exibidos.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 01:06:23 +00:00
Felipe Coutinho
21c6a8d9d0 fix(lint): corrigir schema biome.json e formatação de imports
- biome.json: bump schema 2.4.10 → 2.4.11
- establishment-logo-picker.tsx, establishment-logo.tsx, navigation-menu.tsx, logo/index.ts: organizar imports e ajustar formatação conforme Biome 2.4.11

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 00:31:46 +00:00
Felipe Coutinho
c29ffa9a12 docs: registrar v2.4.0 — integração Logo.dev; atualizar screenshots
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 00:27:03 +00:00
Felipe Coutinho
8875de843b chore(deps): separar radix-ui em pacotes individuais e atualizar dependências; bump 2.4.0
- Remove pacote `radix-ui` (bundle monolítico); importa direto `@radix-ui/react-navigation-menu` e `@radix-ui/react-slider`
- Bump: @ai-sdk/* , @aws-sdk/* , @tanstack/react-query, ai, resend, dotenv, knip, @biomejs/biome, @types/node

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 00:26:56 +00:00
Felipe Coutinho
679ea752bb feat(logo): integração Logo.dev para logos automáticos de estabelecimentos
- Nova tabela `establishment_logos` no schema (userId + nameKey → domain)
- Utilitários: `buildLogoDevUrl`, `toNameKey`, `logoQueryKeys`, `LOGO_DEV_TOKEN`
- `EstablishmentLogo`: exibe logo via Logo.dev com fallback para iniciais; hover mostra ícone de edição
- `EstablishmentLogoPicker`: popover para buscar e fixar domínio Logo.dev por estabelecimento
- API routes: `GET /api/logo/mapping` e `GET /api/logo/search`
- Server actions/queries para persistência do mapeamento por usuário
- CSP: libera `https://img.logo.dev` em `img-src`
- `.env.example`: variáveis `NEXT_PUBLIC_LOGO_DEV_TOKEN` e `LOGO_DEV_SECRET_KEY`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 00:26:48 +00:00
Felipe Coutinho
1161e97d9e docs: reestruturar README em dois perfis; bump 2.3.8
- README: perfil Usar (só Docker) e Desenvolver (hot-reload)
- README: seção Docker simplificada; scripts atualizados
- CHANGELOG: entrada 2.3.8 com mudanças de infraestrutura Docker

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:45:19 +00:00
Felipe Coutinho
55d7dedd9a chore(scripts): reduzir scripts docker de 10 para 5
docker:up, docker:db, docker:down, docker:logs, docker:update

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:45:15 +00:00
Felipe Coutinho
ad2752b7b0 chore(docker): simplificar compose e entrypoint
- compose: removidos profiles, build e dependência de arquivo externo;
  agora standalone com curl + docker compose up -d
- compose: variáveis opcionais movidas para .env via env_file
- entrypoint: extensão pgcrypto criada via Node.js antes das migrations
- entrypoint: loop de retry reescrito; removido hack @localhost→@db

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:45:12 +00:00
Felipe Coutinho
58db357cde docs: reescrever README com guia de instalação para leigos; atualizar changelog 2.3.7
- README: seção "Como rodar" reescrita com 4 modos explicados para leigos
- README: seção Docker atualizada (sem .env obrigatório, localhost funciona)
- package.json: corrigir env:setup apontando para setup-env.sh deletado → setup.mjs
- CHANGELOG 2.3.7: documentar fix do localhost→db e default DATABASE_URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 01:49:40 +00:00
Felipe Coutinho
99a9ff5512 fix(docker): resolver DATABASE_URL localhost→db no container automaticamente
- docker-entrypoint.sh: substituir @localhost: por @db: via sed antes das
  migrations e do Next.js subirem — transparente para o usuário
- docker-compose.yml: adicionar valor padrão para DATABASE_URL para
  permitir subir sem .env configurado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 01:48:20 +00:00
Felipe Coutinho
5bcf4f69d3 chore(scripts): remover órfãos dev.ts e setup-env.sh; atualizar changelog 2.3.7
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 01:17:20 +00:00
Felipe Coutinho
95099c1a94 chore(docker): passar PUBLIC_DOMAIN e variáveis Umami para o container
Adiciona PUBLIC_DOMAIN, UMAMI_URL, UMAMI_WEBSITE_ID e UMAMI_DOMAINS
ao bloco de environment do serviço app no docker-compose.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 01:16:44 +00:00
Felipe Coutinho
94912f7edc fix(scripts): corrigir install-deps.sh — spinner, corepack e PATH
- spinner_stop: adicionar || true em kill/wait para evitar exit com set -e
- suprimir prompt interativo do corepack com COREPACK_ENABLE_DOWNLOAD_PROMPT=0
- exportar PATH do Homebrew antes do resumo para pnpm --version funcionar
- remover mensagem "próximo passo" do final do script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 01:16:40 +00:00
Felipe Coutinho
bf6adfa3f1 chore(analytics): mover configuração do Umami para variáveis de ambiente
- UMAMI_URL, UMAMI_WEBSITE_ID e UMAMI_DOMAINS carregados via process.env
- script só é injetado se as vars estiverem definidas
- CSP atualizada dinamicamente com base no UMAMI_URL
- documentado no .env.example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 22:50:03 +00:00
Felipe Coutinho
e4b9dd4254 chore: versão 2.3.7 — corrigir versão e consolidar CHANGELOG
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 22:43:49 +00:00
Felipe Coutinho
f1907c8697 fix(settings): ajuste de indentação e texto no formulário de exclusão de conta
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 22:43:46 +00:00
Felipe Coutinho
805bcb863d fix(logo-picker): corrigir renderização de miniaturas no modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 22:43:43 +00:00
Felipe Coutinho
11b4f8940f feat(landing): aba de insights de IA e screenshots atualizados em webp lossless
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 22:43:40 +00:00
Felipe Coutinho
fba9686fdb feat(dashboard): tendências top 10 e padronização de espaçamento do inbox
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 22:43:38 +00:00
Felipe Coutinho
9b8ac9f71f feat(payers): upload de avatar via arquivo com redimensionamento client-side
- círculo de upload no final da grade de avatares abre seletor de arquivo
- imagem redimensionada para 200×200px via Canvas e salva como base64
- suporte a data URLs em next/image com prop unoptimized
- object-cover adicionado ao componente base Avatar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 22:43:28 +00:00
Felipe Coutinho
fa41c78a39 feat(navbar): copiar user ID ao lado do nome no menu do usuário
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 18:16:18 +00:00
Felipe Coutinho
5f7bfb98da feat(settings): aba de diagnóstico e cópia de user ID no menu do usuário
- Nova aba "Diagnóstico" em Settings com:
  - Identidade: user ID (copiável), nome, e-mail
  - Sessão: criada em / expira em
  - Aplicação: versão, NODE_ENV, build SHA (se definido)
  - Configuração do servidor: S3, e-mail e domínio público — apenas booleans, sem expor credenciais
  - Saúde: status e latência do banco de dados
  - Uso: contagem de lançamentos, anexos, anotações e itens no inbox
- Botão de cópia do user ID no dropdown do avatar (ao lado do e-mail)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 18:06:40 +00:00
Felipe Coutinho
9ecafdb15f docs: atualizar CLAUDE.md e README; adicionar script de instalação Ubuntu
- CLAUDE.md: rota attachments/ adicionada ao mapa de diretórios (app e features);
  seção Response Style substituída por Security Rules
- README.md: instruções para servidor Ubuntu 24.04, preview do Companion,
  seção de Backup; menção ao Companion atualizada
- scripts/install-deps.sh: prepara VPS Ubuntu limpa instalando Docker,
  Node.js 22 e pnpm

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:51:39 +00:00
Felipe Coutinho
e8cc673e52 style(ui): padronizar tipografia — font-medium para font-semibold
Padronização de peso tipográfico em títulos, rótulos de seção,
nomes de entidades e valores monetários em toda a interface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:51:34 +00:00
Felipe Coutinho
3bd8117b65 fix(i18n): corrigir textos "Payer" para "Pagador" em mensagens de erro
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:51:21 +00:00
Felipe Coutinho
a7268d8f05 feat(inbox): redesenho do card de pré-lançamento
Logo maior (40px), nome do app em font-semibold, data em linha
separada e valor monetário em destaque — melhor hierarquia visual.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:51:17 +00:00
Felipe Coutinho
1f9098879e feat(parcelas): redesenho do card de grupo com dialog de detalhes
Card de grupo de parcelas ganhou um dialog ao clicar em "Ver detalhes",
separando parcelas pagas e pendentes, com seleção parcial e logo do
estabelecimento. Substituída lógica de expand inline pelo dialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:51:14 +00:00
Felipe Coutinho
7a3bff52ac feat(dashboard): novos widgets de anexos, inbox e tendências de categoria
- Widget Anexos: resumo de arquivos do período (total, imagens, PDFs, recentes)
- Widget Inbox: snapshot de pré-lançamentos pendentes do Companion
- Widget Tendências de Categoria: redireciona para relatório de tendências
- fetch-dashboard-data: busca attachmentsSnapshot e inboxSnapshot em paralelo
- widgets-config: tipo DashboardWidgetQuickActionOptions centralizado; props
  adminPayerSlug e quickActionOptions adicionadas ao contrato do widget
- dashboard-grid-editable: usa o novo tipo unificado de quickActionOptions
- proxy.ts: frame-src adicionado à CSP para preview de PDFs via S3
- rota /attachments criada com layout próprio

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:51:09 +00:00
Felipe Coutinho
dfb4126b12 feat(lancamentos): filtros de status e anexo; feedback visual de fatura paga
- Novos filtros no drawer: somente pagos, somente não pagos, com anexo
- Filtros de tipo/condição/pagamento agora usam slugs na URL (sem acentos)
- Coluna de liquidação: lançamentos de cartão com fatura paga exibem ícone
  verde com tooltip — diferenciando do estado pendente
- EstabelecimentoInput: popover respeita largura do input ao abrir
- slugify extraído para shared/utils/string.ts
- INVOICE_PAYMENT_CATEGORY_NAME adicionado em categories/constants.ts
- SETTLED_FILTER_VALUES adicionado em transactions/constants.ts
- establishment-logo.tsx removido (não utilizado)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:50:59 +00:00
Felipe Coutinho
ffead579fa feat(fontes): substituir fonte local America por Inter (Google Fonts)
Next.js self-hosta a Inter em build time — elimina os arquivos .woff2
do repositório e a dependência de localFont.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:50:50 +00:00
Felipe Coutinho
aa85cf8b29 fix(docker,s3): corrigir CRLF no entrypoint e região S3 vazia — v2.3.7
- Adicionado .gitattributes com eol=lf para scripts shell e Dockerfile
- Dockerfile: sed -i 's/\r$//' no entrypoint para eliminar CRLF em ambientes Windows/WSL2
- s3-client.ts: substituído ?? por || para tratar string vazia em S3_REGION e demais vars
- CHANGELOG, package.json e lockfile atualizados para v2.3.7

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 17:50:44 +00:00
Felipe Coutinho
9a7ae0fa3d fix(docker): adicionar NODE_PATH no entrypoint para resolução do drizzle-orm
Corrige erro "Cannot find module 'drizzle-orm'" ao rodar migrations no
container — o drizzle-kit em /app/migrate/ não encontrava o módulo sem
NODE_PATH apontando para o node_modules isolado.

Closes #34

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:35:52 +00:00
Felipe Coutinho
98fe6a0f4f Update version badge from 2.3.4 to 2.3.5 2026-04-07 10:53:10 -03:00
Felipe Coutinho
d10eae13e5 Revise versioning and commit message guidelines
Updated versioning instructions to include README.md updates and clarified commit message guidelines.
2026-04-07 10:52:39 -03:00
Felipe Coutinho
43697b4fd2 fix(csp): mover CSP para proxy.ts para leitura em runtime
Content-Security-Policy estava em next.config.ts (build time),
então S3_ENDPOINT nunca era incluído no connect-src ao buildar
via Docker no CI. Movido para proxy.ts que avalia em runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 13:49:23 +00:00
Felipe Coutinho
27e3ba5f0d Update version badge from 2.1.2 to 2.3.4 2026-04-05 20:33:55 -03:00
Felipe Coutinho
31485eec8f fix(csp): permitir upload de anexos para o storage externo
connect-src bloqueava fetch para o Supabase Storage desde o commit
de segurança (10afef9). Adiciona a origin do S3_ENDPOINT na política.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:47:23 +00:00
Felipe Coutinho
3be64aa8d0 fix(auth): corrigir verify e unificar tokens com prefixo opm_
- Corrige /api/auth/device/verify que rejeitava tokens criados via
  Settings (revertido de JWT para hash lookup)
- Renomeia prefixo de tokens de os_ para opm_ (OpenMonetis)
- Remove rotas JWT não utilizadas (token, refresh)
- Simplifica api-token.ts mantendo apenas hashToken e extractBearerToken

BREAKING CHANGE: tokens existentes com prefixo os_ param de funcionar.
Revogar e recriar tokens após o deploy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 00:05:03 +00:00
Felipe Coutinho
85f6dcfc22 fix(csp): permitir unsafe-eval apenas em desenvolvimento
React precisa de eval() em dev para reconstruir stack traces.
Produção continua sem unsafe-eval.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:21:03 +00:00
Felipe Coutinho
df996df93d fix(segurança): substituir xlsx por exceljs (CVEs sem patch no npm)
xlsx@0.18.5 tem Prototype Pollution e ReDoS sem versão corrigida no
npm. Migrado para exceljs@4.4.0 nos 4 pontos de uso: parser de
importação, geração de template, exportação de lançamentos e
exportação de relatório de categorias.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:12:04 +00:00
Felipe Coutinho
10afef9fec fix(segurança): corrigir 10 vulnerabilidades do relatório de segurança
- tokens: remover aceite de expiresAt NULL e forçar TTL de 1 ano
- tokens: corrigir refresh que invalidava access token anterior
- xlsx: desabilitar parsing de fórmulas (CVE-2024-44294)
- csp: expandir Content-Security-Policy com origens explícitas
- headers: adicionar Referrer-Policy e X-Permitted-Cross-Domain-Policies
- api: retornar 401 JSON em vez de redirect 302 em rotas autenticadas
- health: remover version disclosure do /api/health
- robots.txt: simplificar para não expor rotas internas
- sitemap: corrigir URL com protocolo duplicado
- criar security.txt (RFC 9116)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 02:47:05 +00:00
Felipe Coutinho
fd4d90a53e ci: forçar Node.js 24 nas actions do workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:52:52 +00:00
Felipe Coutinho
a24406271c chore: corrigir formatacao do package.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:51:14 +00:00
Felipe Coutinho
a09942e3d8 chore(release): preparar changelog da versão 2.3.1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:46:47 +00:00
Felipe Coutinho
96febd5904 fix(docker): separar deps drizzle do node_modules standalone
O pnpm install no Stage 3 sobrescrevia o node_modules copiado do
.next/standalone, removendo o modulo next e quebrando o startup.

Agora as deps do drizzle-kit sao instaladas em /app/migrate/ antes
de copiar o standalone, mantendo os dois node_modules isolados.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:45:48 +00:00
Felipe Coutinho
c3cfbc878c fix(tipografia): ajustar display da fonte america 2026-04-03 18:11:56 +00:00
Felipe Coutinho
55bbfabe9f chore(release): preparar changelog da versão 2.3.0 2026-04-03 18:11:34 +00:00
Felipe Coutinho
f5cdae4853 fix(ui): remover avisos visuais e destacar atualizações 2026-04-03 18:11:30 +00:00
Felipe Coutinho
5c4995961c refactor(lista): componentizar inbox e tabela de lançamentos 2026-04-03 18:10:58 +00:00
Felipe Coutinho
1b4dfaaba7 fix(lançamentos): reforçar validações e revisar formulário 2026-04-03 18:10:50 +00:00
Felipe Coutinho
549a5bdba1 fix(financeiro): alinhar saldo, métricas e relatórios 2026-04-03 18:10:43 +00:00
Felipe Coutinho
acaf9d5c27 feat(dados-client): adotar react query em leituras do app 2026-04-03 18:10:34 +00:00
Felipe Coutinho
e4c6a91350 fix(segurança): endurecer autenticação e rotas privadas 2026-04-03 18:10:23 +00:00
Felipe Coutinho
ba369e8a83 chore(infra): atualizar build, docker e tooling 2026-04-03 18:10:16 +00:00
Felipe Coutinho
d01bc8a669 fix(docker): remove chown recursivo da imagem final 2026-04-01 17:15:06 +00:00
Felipe Coutinho
e024e0d54e fix(docker): cria pasta public antes do pnpm install
O postinstall do pdfjs-dist tenta copiar pdf.worker.min.mjs para
public/, mas no stage deps do Dockerfile a pasta não existia.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:49:00 +00:00
Felipe Coutinho
c44089169f style: troca subpixel-antialiased por antialiased no body
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:35:16 +00:00
Felipe Coutinho
d04e30e3c9 fix(robots): remove estático duplicado, corrige robots.ts e llms.txt
Remove `public/robots.txt` que era ignorado pelo Next.js em favor do
`src/app/robots.ts` (Metadata API). Adiciona `/signup` à lista de
rotas bloqueadas e remove referência ao `sitemap.xml` inexistente.

Restaura o link `/robots.txt` no `llms.txt`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:31:37 +00:00
Felipe Coutinho
229b6c5bc0 chore: adiciona robots.txt bloqueando rotas privadas do app
Permite indexação apenas da landing page e do llms.txt. Bloqueia todas
as rotas autenticadas (dashboard, transações, contas, cartões, etc.)
e a API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:29:39 +00:00
Felipe Coutinho
c3b133d8d9 docs(llms.txt): adiciona stack técnico e remove links inexistentes
Inclui stack no cabeçalho do arquivo e remove referências a AGENTS.md,
robots.txt e sitemap.xml que não existem no repositório.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:24:04 +00:00
Felipe Coutinho
e9a2ab1782 chore(release): publicar versão 2.2.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:18:55 +00:00
Felipe Coutinho
c7d6e23398 chore: atualiza biome, CLAUDE.md, llms.txt e corrige optional chaining
- biome.json: schema atualizado para 2.4.9
- public/llms.txt: novo arquivo de documentação pública do projeto
- CLAUDE.md: ajustes menores de documentação interna
- invoices-queries.ts: usa optional chaining `?.startsWith` no lugar de
  verificação dupla de nullish
- CHANGELOG.md: documentadas as mudanças do ciclo atual em [Unreleased]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:15:03 +00:00
Felipe Coutinho
0514efb1c4 style(tipografia): adiciona fonte America Medium e padroniza pesos de texto
Adiciona os arquivos `america-medium.woff2` e `america-bold.woff2` e
registra o weight 500 no `font_index.ts`.

Padroniza o uso de `font-medium` em substituição a `font-semibold` e
`font-bold` em títulos, valores monetários e rótulos de destaque em
todos os componentes do app, landing page e componentes de UI base.

`Card` ganha `hover:border-primary/40` e `CardTitle` recebe `text-base`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:14:55 +00:00
Felipe Coutinho
e32fb85006 perf(cache): migração para diretiva use cache do Next.js
Todas as queries cacheadas do dashboard migram de `unstable_cache` para
a diretiva `use cache` com `cacheTag` e `cacheLife({ revalidate: 3 })`.

Todas as páginas e o layout do dashboard passam a chamar `connection()`
para garantir renderização dinâmica. O root layout envolve os filhos em
`<Suspense>`. `next.config.ts` remove `turbopackFileSystemCacheForDev`
e adota `cacheComponents: true`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:14:23 +00:00
Felipe Coutinho
96df6a1798 feat(notificações): alertas de vencimento para o período seguinte
Boletos e faturas do próximo período com vencimento dentro de 5 dias
agora geram notificações do tipo `due_soon`, evitando duplicatas com
notificações já existentes do período corrente.

A query de boletos passa a filtrar pela data de vencimento não nula e
limita a janela de busca a 12 meses anteriores ao período atual.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:14:07 +00:00
Felipe Coutinho
1f8a97bd16 feat(auth): redesign visual das páginas de autenticação
O sidebar de autenticação ganha mockup animado de faturas e três itens
de funcionalidade no rodapé, substituindo o texto descritivo anterior.

As páginas de login e cadastro recebem gradiente decorativo de fundo e
exibem o logo no topo em viewports mobile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:14:01 +00:00
Felipe Coutinho
0ab3298cef feat(anexos): página de galeria de comprovantes e documentos
Adiciona rota `/attachments` com visualização de todos os anexos do
usuário em grade, visualização inline de imagem e PDF, navegação entre
arquivos do mesmo lançamento e download direto.

Inclui também:
- API REST em `/api/attachments` para servir os arquivos
- Actions `fetch-by-id` e `fetch-dialog-options` em transactions
- Item "Anexos" adicionado à navbar
- `formatBytes` extraído para `src/shared/utils/number.ts`
- Migrations de banco atualizadas
- Fix: uploads e remoções de anexo agora funcionam para todos os
  lançamentos, não apenas os pertencentes a séries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:13:54 +00:00
Felipe Coutinho
cad41680eb feat(pdf): adiciona suporte a visualização de PDF nos anexos
Inclui `pdfjs-dist` como dependência e configura o script `postinstall`
para copiar o web worker necessário para `public/pdf.worker.min.mjs`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:13:39 +00:00
Felipe Coutinho
3b00f328c5 Update version badge to 2.1.2 2026-03-30 15:49:54 -03:00
Felipe Coutinho
20d0c3e0a7 chore(docs): atualizar regra de versionamento no CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:47:56 +00:00
Felipe Coutinho
71b5a004e3 chore: ajustes de formatação e configuração
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:47:27 +00:00
Felipe Coutinho
65b1506d75 chore(release): publicar versão 2.1.2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:47:23 +00:00
Felipe Coutinho
2a458d5a3c chore(configurações): redesign visual da página de configurações
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:47:19 +00:00
Felipe Coutinho
f418987f47 feat(lançamentos): escopo "period" na ação em lote e correção do fluxo de anexos em séries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:46:33 +00:00
Felipe Coutinho
59b4dea071 feat(preferências): configuração de tamanho máximo de anexo por arquivo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:46:28 +00:00
Felipe Coutinho
6ce132fe0c feat(db): adicionar coluna attachmentMaxSizeMb em userPreferences
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:45:41 +00:00
Felipe Coutinho
49731238e4 Update version badge from 2.1.0 to 2.1.1 2026-03-29 11:14:23 -03:00
Felipe Coutinho
c5df97f7aa chore(setup): reduzir logo e colocar nome ASCII lado a lado
Logo reduzido de 19 para 10 linhas (seleção dos frames-chave).
Nome do projeto em ASCII art posicionado ao lado direito do logo,
centralizado verticalmente. Tagline abaixo do bloco.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 14:05:53 +00:00
Felipe Coutinho
3476fda4db chore(setup): adicionar banner ASCII do logo e corrigir script db:extensions
Substitui o header simples pelo logo em ASCII art na cor primária
(laranja) com nome e tagline centralizados. Corrige chamada
db:enableExtensions → db:extensions após renomeio do script.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:55:26 +00:00
Felipe Coutinho
519b673ae5 chore(release): publicar versão 2.1.1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:44:21 +00:00
Felipe Coutinho
303b8bedd4 chore(config): limpeza de tsconfig.json e .vscode/settings.json
Reformata arrays no tsconfig para multi-line. Remove configurações
obsoletas do .vscode (explorerExclude.backup, eslint.enable,
typescript.preferences.organizeImportsCollation).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:44:17 +00:00
Felipe Coutinho
f2b9b16896 chore(package): renomear scripts e remover dependências Vercel
Renomeia mockup→db:seed, db:enableExtensions→db:extensions e remove
o script dev-env. Remove @vercel/analytics e @vercel/speed-insights.
Atualiza README com o novo nome do script.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:44:13 +00:00
Felipe Coutinho
6eba35542b chore(logo): remover prop showVersion e atualizar logo_small.png
Remove a prop showVersion do componente Logo e seu uso na sidebar.
Aplica iconFilterClass também no variant compact. Atualiza a imagem
logo_small.png.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:44:10 +00:00
Felipe Coutinho
f5e95ffba6 chore(analytics): substituir Vercel Analytics por Umami self-hosted
Remove @vercel/analytics e @vercel/speed-insights e adiciona o script
do Umami self-hosted no layout raiz, restrito ao domínio openmonetis.com.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:44:03 +00:00
Felipe Coutinho
a75bb86eec refactor(navbar): extrair NavbarShell e adicionar variante navbar no Button
Unifica a estrutura da navbar entre o app e a landing page via novo
componente NavbarShell. Centraliza estilos de botões da navbar na
variante `navbar` do Button, eliminando nav-styles.ts e as classes
inline duplicadas. AnimatedThemeToggler, RefreshPageButton e MobileNav
passam a aceitar prop `variant` para adaptar ao contexto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:43:59 +00:00
Felipe Coutinho
a3b858621f fix(transactions): preservar período salvo ao editar lançamento de cartão
No modal de edição, o período não era recalculado com base no fechamento
do cartão, garantindo que o valor salvo no banco seja sempre exibido.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 17:51:43 +00:00
Felipe Coutinho
fee2a2c9f5 fix(build): corrigir erros de tipo introduzidos pelo TypeScript 6.0
- Adiciona src/global.d.ts com declare module '*.css' para suportar
  side-effect imports de CSS com moduleResolution bundler
- Adiciona ignoreDeprecations "6.0" no tsconfig para silenciar aviso
  de depreciação do baseUrl (será removido no TS 7)
- Corrige cast de .message em better-auth 1.5.6, cujo tipo passou a
  ser string | RawError em chamadas de passkey

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:21:56 +00:00
Felipe Coutinho
839d7d0866 chore(release): publicar versão 2.1.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:13:47 +00:00
Felipe Coutinho
7cd7d95245 docs: atualizar README, .env.example e CLAUDE.md para a versão 2.1.0
Documenta variáveis S3 opcionais, instruções de self-hosting com anexos
e padrão de commit messages no guia do projeto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:13:44 +00:00
Felipe Coutinho
9bd762f7a3 chore(db): reorganizar migrations e adicionar tabelas de anexos
Consolida migrations anteriores e adiciona tabelas `anexos` e
`lancamento_anexos` com constraints de integridade referencial.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:13:39 +00:00
Felipe Coutinho
9b76db4ce9 chore(deps): adicionar AWS SDK S3 e atualizar dependências
Adiciona @aws-sdk/client-s3 e @aws-sdk/s3-request-presigner para
suporte a anexos; atualiza ai-sdk, better-auth, drizzle-orm, recharts,
biome e typescript para versões mais recentes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:13:30 +00:00
Felipe Coutinho
91457b6490 chore(ci): adicionar workflow de release automático
Cria tag e GitHub Release a partir da versão do package.json e da
entrada correspondente no CHANGELOG.md ao fazer push na branch main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:13:26 +00:00
Felipe Coutinho
a0a71623d7 fix(ui): corrigir overflow do dialog e ícone de anexo nas categorias
Adiciona min-w-0 e overflow-x-hidden no DialogContent para evitar
expansão indevida; corrige referência do ícone RiAttachment2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:13:13 +00:00
Felipe Coutinho
00e624b8bc fix(lancamentos): bloquear criação em fatura já paga no cartão de crédito
Evita divergência no relatório de análise de parcelas ao impedir o
cadastro de lançamentos em períodos cujas faturas já foram quitadas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:13:10 +00:00
Felipe Coutinho
f82043127a feat(lancamentos): adicionar suporte a anexos com upload para storage S3
Permite vincular arquivos (PDF, imagens) a lançamentos via upload direto
para storage compatível com S3, usando token assinado por arquivo e
validação de propriedade na leitura e remoção.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:13:05 +00:00
Felipe Coutinho
32da4f906e fix(transactions): avoid crypto.randomUUID on initial load 2026-03-26 14:18:47 +00:00
Felipe Coutinho
0bd9d0ac47 chore(docker): simplify compose file for public self-hosting
Remove build step (use published image), strip verbose comments,
and inline pgcrypto init instead of external script.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 19:16:56 +00:00
Felipe Coutinho
9f45fd1ecd chore(release): publish 2.0.2 2026-03-25 00:31:16 +00:00
Felipe Coutinho
f528e75ee1 style(ui): polish dashboard chrome and landing copy 2026-03-25 00:30:55 +00:00
Felipe Coutinho
da32b41bbc chore(tooling): add mockup helper and backup fixes 2026-03-25 00:30:46 +00:00
Felipe Coutinho
1e0c93fb6c fix(finance): preserve visibility and settlement updates 2026-03-25 00:29:36 +00:00
Felipe Coutinho
5f70421f5a feat(dashboard): persist notification center state 2026-03-25 00:29:24 +00:00
Felipe Coutinho
50477fb1be fix(inbox): corrigir agrupamento de data por fuso de Brasilia
O Companion envia hora local com 'Z' literal (nao converte para UTC),
entao o timestamp no DB ja carrega a data correta de Brasilia. Usava-se
+3h no frontend, que deslocava a virada de dia para as 21h locais e
fazia compras da tarde aparecerem como 'Ontem'.

- getItemDateKey: remove offset (data UTC ja e a data de Brasilia)
- getBrasiliaDateKey: usa UTC-3 apenas para calcular hoje/ontem
- Paraleliza insercoes no batch endpoint com Promise.allSettled
- Usa selectDistinct no fetchInboxSourceApps
- Envolve InboxCard em memo e callbacks em useCallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:55:46 +00:00
646 changed files with 61003 additions and 16497 deletions

View File

@@ -3,10 +3,10 @@
# ============================================
# === Database ===
# PostgreSQL local (Docker): use host "db"
# PostgreSQL local (sem Docker): use host "localhost"
# Desenvolvimento local (pnpm dev): use host "localhost" (padrão abaixo)
# Docker Compose completo: o compose.yml define DATABASE_URL automaticamente com host "db"
# PostgreSQL remoto: use URL completa do provider
DATABASE_URL=postgresql://openmonetis:openmonetis_dev_password@db:5432/openmonetis_db
DATABASE_URL=postgresql://openmonetis:openmonetis_dev_password@localhost:5432/openmonetis_db
# Credenciais do PostgreSQL (apenas para Docker local) - Alterar
POSTGRES_USER=openmonetis
@@ -18,10 +18,29 @@ POSTGRES_DB=openmonetis_db
BETTER_AUTH_SECRET=your-secret-key-here-change-this
BETTER_AUTH_URL=http://localhost:3000
# Origins adicionais confiáveis para o Better Auth.
# Útil para Cloudflare Tunnel, reverse proxy e URLs diferentes de BETTER_AUTH_URL.
# Separe múltiplas origins por vírgula.
# Exemplo: https://*.trycloudflare.com,https://openmonetis.seudominio.com
BETTER_AUTH_TRUSTED_ORIGINS=
# Defina como true para bloquear novos cadastros
DISABLE_SIGNUP=false
# Duração de sessões persistentes quando "Manter conectado" estiver marcado
AUTH_SESSION_EXPIRES_IN_DAYS=30
AUTH_SESSION_UPDATE_AGE_HOURS=24
# === Portas ===
APP_PORT=3000
DB_PORT=5432
# === S3 Server (Opcional) ===
S3_ENDPOINT=
S3_REGION=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET=
# === Email (Opcional) ===
# Provider: Resend (https://resend.com)
RESEND_API_KEY=
@@ -37,8 +56,22 @@ GOOGLE_CLIENT_SECRET=
# Se não definido, todas as rotas ficam acessíveis.
# PUBLIC_DOMAIN=openmonetis.com
# === Analytics (Opcional) ===
# Umami: https://umami.is — self-hosted ou cloud
UMAMI_URL=
UMAMI_WEBSITE_ID=
UMAMI_DOMAINS=
# === AI Providers (Opcional) ===
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
GOOGLE_GENERATIVE_AI_API_KEY=
OPENROUTER_API_KEY=
MINIMAX_API_KEY=
OPENROUTER_API_KEY=
OLLAMA_BASE_URL=http://127.0.0.1:11434/v1
OLLAMA_API_KEY=
# === Logo.dev (Opcional) ===
# Logos automáticos de estabelecimentos. Cadastre em https://www.logo.dev
LOGO_DEV_TOKEN=
LOGO_DEV_SECRET_KEY=

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
# Força LF para arquivos que precisam de line endings Unix no container
*.sh text eol=lf
docker-entrypoint.sh text eol=lf
Dockerfile text eol=lf

View File

@@ -1,58 +0,0 @@
# AI Coding Assistant Instructions for OpenMonetis
## Project Overview
OpenMonetis is a self-hosted personal finance management application built with Next.js 16, TypeScript, PostgreSQL, and Drizzle ORM. It provides manual transaction tracking, account management, budgeting, and financial insights with a Portuguese interface.
## Architecture
- **Frontend**: Next.js App Router with React 19, shadcn/ui components, Tailwind CSS
- **Backend**: Server actions in Next.js, API routes for auth/health
- **Database**: PostgreSQL with Drizzle ORM, schema in `db/schema.ts`
- **Auth**: Better Auth (OAuth + email magic links)
- **Deployment**: Docker multi-stage build, health checks
## Key Patterns
- **Server Actions**: Use `"use server"` for mutations, validate with Zod schemas, handle errors with `handleActionError`
- **Database Queries**: Use Drizzle's query API with relations, e.g., `db.query.lancamentos.findMany({ with: { categoria: true } })`
- **Authentication**: Import from `lib/auth/server`, redirect on failure
- **Revalidation**: Call `revalidateForEntity("lancamentos")` after mutations
- **Portuguese Naming**: DB fields like `nome`, `tipo_conta`, `pagador` (payer), `lancamento` (transaction)
- **Component Structure**: Feature-based folders in `components/`, shared UI in `components/ui/`
## Development Workflow
- **Start Dev**: `pnpm dev` (Turbopack), `docker compose up db -d` for DB
- **Database**: `pnpm db:push` to sync schema, `pnpm db:studio` for visual editor
- **Build**: `pnpm build`, `pnpm start` for production
- **Docker**: `pnpm docker:up` for full stack, `pnpm docker:logs` for monitoring
## Common Tasks
- **Add Transaction**: Create server action in `app/(dashboard)/lancamentos/actions.ts`, validate with Zod, insert via Drizzle
- **New Entity**: Add to `db/schema.ts`, define relations, create CRUD actions in `lib/[entity]/actions.ts`
- **UI Component**: Use shadcn/ui, place in `components/[feature]/`, export from `components/ui/`
- **API Route**: Add to `app/api/`, use `getUserSession()` for auth
## Conventions
- **Imports**: Absolute paths with `@/`, group by external/internal
- **Error Handling**: Return `{ success: false, error: string }` from actions
- **Currency**: Store as decimal strings (e.g., "123.45"), convert to cents for calculations
- **Periods**: Format as "YYYY-MM", use `parsePeriodParam()` for URL params
- **Notifications**: Send emails via `sendPagadorAutoEmails()` for payer updates
## External Integrations
- **Better Auth**: Config in `lib/auth/config.ts`, session handling
- **Drizzle**: Migrations in `drizzle/`, studio at `pnpm db:studio`
- **AI Features**: Use `@ai-sdk/*` for insights, configured in environment
- **Email**: Resend for notifications, configured via `RESEND_API_KEY`
## File Examples
- Schema: `db/schema.ts` (relations, indexes)
- Actions: `app/(dashboard)/lancamentos/actions.ts` (CRUD with validation)
- Components: `components/lancamentos/page/lancamentos-page.tsx` (client component)
- Utils: `lib/lancamentos/page-helpers.ts` (data transformation)

49
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate Next.js types
run: pnpm exec next typegen
- name: Typecheck
run: pnpm exec tsc --noEmit
- name: Lint
run: pnpm run lint
- name: Build application
run: pnpm run build

View File

@@ -1,65 +0,0 @@
name: Build and Push to Docker Hub
on:
push:
branches:
- main
tags:
- 'v*.*.*'
pull_request:
branches:
- main
workflow_dispatch:
env:
DOCKER_IMAGE_NAME: openmonetis
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
- name: Image digest
run: echo ${{ steps.meta.outputs.digest }}

152
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,152 @@
name: Release
on:
push:
tags:
- "v*.*.*"
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
DOCKER_IMAGE_NAME: openmonetis
permissions:
contents: read
jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Validate release version
shell: bash
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
PACKAGE_VERSION="$(jq -r '.version' package.json)"
if [[ ! "$TAG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "A tag $GITHUB_REF_NAME não segue o formato vX.Y.Z."
exit 1
fi
if [[ "$TAG_VERSION" != "$PACKAGE_VERSION" ]]; then
echo "A tag $GITHUB_REF_NAME não corresponde à versão $PACKAGE_VERSION do package.json."
exit 1
fi
if ! grep -Fq "## [$TAG_VERSION]" CHANGELOG.md; then
echo "A versão $TAG_VERSION não foi encontrada no CHANGELOG.md."
exit 1
fi
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate Next.js types
run: pnpm exec next typegen
- name: Typecheck
run: pnpm exec tsc --noEmit
- name: Lint
run: pnpm run lint
docker:
runs-on: ubuntu-latest
needs: quality
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
- name: Image digest
run: echo "${{ steps.build.outputs.digest }}"
github-release:
runs-on: ubuntu-latest
needs: docker
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Extract changelog for this version
id: changelog
shell: bash
run: |
VERSION="${GITHUB_REF_NAME#v}"
NOTES=$(awk -v version="$VERSION" '
index($0, "## [" version "]") == 1 { found=1; next }
found && /^## \[/ { exit }
found && !started && /^[[:space:]]*$/ { next }
found { started=1; print }
' CHANGELOG.md)
if [[ -z "$NOTES" ]]; then
echo "Não foi possível extrair as notas da versão $VERSION."
exit 1
fi
{
echo "notes<<EOF"
echo "$NOTES"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body: ${{ steps.changelog.outputs.notes }}
draft: false
prerelease: false

2
.gitignore vendored
View File

@@ -105,7 +105,7 @@ docker-compose.override.yml
.gemini/
.cursor/
QWEN.md
AGENTS.md
.codex
# === Backups locais ===
/backup/

35
.vscode/settings.json vendored
View File

@@ -1,35 +0,0 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/node_modules": true,
"node_modules": true,
"**/.vscode": true,
".vscode": true,
"**/.next": true,
".next": true
},
"explorerExclude.backup": {},
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "never",
"source.organizeImports.biome": "always",
"source.fixAll": "never",
"source.fixAll.biome": "always",
"source.fixAll.eslint": "never"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"eslint.enable": false,
"prettier.enable": false,
"typescript.preferences.organizeImportsCollation": "ordinal",
"editor.fontSize": 15,
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
}

366
AGENTS.md Normal file
View File

@@ -0,0 +1,366 @@
# AGENTS.md - OpenMonetis
> Self-hosted personal finance app (Next.js 16, React 19, PostgreSQL, Drizzle ORM, Better Auth, Tailwind 4, shadcn/ui).
> Portuguese UI, English folders/imports. Linter: Biome 2.x. Package manager: pnpm.
## Related Projects
- **OpenMonetis Companion** (`~/github/openmonetis-companion`): Android app que captura notificacoes de apps bancarios e envia para o OpenMonetis via API. Os itens chegam na feature `inbox` para revisao.
---
## Critical Rules
1. **Sempre filtrar por `userId`** em queries.
2. **Usar `getAdminPayerId(userId)`** de `src/shared/lib/payers/get-admin-id.ts` ao inves de JOIN com `payers` para descobrir o admin.
3. **Periods** usam formato `YYYY-MM` (ex: `"2025-11"`). Utils em `src/shared/utils/period/`.
4. **Moeda**: R$ com 2 decimais. DB: `numeric(12, 2)`. Utils em `src/shared/utils/currency.ts`.
5. **Revalidation**: usar `revalidateForEntity("entity")` de `src/shared/lib/actions/helpers.ts` apos mutations.
6. **Versionamento e publicação**: registrar mudancas no `CHANGELOG.md` seguindo Keep a Changelog, também alterar o `package.json` e o badge de versão do `README.md`. Cada versão deve ter um parágrafo introdutório em linguagem humana logo abaixo do cabeçalho `## [x.y.z]`, antes das seções `### Adicionado/Alterado/Removido` — descrevendo em prosa o que a versão representa (ex: "Esta versão foca em polimento visual e reorganização interna..."). A `main` executa somente a CI; imagens Docker e GitHub Releases são publicadas exclusivamente por tags SemVer no formato `vX.Y.Z`. Antes de criar a tag, confirmar que a CI da `main` passou e que tag, `package.json`, `CHANGELOG.md` e badge do `README.md` usam a mesma versão. A tag deve apontar para o commit validado. Criar ou enviar uma tag dispara publicação externa (`X.Y.Z`, `X.Y`, `X` e `latest` no Docker Hub, seguida da GitHub Release), portanto agentes nunca devem criar ou fazer push de tags sem autorização explícita do usuário. Quando o usuário pedir **"commit e push"** em uma mudança que prepara uma nova versão, isso conta como autorização explícita para executar o fluxo completo: commitar, enviar a `main`, aguardar a CI da `main` passar, criar a tag SemVer correspondente à versão preparada e enviar essa tag. Se não houver versão preparada ou se houver divergência entre `package.json`, `CHANGELOG.md`, badge do `README.md` e tag pretendida, parar e pedir confirmação. Não voltar a publicar `latest` diretamente de pushes na `main`.
7. **Comunicacao**: responder em portugues clara e direta com o time.
8. **Commit messages**: agrupar por natureza. em pt-br. seguindo o padrao do sistema.
9. **README.md**: sempre que fizer alteracoes significativas, atualize o README.md.
---
## Architecture
### Feature-First
- `src/app/`: roteamento, layouts, loading states e paginas finas
- `src/features/`: codigo de dominio por feature
- `src/shared/`: tudo que e genuinamente reutilizado entre features
- `src/db/`: schema do banco
### Regra Feature vs Shared
Use esta pergunta:
> Se eu deletar esta feature, este arquivo deveria sumir junto?
- Sim: vai para `src/features/<feature>/`
- Nao: vai para `src/shared/`
### Features nao importam outras features
Se um contrato cruza dominios, ele deve morar em `src/shared/`.
**Excecao intencional: `attachments` depende de `transactions`**
`src/features/attachments` importa `TransactionDialog`, `TransactionDetailsDialog` e `TransactionItem` diretamente de `src/features/transactions`. Isso e uma dependencia explicita e aceita: anexos sao semanticamente uma extensao de lancamentos — existem por causa deles e nao fazem sentido sem esse contexto. Mover esses componentes para `shared/` seria errado (eles pertencem a transactions). Nao tratar isso como bug a corrigir.
Exemplos comuns:
- auth: `src/shared/lib/auth/*`
- db: `src/shared/lib/db.ts`
- revalidation helpers: `src/shared/lib/actions/*`
- payers cross-domain helpers: `src/shared/lib/payers/*`
- period/currency/date: `src/shared/utils/*`
- shadcn/ui: `src/shared/components/ui/*`
---
## Directory Structure
```text
src/
├── app/
│ ├── (auth)/
│ │ ├── login/page.tsx
│ │ └── signup/page.tsx
│ ├── (dashboard)/
│ │ ├── dashboard/
│ │ ├── transactions/
│ │ ├── cards/
│ │ │ └── [cardId]/invoice/
│ │ ├── accounts/
│ │ │ └── [accountId]/statement/
│ │ ├── categories/
│ │ │ ├── [categoryId]/
│ │ │ └── history/
│ │ ├── budgets/
│ │ ├── payers/
│ │ │ └── [payerId]/
│ │ ├── notes/
│ │ ├── insights/
│ │ ├── calendar/
│ │ ├── inbox/
│ │ ├── attachments/
│ │ ├── changelog/
│ │ ├── reports/
│ │ │ ├── category-trends/
│ │ │ ├── card-usage/
│ │ │ ├── installment-analysis/
│ │ │ └── establishments/
│ │ └── settings/
│ ├── (landing-page)/
│ ├── api/
│ ├── globals.css
│ └── layout.tsx
├── features/ # cada feature segue: actions.ts, queries.ts, actions/, components/, hooks/, lib/
│ ├── auth/
│ ├── landing/
│ ├── dashboard/
│ ├── transactions/
│ ├── cards/
│ ├── invoices/
│ ├── accounts/
│ ├── categories/
│ ├── budgets/
│ ├── payers/
│ ├── notes/
│ ├── insights/
│ ├── calendar/
│ ├── inbox/
│ ├── attachments/
│ ├── reports/
│ └── settings/
├── shared/
│ ├── components/
│ │ ├── ui/ # shadcn/ui primitives
│ │ ├── navigation/ # navbar, sidebar, breadcrumbs
│ │ ├── providers/ # React context providers
│ │ ├── brand/ # logos do app (logo, logo-icon, logo-text)
│ │ ├── widgets/ # widget-card, widget-empty-state, expandable-widget-card
│ │ ├── feedback/ # empty-state, status-dot, payment-success
│ │ ├── month-picker/
│ │ ├── logo-picker/
│ │ ├── calculator/
│ │ ├── entity-avatar/
│ │ └── skeletons/
│ ├── hooks/
│ ├── lib/
│ │ ├── actions/
│ │ ├── auth/
│ │ ├── accounts/
│ │ ├── cards/
│ │ ├── calculator/
│ │ ├── categories/
│ │ ├── email/
│ │ ├── import/
│ │ ├── installments/
│ │ ├── invoices/
│ │ ├── logo/
│ │ ├── notifications/
│ │ ├── payers/
│ │ ├── schemas/
│ │ ├── storage/
│ │ ├── transfers/
│ │ ├── types/
│ │ ├── version/
│ │ └── db.ts
│ └── utils/
│ ├── period/
│ ├── calculator.ts
│ ├── calendar.ts
│ ├── category-colors.ts
│ ├── currency.ts
│ ├── date.ts
│ ├── export-branding.ts
│ ├── fetch-json.ts
│ ├── financial-dates.ts
│ ├── icons.tsx
│ ├── id.ts
│ ├── initials.ts
│ ├── math.ts
│ ├── number.ts
│ ├── percentage.ts
│ ├── string.ts
│ └── ui.ts
└── db/
└── schema.ts
```
### Estrutura interna padrão de uma feature
Toda feature em `src/features/<nome>/` segue:
```text
<feature>/
├── actions.ts # entry point de Server Actions (barrel quando há actions/)
├── queries.ts # entry point de leitura do banco
├── actions/ # (opcional) Server Actions divididas por domínio quando o volume cresce
├── components/ # componentes de UI da feature
├── hooks/ # React hooks específicos da feature
└── lib/ # helpers, types, sub-queries e constantes internas
```
`actions.ts` e `queries.ts` são as portas de entrada da feature. Tudo que é helper interno fica em `lib/`. Componentes e hooks ficam nas pastas com nome óbvio.
---
## Import Patterns
### Preferidos
```ts
import { getUser } from "@/shared/lib/auth/server";
import { revalidateForEntity } from "@/shared/lib/actions/helpers";
import { parsePeriodParam } from "@/shared/utils/period";
import { TransactionsPage } from "@/features/transactions/components/page/transactions-page";
import { fetchLancamentos } from "@/features/transactions/queries";
```
### Evitar
```ts
import { Something } from "@/components/...";
import { Something } from "@/lib/...";
import { something } from "@/app/(dashboard)/...";
```
---
## App Router Pattern
Paginas em `src/app/` devem ser finas:
```ts
import { getUser } from "@/shared/lib/auth/server";
import { TransactionsPage } from "@/features/transactions/components/page/transactions-page";
import { fetchLancamentos } from "@/features/transactions/queries";
export default async function Page() {
const user = await getUser();
const data = await fetchLancamentos([/* filters */]);
return <TransactionsPage {...data} />;
}
```
Layouts, `loading.tsx` e metadata continuam em `src/app/`.
---
## Naming
### Routes / folders
| Portugues | English |
|---|---|
| `lancamentos` | `transactions` |
| `cartoes` | `cards` |
| `contas` | `accounts` |
| `categorias` | `categories` |
| `orcamentos` | `budgets` |
| `pessoas` | `payers` |
> **Nota:** o conceito de "pagador" foi renomeado para **"pessoa"** na UI (labels, toasts, textos visíveis ao usuário). O código, rotas e schema continuam usando o termo original em inglês (`payer`, `payerId`, `adminPayerId`) e em português interno (`pagador` como variável). Não renomear esses identificadores — a divergência entre UI e código é intencional e documentada.
| `anotacoes` | `notes` |
| `calendario` | `calendar` |
| `ajustes` | `settings` |
| `pre-lancamentos` | `inbox` |
| `relatorios/tendencias` | `reports/category-trends` |
| `relatorios/uso-cartoes` | `reports/card-usage` |
| `relatorios/analise-parcelas` | `reports/installment-analysis` |
| `relatorios/estabelecimentos` | `reports/establishments` |
| `contas/[contaId]/extrato` | `accounts/[accountId]/statement` |
| `cartoes/[cartaoId]/fatura` | `cards/[cardId]/invoice` |
| `categorias/historico` | `categories/history` |
| `changelog` | `settings/changelog` |
### Files
- preferir `kebab-case`
- preferir nomes em ingles
- manter nomes internos de tipos/funcoes somente quando a troca aumentar risco sem ganho real
---
## Commands
```bash
pnpm run dev
pnpm run build
pnpm run lint
pnpm run lint:fix
pnpm exec next typegen
pnpm exec tsc --noEmit
pnpm run db:generate
pnpm run db:push
pnpm run db:studio
pnpm run docker:up:db
```
---
## Revalidation
Arquivo: `src/shared/lib/actions/helpers.ts`
- atualizar sempre os paths em ingles
- lembrar de manter a tag `"dashboard"` para invalidacoes financeiras
---
## Auth
- `getUser()` / `getUserId()` em `src/shared/lib/auth/server.ts`
- sessao deduplicada por request com `React.cache()`
---
## Dashboard Fetcher
Padrao recomendado:
```ts
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
export async function fetchData(userId: string, period: string) {
const adminPayerId = await getAdminPayerId(userId);
if (!adminPayerId) return [];
return db.query.transactions.findMany({
where: /* sempre com userId + adminPayerId + period */,
});
}
```
---
## New Feature Checklist
1. Criar a rota fina em `src/app/(dashboard)/<feature>/page.tsx`
2. Criar a feature em `src/features/<feature>/`
3. Separar:
- `components/`
- `queries.ts` (entry point de leitura)
- `actions.ts` (entry point de Server Actions; vira barrel quando crescer e migrar para `actions/`)
- `lib/` para helpers internos, sub-queries por tópico, types e constantes da feature
- `types.ts` ou `schemas.ts` quando fizer sentido (alternativa a `lib/`)
- `hooks/` quando houver hooks específicos da feature
4. Extrair para `src/shared/` tudo que for reutilizavel
5. Atualizar navegacao e `revalidateForEntity()` se a feature tiver CRUD
6. Rodar:
- `pnpm exec next typegen`
- `pnpm exec tsc --noEmit`
- `pnpm run lint`
---
## Security Rules
Regras aplicadas automaticamente ao gerar codigo.
### Secrets
Nunca colocar API keys, credenciais de banco ou tokens em codigo frontend. Evitar variaveis prefixadas com `NEXT_PUBLIC_` para dados sensiveis — estas sao bundladas no cliente. Usar variaveis server-side apenas. `.env` deve estar no `.gitignore` antes do primeiro commit. `.env.example` deve ter apenas placeholders.
### Autenticacao & Autorizacao
Toda rota protegida em `src/app/api/` requer `getUser()` ou `getOptionalUserSession()` antes de qualquer logica, retornando 401 para nao autenticados. Rotas com IDs de recursos devem verificar ownership: `eq(table.userId, userId)`. Rotas admin devem checar role e retornar 403 para nao-admins. Session cookies em Better Auth ja tem `httpOnly`, `secure` e `sameSite` configurados — nao alterar.
### Input & Output
Usar Drizzle ORM (parametrizado por padrao) — nunca concatenar input de usuario em SQL. Validar todo input com Zod antes de usar. Upload de arquivos: usar whitelist de MIME types (`ALLOWED_MIME_TYPES`), presigned URLs para S3, token de upload assinado com verificacao pos-upload. Nunca usar `dangerouslySetInnerHTML` com conteudo de usuario.
### Headers & CSP
CSP definida em `src/proxy.ts` via middleware — alterar la, nao em `next.config.ts`. Headers de seguranca (HSTS, X-Frame-Options, etc.) definidos em `next.config.ts`. Nao remover nem enfraquecer essas configuracoes.
### Rate Limiting
Login: 5 tentativas/min. Signup: 3 tentativas/min. API tokens: 100 req/min (inbox), 20 req/min (batch). Configurado em `src/shared/lib/auth/config.ts` e nas rotas de inbox. Nao remover.
### Tratamento de Erros
Erros nao devem expor stack traces, paths ou nomes de bibliotecas ao cliente. Usar mensagens genericas: `"Algo deu errado"`. Logar detalhes apenas no servidor com `console.error()`.
### Dependencias
Verificar pacotes novos sugeridos pela IA em npmjs.com antes de instalar. Red flags: menos de 1.000 downloads/semana, publicado nos ultimos 30 dias, nome muito parecido com pacote popular. Rodar `pnpm audit` periodicamente.
---

File diff suppressed because it is too large Load Diff

104
CLAUDE.md
View File

@@ -16,8 +16,10 @@
3. **Periods** usam formato `YYYY-MM` (ex: `"2025-11"`). Utils em `src/shared/utils/period/`.
4. **Moeda**: R$ com 2 decimais. DB: `numeric(12, 2)`. Utils em `src/shared/utils/currency.ts`.
5. **Revalidation**: usar `revalidateForEntity("entity")` de `src/shared/lib/actions/helpers.ts` apos mutations.
6. **Versionamento**: registrar mudancas no `CHANGELOG.md` seguindo Keep a Changelog.
6. **Versionamento e publicação**: registrar mudancas no `CHANGELOG.md` seguindo Keep a Changelog, também alterar o `package.json` e o badge de versão do `README.md`. Cada versão deve ter um parágrafo introdutório em linguagem humana logo abaixo do cabeçalho `## [x.y.z]`, antes das seções `### Adicionado/Alterado/Removido` — descrevendo em prosa o que a versão representa (ex: "Esta versão foca em polimento visual e reorganização interna..."). A `main` executa somente a CI; imagens Docker e GitHub Releases são publicadas exclusivamente por tags SemVer no formato `vX.Y.Z`. Antes de criar a tag, confirmar que a CI da `main` passou e que tag, `package.json`, `CHANGELOG.md` e badge do `README.md` usam a mesma versão. A tag deve apontar para o commit validado. Criar ou enviar uma tag dispara publicação externa (`X.Y.Z`, `X.Y`, `X` e `latest` no Docker Hub, seguida da GitHub Release), portanto agentes nunca devem criar ou fazer push de tags sem autorização explícita do usuário. Quando o usuário pedir **"commit e push"** em uma mudança que prepara uma nova versão, isso conta como autorização explícita para executar o fluxo completo: commitar, enviar a `main`, aguardar a CI da `main` passar, criar a tag SemVer correspondente à versão preparada e enviar essa tag. Se não houver versão preparada ou se houver divergência entre `package.json`, `CHANGELOG.md`, badge do `README.md` e tag pretendida, parar e pedir confirmação. Não voltar a publicar `latest` diretamente de pushes na `main`.
7. **Comunicacao**: responder em portugues clara e direta com o time.
8. **Commit messages**: agrupar por natureza. em pt-br. seguindo o padrao do sistema.
9. **README.md**: sempre que fizer alteracoes significativas, atualize o README.md.
---
@@ -43,6 +45,10 @@ Use esta pergunta:
Se um contrato cruza dominios, ele deve morar em `src/shared/`.
**Excecao intencional: `attachments` depende de `transactions`**
`src/features/attachments` importa `TransactionDialog`, `TransactionDetailsDialog` e `TransactionItem` diretamente de `src/features/transactions`. Isso e uma dependencia explicita e aceita: anexos sao semanticamente uma extensao de lancamentos — existem por causa deles e nao fazem sentido sem esse contexto. Mover esses componentes para `shared/` seria errado (eles pertencem a transactions). Nao tratar isso como bug a corrigir.
Exemplos comuns:
- auth: `src/shared/lib/auth/*`
@@ -79,6 +85,7 @@ src/
│ │ ├── insights/
│ │ ├── calendar/
│ │ ├── inbox/
│ │ ├── attachments/
│ │ ├── changelog/
│ │ ├── reports/
│ │ │ ├── category-trends/
@@ -90,7 +97,7 @@ src/
│ ├── api/
│ ├── globals.css
│ └── layout.tsx
├── features/
├── features/ # cada feature segue: actions.ts, queries.ts, actions/, components/, hooks/, lib/
│ ├── auth/
│ ├── landing/
│ ├── dashboard/
@@ -105,13 +112,17 @@ src/
│ ├── insights/
│ ├── calendar/
│ ├── inbox/
│ ├── attachments/
│ ├── reports/
│ └── settings/
├── shared/
│ ├── components/
│ │ ├── ui/
│ │ ├── navigation/
│ │ ├── providers/
│ │ ├── ui/ # shadcn/ui primitives
│ │ ├── navigation/ # navbar, sidebar, breadcrumbs
│ │ ├── providers/ # React context providers
│ │ ├── brand/ # logos do app (logo, logo-icon, logo-text)
│ │ ├── widgets/ # widget-card, widget-empty-state, expandable-widget-card
│ │ ├── feedback/ # empty-state, status-dot, payment-success
│ │ ├── month-picker/
│ │ ├── logo-picker/
│ │ ├── calculator/
@@ -126,34 +137,56 @@ src/
│ │ ├── calculator/
│ │ ├── categories/
│ │ ├── email/
│ │ ├── import/
│ │ ├── installments/
│ │ ├── invoices/
│ │ ├── logo/
│ │ ├── notifications/
│ │ ├── payers/
│ │ ├── schemas/
│ │ ├── storage/
│ │ ├── transfers/
│ │ ├── types/
│ │ ├── version/
│ │ └── db.ts
│ └── utils/
│ ├── period/
│ ├── calculator.ts
│ ├── calendar.ts
│ ├── category-colors.ts
│ ├── currency.ts
│ ├── date.ts
│ ├── export-branding.ts
│ ├── fetch-json.ts
│ ├── financial-dates.ts
│ ├── percentage.ts
│ ├── category-colors.ts
│ ├── calendar.ts
│ ├── icons.tsx
│ ├── id.ts
│ ├── initials.ts
│ ├── math.ts
│ ├── number.ts
│ ├── percentage.ts
│ ├── string.ts
── initials.ts
│ ├── icons.tsx
│ ├── export-branding.ts
│ ├── ui.ts
│ └── calculator.ts
── ui.ts
└── db/
└── schema.ts
```
### Estrutura interna padrão de uma feature
Toda feature em `src/features/<nome>/` segue:
```text
<feature>/
├── actions.ts # entry point de Server Actions (barrel quando há actions/)
├── queries.ts # entry point de leitura do banco
├── actions/ # (opcional) Server Actions divididas por domínio quando o volume cresce
├── components/ # componentes de UI da feature
├── hooks/ # React hooks específicos da feature
└── lib/ # helpers, types, sub-queries e constantes internas
```
`actions.ts` e `queries.ts` são as portas de entrada da feature. Tudo que é helper interno fica em `lib/`. Componentes e hooks ficam nas pastas com nome óbvio.
---
## Import Patterns
@@ -209,7 +242,9 @@ Layouts, `loading.tsx` e metadata continuam em `src/app/`.
| `contas` | `accounts` |
| `categorias` | `categories` |
| `orcamentos` | `budgets` |
| `pagadores` | `payers` |
| `pessoas` | `payers` |
> **Nota:** o conceito de "pagador" foi renomeado para **"pessoa"** na UI (labels, toasts, textos visíveis ao usuário). O código, rotas e schema continuam usando o termo original em inglês (`payer`, `payerId`, `adminPayerId`) e em português interno (`pagador` como variável). Não renomear esses identificadores — a divergência entre UI e código é intencional e documentada.
| `anotacoes` | `notes` |
| `calendario` | `calendar` |
| `ajustes` | `settings` |
@@ -289,9 +324,11 @@ export async function fetchData(userId: string, period: string) {
2. Criar a feature em `src/features/<feature>/`
3. Separar:
- `components/`
- `queries.ts`
- `actions.ts`
- `types.ts` ou `schemas.ts` quando fizer sentido
- `queries.ts` (entry point de leitura)
- `actions.ts` (entry point de Server Actions; vira barrel quando crescer e migrar para `actions/`)
- `lib/` para helpers internos, sub-queries por tópico, types e constantes da feature
- `types.ts` ou `schemas.ts` quando fizer sentido (alternativa a `lib/`)
- `hooks/` quando houver hooks específicos da feature
4. Extrair para `src/shared/` tudo que for reutilizavel
5. Atualizar navegacao e `revalidateForEntity()` se a feature tiver CRUD
6. Rodar:
@@ -301,18 +338,29 @@ export async function fetchData(userId: string, period: string) {
---
## Response Style
## Security Rules
Quando o time pedir avaliacao de plano ou feature:
Regras aplicadas automaticamente ao gerar codigo.
1. Responder em portugues simples.
2. Listar 3-5 problemas principais.
3. Fechar com decisao pratica:
- aprova agora
- nao aprova agora
- o que ajustar antes de comecar codigo
### Secrets
Nunca colocar API keys, credenciais de banco ou tokens em codigo frontend. Evitar variaveis prefixadas com `NEXT_PUBLIC_` para dados sensiveis — estas sao bundladas no cliente. Usar variaveis server-side apenas. `.env` deve estar no `.gitignore` antes do primeiro commit. `.env.example` deve ter apenas placeholders.
Exemplo:
### Autenticacao & Autorizacao
Toda rota protegida em `src/app/api/` requer `getUser()` ou `getOptionalUserSession()` antes de qualquer logica, retornando 401 para nao autenticados. Rotas com IDs de recursos devem verificar ownership: `eq(table.userId, userId)`. Rotas admin devem checar role e retornar 403 para nao-admins. Session cookies em Better Auth ja tem `httpOnly`, `secure` e `sameSite` configurados — nao alterar.
- "Nao aprovaria para comecar codigo imediatamente."
- "Primeiro ajustaria o doc com estes 5 pontos."
### Input & Output
Usar Drizzle ORM (parametrizado por padrao) — nunca concatenar input de usuario em SQL. Validar todo input com Zod antes de usar. Upload de arquivos: usar whitelist de MIME types (`ALLOWED_MIME_TYPES`), presigned URLs para S3, token de upload assinado com verificacao pos-upload. Nunca usar `dangerouslySetInnerHTML` com conteudo de usuario.
### Headers & CSP
CSP definida em `src/proxy.ts` via middleware — alterar la, nao em `next.config.ts`. Headers de seguranca (HSTS, X-Frame-Options, etc.) definidos em `next.config.ts`. Nao remover nem enfraquecer essas configuracoes.
### Rate Limiting
Login: 5 tentativas/min. Signup: 3 tentativas/min. API tokens: 100 req/min (inbox), 20 req/min (batch). Configurado em `src/shared/lib/auth/config.ts` e nas rotas de inbox. Nao remover.
### Tratamento de Erros
Erros nao devem expor stack traces, paths ou nomes de bibliotecas ao cliente. Usar mensagens genericas: `"Algo deu errado"`. Logar detalhes apenas no servidor com `console.error()`.
### Dependencias
Verificar pacotes novos sugeridos pela IA em npmjs.com antes de instalar. Red flags: menos de 1.000 downloads/semana, publicado nos ultimos 30 dias, nome muito parecido com pacote popular. Rodar `pnpm audit` periodicamente.
---

178
DESIGN.md Normal file
View File

@@ -0,0 +1,178 @@
# Design System do OpenMonetis
Este documento descreve a identidade visual implementada no OpenMonetis. Ele deve
ser usado como referência ao criar telas, revisar componentes e manter a
experiência consistente entre dashboard, relatórios, formulários e landing page.
## 1. Direção visual
O OpenMonetis busca tornar a gestão financeira clara e acolhedora. A interface
usa superfícies quentes, poucos elementos decorativos e uma cor laranja de
destaque para orientar o olhar sem transformar toda ação em urgência.
Princípios:
- priorizar legibilidade e hierarquia em telas com muitos dados;
- usar laranja para ações principais, seleção e foco;
- manter superfícies leves no tema claro e contraste confortável no tema escuro;
- aplicar cores semânticas para comunicar estado, não como decoração;
- preservar espaço suficiente entre blocos para evitar ruído visual;
- favorecer componentes responsivos e navegação acessível por teclado.
## 2. Fonte de verdade
Os tokens globais estão definidos em
[`src/app/globals.css`](./src/app/globals.css). Componentes reutilizáveis ficam
em [`src/shared/components/ui/`](./src/shared/components/ui/) e seguem o padrão
do shadcn/ui com Radix UI e Tailwind CSS 4.
Ao implementar uma tela:
1. use classes semânticas como `bg-background`, `bg-card`, `text-foreground`,
`text-muted-foreground`, `border-border` e `ring-ring`;
2. reutilize os componentes em `src/shared/components/ui/`;
3. evite cores hexadecimais e valores arbitrários quando já existir um token;
4. valide os dois temas antes de concluir a alteração.
## 3. Cores
A paleta é definida em OKLCH para manter uma percepção de contraste mais
consistente. Não copie os valores para componentes: use os tokens semânticos.
| Token | Papel |
|---|---|
| `background` | Fundo geral da aplicação |
| `foreground` | Texto principal |
| `card` / `card-foreground` | Cards e conteúdo em destaque |
| `popover` / `popover-foreground` | Menus, popovers e overlays |
| `primary` / `primary-foreground` | Ações principais, foco e seleção |
| `secondary` / `secondary-foreground` | Ações secundárias e superfícies discretas |
| `muted` / `muted-foreground` | Apoio visual, descrições e metadados |
| `accent` / `accent-foreground` | Hover e seleção leve |
| `success` | Confirmações, recebimentos e estados positivos |
| `warning` | Atenção, vencimentos e estados intermediários |
| `info` | Informações auxiliares |
| `destructive` | Erros e ações destrutivas |
| `border`, `input`, `ring` | Bordas, campos e foco |
### Gráficos
Gráficos usam `chart-1` a `chart-10`. Visualizações que precisam de uma escala
sequencial quente podem usar `data-1` a `data-6`. A cor nunca deve ser o único
meio de distinguir uma série: inclua legenda, rótulo ou tooltip.
### Tema escuro
O tema escuro redefine a mesma camada semântica dentro de `.dark`. Não crie uma
segunda árvore de componentes para suportá-lo. Prefira tokens e, somente quando
necessário, variantes Tailwind `dark:`.
## 4. Tipografia
A família principal é **Bricolage Grotesque**, carregada com `next/font` em
[`public/fonts/font_index.ts`](./public/fonts/font_index.ts). Os pesos
disponíveis são `500`, `600` e `700`, com fallback para Arial e fontes sans-serif
do sistema.
Diretrizes:
- corpo e controles: `text-sm` ou `text-base`;
- descrições e metadados: `text-sm text-muted-foreground`;
- títulos de card: `text-base font-medium`;
- títulos de modal: `text-lg font-semibold`;
- títulos de página: hierarquia responsiva conforme a densidade da tela;
- números financeiros: destaque por peso e alinhamento, sem depender apenas da
cor.
## 5. Espaçamento, raio e elevação
A escala base é de `0.25rem` (`4px`). Prefira a escala padrão do Tailwind para
padding, gap e margens. O raio base é `0.7rem`, exposto pelas classes
`rounded-sm`, `rounded-md`, `rounded-lg` e `rounded-xl`.
Sombras também são tokens. Cards comuns usam `shadow-xs`; menus, tooltips e
modais podem subir de nível conforme a necessidade. Evite adicionar sombra forte
a cada bloco: bordas e diferença de superfície devem resolver a maior parte da
hierarquia.
## 6. Componentes
### Botões
Use [`Button`](./src/shared/components/ui/button.tsx) e suas variantes:
| Variante | Uso |
|---|---|
| `default` | Ação principal da tela ou do fluxo |
| `secondary` | Ação complementar |
| `outline` | Ação neutra com contorno |
| `ghost` | Ação discreta em barras e grupos |
| `link` | Ação textual |
| `destructive` | Exclusão ou operação irreversível |
| `navbar` | Ferramentas da navegação superior |
Não coloque duas ações `default` competindo na mesma região. Para ícones sem
rótulo visível, inclua `aria-label` ou texto apenas para leitores de tela.
### Cards
Use [`Card`](./src/shared/components/ui/card.tsx) para agrupar informações
relacionadas. O componente já define fundo, borda, sombra leve, raio e destaque
de hover. Não transforme todo conteúdo em card: listas densas e tabelas podem
usar uma única superfície.
### Formulários
Campos devem usar os componentes compartilhados, como `Input`, `Select`,
`Checkbox`, `Switch` e `DatePicker`. Eles já aplicam foco com `ring`, estados
desabilitados e integração visual com os temas. Sempre associe controles a
`Label` e apresente erros próximos ao campo correspondente.
### Diálogos
Use [`Dialog`](./src/shared/components/ui/dialog.tsx) para tarefas focadas. Em
mobile, o conteúdo respeita a largura disponível; em telas maiores, o modal pode
ganhar mais espaço. Botões do rodapé devem preservar a ordem e a hierarquia da
ação principal.
### Feedback
Use toast para retorno breve, `Alert` para contexto persistente e componentes em
[`src/shared/components/feedback/`](./src/shared/components/feedback/) para
estados vazios, status e confirmações. Textos visíveis ao usuário devem estar em
português claro.
## 7. Layout e navegação
As páginas protegidas usam uma navbar fixa e um contêiner central com largura
máxima `max-w-8xl`, padding lateral responsivo e espaçamento vertical enxuto. A
navegação principal fica em
[`src/shared/components/navigation/navbar/`](./src/shared/components/navigation/navbar/).
Padrões:
- telas do App Router devem continuar finas;
- conteúdo principal começa abaixo da navbar fixa (`pt-16`);
- use uma coluna em telas pequenas e expanda grids progressivamente;
- tabelas e gráficos devem preservar leitura em viewport estreita;
- ações essenciais precisam continuar alcançáveis por toque e teclado.
## 8. Acessibilidade
- mantenha foco visível com os tokens `ring`;
- use HTML semântico antes de adicionar ARIA;
- não comunique estado apenas por cor;
- associe labels a inputs;
- forneça nome acessível para botões de ícone;
- confira contraste e navegação por teclado nos temas claro e escuro;
- mantenha áreas de toque confortáveis em mobile.
## 9. Checklist de revisão visual
- O componente compartilhado existente foi reutilizado?
- As cores usam tokens semânticos?
- A tela funciona em tema claro e escuro?
- O layout continua legível em mobile?
- Foco, labels e nomes acessíveis estão presentes?
- Estados vazio, carregando, erro e sucesso foram considerados?
- Valores financeiros continuam fáceis de comparar?

View File

@@ -5,13 +5,16 @@
# ============================================
FROM node:22-alpine AS deps
# Instalar pnpm globalmente
RUN corepack enable && corepack prepare pnpm@latest --activate
ARG PNPM_VERSION=11.1.3
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
WORKDIR /app
# Copiar apenas arquivos de dependências para aproveitar cache
COPY package.json pnpm-lock.yaml* ./
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
# Criar pasta public para o postinstall do pdfjs-dist
RUN mkdir -p public
# Instalar dependências (production + dev para o build)
RUN pnpm install --frozen-lockfile
@@ -21,8 +24,8 @@ RUN pnpm install --frozen-lockfile
# ============================================
FROM node:22-alpine AS builder
# Instalar pnpm globalmente
RUN corepack enable && corepack prepare pnpm@latest --activate
ARG PNPM_VERSION=11.1.3
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
WORKDIR /app
@@ -32,13 +35,18 @@ COPY --from=deps /app/node_modules ./node_modules
# Copiar todo o código fonte
COPY . .
# Garantir que o pdf.worker vem da versão instalada no stage 1, não do host
COPY --from=deps /app/public/pdf.worker.min.mjs ./public/pdf.worker.min.mjs
# Variáveis de ambiente necessárias para o build
# DATABASE_URL será fornecida em runtime, mas precisa estar definida para validação
ENV NEXT_TELEMETRY_DISABLED=1 \
NODE_ENV=production
# Nota: a integração Logo.dev não precisa mais de build args. O token
# `LOGO_DEV_TOKEN` é lido em runtime no servidor — basta configurá-lo no
# host (Coolify, Railway, etc.) junto com `LOGO_DEV_SECRET_KEY`.
# Build da aplicação Next.js
# Nota: Se houver erros de tipo, ajuste typescript.ignoreBuildErrors no next.config.ts
RUN pnpm build
# ============================================
@@ -46,8 +54,8 @@ RUN pnpm build
# ============================================
FROM node:22-alpine AS runner
# Instalar pnpm globalmente
RUN corepack enable && corepack prepare pnpm@latest --activate
ARG PNPM_VERSION=11.1.3
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
WORKDIR /app
@@ -55,12 +63,27 @@ WORKDIR /app
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Copiar apenas arquivos necessários para produção
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/pnpm-lock.yaml ./pnpm-lock.yaml
# Instalar deps do drizzle-kit em diretório separado ANTES de copiar o standalone
# Isso evita que o pnpm install sobrescreva o node_modules do Next.js standalone
COPY --from=builder /app/package.json /tmp/pkg.json
RUN mkdir -p /app/migrate && \
node -e "\
const p=JSON.parse(require('fs').readFileSync('/tmp/pkg.json','utf8'));\
require('fs').writeFileSync('/app/migrate/package.json',JSON.stringify({\
name:'openmonetis-migrate',version:p.version,\
dependencies:{\
'drizzle-orm':p.dependencies['drizzle-orm'],\
'pg':p.dependencies['pg']\
},\
devDependencies:{'drizzle-kit':p.devDependencies['drizzle-kit']}\
}));" && \
cd /app/migrate && pnpm install --no-frozen-lockfile --ignore-scripts && \
chown -R nextjs:nodejs /app/migrate
# Copiar arquivos de build do Next.js
# Copiar apenas arquivos necessários para produção
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
# Copiar arquivos de build do Next.js (inclui node_modules standalone com next)
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
@@ -69,8 +92,11 @@ COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle
COPY --from=builder --chown=nextjs:nodejs /app/drizzle.config.ts ./drizzle.config.ts
COPY --from=builder --chown=nextjs:nodejs /app/src/db ./src/db
# Copiar node_modules para ter drizzle-kit disponível para migrations
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
# Copiar entrypoint de migrations
COPY docker-entrypoint.sh ./
RUN sed -i 's/\r$//' /app/docker-entrypoint.sh && \
chmod +x /app/docker-entrypoint.sh && \
chown nextjs:nodejs /app/docker-entrypoint.sh
# Definir variáveis de ambiente de produção
ENV NODE_ENV=production \
@@ -81,16 +107,13 @@ ENV NODE_ENV=production \
# Expor porta
EXPOSE 3000
# Ajustar permissões para o usuário nextjs
RUN chown -R nextjs:nodejs /app
# Mudar para usuário não-root
USER nextjs
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" || exit 1
CMD wget --quiet --tries=1 --spider http://127.0.0.1:3000/api/health || exit 1
# Comando de inicialização
# Nota: Em produção com standalone build, o servidor é iniciado pelo arquivo server.js
# Entrypoint: roda migrations e depois executa o CMD
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["node", "server.js"]

483
README.md
View File

@@ -1,14 +1,16 @@
<p align="center">
<img src="./public/images/logo_small.png" alt="OpenMonetis Logo" height="80" />
<img src="./public/images/logo_small.svg" alt="OpenMonetis Logo" height="80" />
</p>
<p align="center">
Projeto pessoal de gestão financeira. Self-hosted, manual e open source.
</p>
> **⚠️ Não há versão online hospedada.** Você precisa clonar o repositório e rodar localmente ou no seu próprio servidor.
> **⚠️ Nota:** o OpenMonetis não está sendo encerrado, mas o desenvolvimento deve reduzir para quase zero daqui em diante. O app já cobre minhas demandas atuais de gerenciamento financeiro, então novas mudanças tendem a ser pontuais: correções, ajustes necessários e pequenas melhorias quando fizerem bastante sentido para meu uso.
[![Version](https://img.shields.io/badge/version-2.0.0-blue?style=flat-square)](CHANGELOG.md)
> **Não há versão online hospedada.** Você precisa clonar o repositório e rodar localmente ou no seu próprio servidor.
[![Version](https://img.shields.io/badge/version-2.7.10-blue?style=flat-square)](CHANGELOG.md)
[![Next.js](https://img.shields.io/badge/Next.js-black?style=flat-square&logo=next.js)](https://nextjs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-blue?style=flat-square&logo=postgresql)](https://www.postgresql.org/)
@@ -20,7 +22,7 @@
---
<p align="center">
<img src="./public/images/dashboard-preview-light.webp" alt="Dashboard Preview" width="800" />
<img src="./public/images/dashboard-preview-light.png" alt="Dashboard Preview" width="800" />
</p>
---
@@ -28,14 +30,19 @@
## 📖 Índice
- [Sobre o Projeto](#-sobre-o-projeto)
- [Instalação via Script](#-instalação-via-script)
- [Início Rápido (manual)](#-início-rápido)
- [Como rodar o OpenMonetis](#-como-rodar-o-openmonetis)
- [Perfil 1 — Usar](#perfil-1--usar-self-hosting)
- [Perfil 2 — Desenvolver](#perfil-2--desenvolver)
- [Scripts Disponíveis](#-scripts-disponíveis)
- [Docker](#-docker)
- [Backup](#-backup)
- [Storage S3 Compatível](#-storage-s3-compatível)
- [Variáveis de Ambiente](#-variáveis-de-ambiente)
- [Design System](#-design-system)
- [Arquitetura](#-arquitetura)
- [Contribuindo](#-contribuindo)
- [Apoie o Projeto](#-apoie-o-projeto)
- [Star History](#-star-history)
- [Licença](#-licença)
---
@@ -52,33 +59,37 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ
**1. Não há versão hospedada online** — Este projeto é self-hosted. Você precisa rodar no seu próprio computador ou servidor.
**2. Não há Open Finance** — Não há conexão automática com bancos. Você pode registrar transações manualmente ou importar extratos nos formatos OFX e XLS/XLSX.
**2. Não há Open Finance** — Não há conexão automática com bancos. Você pode registrar transações manualmente, usar o app companion para capturar notificações bancárias ou importar extratos nos formatos OFX e XLS/XLSX.
**3. Requer disciplina** — O OpenMonetis funciona melhor para quem tem disciplina de registrar os gastos regularmente, quer controle total sobre seus dados e gosta de entender exatamente onde o dinheiro está indo.
### Funcionalidades
💰 **Contas e transações** — Contas bancárias, cartões, dinheiro. Receitas, despesas e transferências. Categorização, extratos detalhados e importação de extratos OFX e XLS/XLSX com detecção automática de categoria.
💰 **Contas e transações** — Contas bancárias, cartões, dinheiro. Receitas, despesas, rendimentos e transferências. Categorização, divisão de lançamentos entre várias pessoas, filtros combináveis com intervalo de datas, extratos detalhados com identificação visual clara da conta e importação de extratos OFX e XLS/XLSX com detecção automática de categoria.
📊 **Dashboard e relatórios** — Widgets interativos de métricas, gráficos de evolução, comparativos por categoria, tendências, uso de cartões, top estabelecimentos. Exportação em PDF e Excel.
📊 **Dashboard e relatórios** — Widgets personalizáveis com listas consistentes, métricas com atalhos para lançamentos, gráficos de evolução, comparativos por categoria, tendências, uso de cartões, top estabelecimentos e navegação direta entre meses pelo seletor de período. Exportação em PDF e Excel.
💳 **Faturas de cartão** — Acompanhe faturas por período, controle limites e vencimentos.
💳 **Faturas de cartão** — Acompanhe faturas por período, controle limites e vencimentos com identificação visual mais clara do cartão.
🎯 **Orçamentos** — Defina limites por categoria e acompanhe o progresso.
💸 **Parcelamentos avançados** — Séries de parcelas, antecipação com cálculo de desconto, análise consolidada.
🤖 **Insights com IA** — Análises geradas por Claude, GPT, Gemini ou OpenRouter. Insights personalizados e histórico salvo.
🤖 **Insights com IA** — Análises geradas por Claude, GPT, Gemini, MiniMax, OpenRouter ou modelos locais via Ollama. Insights personalizados e histórico salvo.
👥 **Gestão colaborativa** — Pagadores com permissões (admin/viewer), notificações automáticas por e-mail, códigos de compartilhamento.
📝 **Anotações e tarefas** — Notas de texto, listas com checkboxes, sistema de arquivamento.
📝 **Anotações e tarefas** — Notas de texto com anexos, listas com checkboxes e sistema de arquivamento.
📅 **Calendário financeiro** — Visualize todos os lançamentos em um calendário mensal.
📲 **OpenMonetis Companion** — App Android que captura notificações bancárias (Nubank, Itaú, Bradesco, Inter, C6 e outros) e envia como pré-lançamentos para revisão. [Repositório](https://github.com/felipegcoutinho/openmonetis-companion).
📲 **OpenMonetis Companion** — App Android que captura notificações bancárias (Nubank, Itaú, Bradesco, Inter, C6 e outros) e envia automaticamente como pré-lançamentos para revisão — sem digitar nada. [Repositório](https://github.com/felipegcoutinho/openmonetis-companion).
⚙️ **Personalização** — Tema dark/light e modo privacidade.
<p align="center">
<img src="./public/images/companion-preview-light.webp" alt="OpenMonetis Companion" width="300" height="600" />
</p>
⚙️ **Personalização** — Tema dark/light, modo privacidade, ordem das colunas, exibição de anotações, tamanho máximo de anexos, resumo opcional no modal de lançamento e changelog visual para acompanhar as novidades do app.
### Stack técnica
@@ -86,88 +97,126 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ
- **PostgreSQL** + **Drizzle ORM**
- **Better Auth** (email/senha, OAuth, Passkeys/WebAuthn)
- **shadcn/ui** (Radix UI) + **Tailwind CSS**
- **Bricolage Grotesque** via `next/font`
- **Docker** (multi-stage build)
- **Biome** (linting + formatting)
- **Vercel AI SDK** (Claude, GPT, Gemini, OpenRouter)
- **Vercel AI SDK** (Claude, GPT, Gemini, MiniMax, OpenRouter, Ollama)
---
## ⚡ Instalação via Script
## 🚀 Como rodar o OpenMonetis
A forma mais rápida de instalar. O script verifica dependências, configura o `.env` interativamente e sobe o banco automaticamente.
Escolha o perfil que corresponde ao seu objetivo:
**Pré-requisito:** Node.js 22+
| | Perfil 1 — Usar | Perfil 2 — Desenvolver |
|---|---|---|
| **Objetivo** | Rodar o app pronto | Modificar o código |
| **Clonar repositório** | Não | Sim |
| **Node.js / pnpm** | Não | Sim (Node 22+) |
| **Docker** | Sim | Sim |
| **Como iniciar** | `docker compose up -d` | `pnpm docker:db` + `pnpm dev` |
| **App roda em** | Container Docker | Host local (hot-reload) |
| **Banco roda em** | Container Docker | Container Docker |
| **`DATABASE_URL` (host)** | `db` (automático pelo compose) | `localhost` |
| **Banco remoto (Supabase, Neon...)** | Sim (`docker compose up -d app`) | Sim (ajustar `DATABASE_URL`) |
| **Como atualizar** | `pnpm docker:update` | `git pull` + `pnpm install` + `pnpm db:push` |
| **Indicado para** | Self-hosting, VPS, servidor | Contribuidores, customizações |
---
### Perfil 1 — Usar (self-hosting)
Só quer rodar o OpenMonetis. **Não precisa clonar o repositório nem instalar Node.js** — apenas Docker.
```bash
# Mac / Linux / WSL
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/setup.mjs -o setup.mjs && node setup.mjs
# 1. Baixe o compose
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/docker-compose.yml -o docker-compose.yml
# Windows (PowerShell)
curl -o setup.mjs https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/setup.mjs ; node setup.mjs
# 2. Crie um .env na mesma pasta.
# .env mínimo recomendado para produção
BETTER_AUTH_SECRET=gere-um-valor-com-openssl-rand-base64-32
BETTER_AUTH_URL=http://seu-dominio.com
DISABLE_SIGNUP=false # opcional: true bloqueia novos cadastros
# 3. Suba tudo
docker compose up -d
```
O script irá:
- Verificar Node, pnpm, Git e Docker
- Perguntar se quer banco local (Docker) ou remoto (Supabase, Neon, etc.)
- Gerar o `BETTER_AUTH_SECRET` automaticamente
- Configurar opcionais: Google OAuth, e-mail, IA, domínio público
- Clonar o repositório, instalar dependências e aplicar o schema
Acesse em: `http://localhost:3000`
O banco sobe com credenciais padrão. Para personalizar (senha, Google OAuth, e-mail, IA...), crie um `.env` na mesma pasta **antes** de subir.
Mais sobre .env em [Variáveis de Ambiente](#-variáveis-de-ambiente).
**Banco remoto (Supabase, Neon, Railway...):** defina `DATABASE_URL` no `.env` e suba só o app:
```bash
docker compose up -d app
```
**Não tem Docker instalado?** Em servidores Ubuntu 24.04 limpos, use o script de instalação:
```bash
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/scripts/install-deps.sh -o install-deps.sh
sudo sh install-deps.sh
```
> Ao final, faça **logout e login** para as permissões do grupo `docker` terem efeito.
#### Atualizando (Perfil 1)
```bash
pnpm docker:update
# ou equivalente:
docker compose pull && docker compose up -d
```
O schema do banco é aplicado automaticamente no startup — nenhum passo extra necessário.
---
## 🚀 Início Rápido (manual)
### Perfil 2 — Desenvolver
### Pré-requisitos
Quer modificar o código com hot-reload. O banco roda no Docker, o app roda direto no seu servidor.
- Node.js 22+ e pnpm
- Docker e Docker Compose
**Requisitos:** Docker + Node.js 22+ + pnpm
### Passo a Passo
```bash
# 1. Clone o repositório
git clone https://github.com/felipegcoutinho/openmonetis.git
cd openmonetis
1. **Clone e instale**
# 2. Instale as dependências
pnpm install
```bash
git clone https://github.com/felipegcoutinho/openmonetis.git
cd openmonetis
pnpm install
```
# 3. Configure o ambiente
cp .env.example .env
# O DATABASE_URL já vem com host "localhost" (correto para dev local).
# Edite o .env com suas configurações (BETTER_AUTH_SECRET, etc.)
2. **Configure o `.env`**
# 4. Suba o banco
pnpm docker:db
```bash
cp .env.example .env
```
# 5. Aplique o schema no banco (apenas no primeiro setup)
pnpm db:push
Edite o `.env` com suas credenciais. O principal é o `DATABASE_URL` e o `BETTER_AUTH_SECRET`:
# 6. Inicie o app com hot-reload
pnpm dev
```
```env
# Banco local (Docker): use host "localhost"
DATABASE_URL=postgresql://openmonetis:openmonetis_dev_password@localhost:5432/openmonetis_db
Acesse em: `http://localhost:3000`
# Banco remoto (Supabase, Neon, etc): use a URL completa do provider
# DATABASE_URL=postgresql://user:password@host:5432/database?sslmode=require
Toda vez que salvar um arquivo, o app atualiza automaticamente sem precisar reiniciar.
BETTER_AUTH_SECRET=seu-secret-aqui # gere com: openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
```
#### Atualizando (Perfil 2)
3. **Suba o banco de dados** (pule se estiver usando banco remoto)
```bash
git pull
pnpm install # instala dependências novas, se houver
pnpm db:push # aplica mudanças de schema, se houver
```
```bash
docker compose up db -d
pnpm db:enableExtensions
```
4. **Execute as migrations e inicie**
```bash
pnpm db:push
pnpm dev
```
5. Acesse `http://localhost:3000`
> **Docker completo** (app + banco em containers): use `pnpm docker:up` ao invés dos passos 3-4.
O `pnpm dev` já em execução detecta as mudanças de código automaticamente — não precisa reiniciar.
---
@@ -195,41 +244,54 @@ pnpm db:studio # Drizzle Studio (UI visual)
### Utilitários
```bash
pnpm backup # Backup do banco (requer scripts/backup.sh configurado)
pnpm backup # Backup completo do banco (ver seção Backup)
```
### Docker
```bash
pnpm docker:up # Subir app + banco
pnpm docker:up:d # Subir em background
pnpm docker:up:db # Subir apenas o banco
pnpm docker:down # Parar containers
pnpm docker:down:volumes # Parar e remover volumes (⚠️ apaga dados!)
pnpm docker:logs # Logs em tempo real
pnpm docker:restart # Reiniciar
pnpm docker:rebuild # Rebuild completo
pnpm docker:up # Sobe app (Docker Hub) + banco em background
pnpm docker:db # Sobe apenas o banco em background (usar com pnpm dev)
pnpm docker:down # Para e remove os containers
pnpm docker:logs # Logs em tempo real (todos os containers)
pnpm docker:update # Atualiza para a imagem mais recente do Hub e reinicia
```
---
## 🐳 Docker
O `Dockerfile` usa multi-stage build (deps → builder → runner) com imagem final ~200MB rodando como usuário não-root.
O `Dockerfile` usa multi-stage build (deps → builder → runner) com imagem final ~200MB rodando como usuário não-root. Health checks configurados para ambos os serviços (PostgreSQL via `pg_isready`, app via `GET /api/health`).
Health checks configurados para ambos os serviços (PostgreSQL via `pg_isready`, app via `GET /api/health`).
### Self-hosting (recomendado)
Baixe apenas o `docker-compose.yml` e suba tudo — sem clonar o repositório, sem instalar dependências:
```bash
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/docker-compose.yml -o docker-compose.yml
docker compose up -d
```
As credenciais padrão do banco já estão configuradas. Para personalizar (senhas, opcionais), crie um `.env` na mesma pasta antes de subir — veja [Variáveis de Ambiente](#-variáveis-de-ambiente).
### Banco remoto (Supabase, Neon, Railway...)
Suba apenas o app e aponte para o banco externo via `DATABASE_URL` no `.env`:
```bash
docker compose up -d app
```
### Comandos úteis
```bash
docker compose exec app sh # Shell da aplicação
docker compose exec app sh # Shell da aplicação
docker compose exec db psql -U openmonetis -d openmonetis_db # Shell do banco
docker compose ps # Status
docker compose exec db pg_dump -U openmonetis openmonetis_db > backup.sql # Backup
docker compose exec -T db psql -U openmonetis -d openmonetis_db < backup.sql # Restore
pnpm backup # Backup (ver seção Backup)
```
### Customizando Portas
### Customizando portas
```env
APP_PORT=3001 # Padrão: 3000
@@ -238,13 +300,142 @@ DB_PORT=5433 # Padrão: 5432
---
## 💾 Backup
O backup é uma rotina de infraestrutura — não é uma tela no app. Ele opera diretamente sobre o banco PostgreSQL e é executado via linha de comando.
```bash
pnpm backup
```
### O que é salvo
Cada execução gera **3 arquivos** em `backup/`:
| Arquivo | Conteúdo | Uso |
|---|---|---|
| `openmonetis_YYYY-MM-DD_HH-MM.dump` | Dump custom dos schemas `public` + `drizzle` | Restore completo via `pg_restore` |
| `openmonetis_YYYY-MM-DD_HH-MM.sql.gz` | Dump SQL compactado dos schemas `public` + `drizzle` | Inspeção manual, portabilidade |
| `openmonetis_YYYY-MM-DD_HH-MM.data.sql.gz` | Apenas os dados do schema `public` (sem DDL) | Migração parcial, seed de outro ambiente |
### Modos de conexão
Configure `DB_MODE` no topo de `scripts/backup.sh`:
| Modo | Quando usar | Fonte de dados |
|---|---|---|
| `remote` (padrão) | Banco em Supabase, Neon, Railway, etc. | `DATABASE_URL` do `.env` |
| `docker` | Banco no container local | Container `openmonetis_postgres` |
### Upload para Google Drive (opcional)
Se o [rclone](https://rclone.org/) estiver instalado e configurado com um remote chamado `gdrive`, os arquivos são enviados automaticamente para `gdrive:BACKUP OPENMONETIS`. Sem o rclone, o backup funciona normalmente e fica apenas local.
**Retenção:**
- Local: 7 dias
- Google Drive: 30 dias
### Automatizar com cron
Para rodar o backup automaticamente todo dia às 3h:
```bash
crontab -e
```
```cron
0 3 * * * cd /caminho/para/openmonetis && pnpm backup >> /var/log/openmonetis-backup.log 2>&1
```
### Restore
```bash
# 1. Zerar o banco
docker exec <container-db> psql -U openmonetis -d openmonetis_db \
-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
# 2. Restaurar schema + dados (um comando)
docker exec -i <container-db> pg_restore \
-U openmonetis -d openmonetis_db \
--clean --if-exists --disable-triggers --no-owner --no-privileges \
< backup/openmonetis_YYYY-MM-DD_HH-MM.dump
```
> `--disable-triggers` é necessário para evitar erros de FK durante o restore (os dados são inseridos fora de ordem). O usuário `openmonetis` tem permissão para isso.
---
## ☁️ Storage S3 Compatível
O suporte a anexos de lançamentos usa upload direto com URL pré-assinada. Essa configuração é opcional, mas passa a ser necessária se você quiser habilitar anexos no app.
### Variáveis
```env
S3_ENDPOINT=
S3_REGION=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET=
```
### Compatibilidade
- O código atual espera um provider com API compatível com S3 e suporte a `PutObject`, `GetObject`, `HeadObject`, `DeleteObject` e URLs pré-assinadas.
- A implementação usa `endpoint` customizado e `forcePathStyle: true` em [`src/shared/lib/storage/s3-client.ts`](./src/shared/lib/storage/s3-client.ts).
- Em geral isso cobre MinIO, Cloudflare R2, Backblaze B2 S3-Compatible, DigitalOcean Spaces e AWS S3. Mas foi testado apenas no Supabase Storage.
- Se o seu provider exigir `virtual-hosted-style` em vez de `path-style`, você vai precisar ajustar essa configuração antes de usar anexos.
- Se as variáveis de S3 não forem configuradas, mantenha os anexos desabilitados no seu fluxo de uso.
---
## 🏷️ Logos de Estabelecimentos (Logo.dev)
O app exibe logos automáticos de marcas na coluna de estabelecimentos nos lançamentos. A integração usa a [Logo.dev](https://www.logo.dev) e é opcional — sem ela, o app exibe as iniciais coloridas normalmente.
### Variáveis
```env
LOGO_DEV_TOKEN=pk_... # token público (obrigatório para exibir logos)
LOGO_DEV_SECRET_KEY=sk_... # chave secreta (obrigatório para o picker de busca)
```
> **Atualizando da v2.4.1 ou anterior:** a variável foi renomeada de `NEXT_PUBLIC_LOGO_DEV_TOKEN` para `LOGO_DEV_TOKEN`. Renomeie no seu `.env` (ou nas variáveis do Coolify/host) e remova o secret homônimo do GitHub Actions — ele não é mais usado. Não há outra etapa de migração.
### Como configurar
Ambas as variáveis são lidas em **runtime** pelo servidor Next.js. Não há mais nenhuma etapa no CI nem `--build-arg` no Docker.
**Self-hosted via Docker Hub (Coolify, Railway, etc.):**
1. Adicione `LOGO_DEV_TOKEN` e `LOGO_DEV_SECRET_KEY` nas variáveis de ambiente do host
2. Reinicie o container — pronto
**Desenvolvimento local:**
Adicione as duas no `.env` e rode `pnpm dev`.
### Como usar
Após configurado, passe o mouse sobre o avatar de qualquer estabelecimento nos lançamentos — um ícone de lápis aparece. Clique para abrir o picker, busque pelo nome da marca e selecione o logo desejado. O mapeamento fica salvo por usuário no banco.
### Arquitetura
O token **nunca chega ao cliente**. O servidor constrói a URL `https://img.logo.dev/{domain}?token=...` nos endpoints `/api/logo/mapping` e `/api/logo/search`, e o cliente apenas consome a URL pronta. Um Context Provider (`LogoDevProvider`) propaga a flag `enabled` para os componentes que decidem se renderizam o picker.
---
## 🔐 Variáveis de Ambiente
Copie `.env.example` para `.env` e configure:
**Perfil 2 (dev):** copie `.env.example` para `.env` — o `DATABASE_URL` já vem com `localhost`, pronto para uso com `pnpm dev`.
**Perfil 1 (Docker):** não precisa definir `DATABASE_URL` — o compose já configura automaticamente com host `db`. Só defina se usar banco remoto (Supabase, Neon, etc.).
### Obrigatórias
```env
# Perfil 2 (dev): host "localhost" — o banco roda em container, o app no host
# Perfil 1 (Docker): não precisa definir — o compose usa "db" automaticamente
DATABASE_URL=postgresql://openmonetis:openmonetis_dev_password@localhost:5432/openmonetis_db
BETTER_AUTH_SECRET=seu-secret-aqui # openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
@@ -258,6 +449,19 @@ POSTGRES_USER=openmonetis
POSTGRES_PASSWORD=openmonetis_dev_password
POSTGRES_DB=openmonetis_db
# Autenticação
DISABLE_SIGNUP=false # true bloqueia novos cadastros
AUTH_SESSION_EXPIRES_IN_DAYS=30 # duração de sessões persistentes
AUTH_SESSION_UPDATE_AGE_HOURS=24 # frequência de renovação da sessão
BETTER_AUTH_TRUSTED_ORIGINS= # origins adicionais confiáveis, separadas por vírgula
# S3 Server (opcional, necessario para anexos)
S3_ENDPOINT=
S3_REGION=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET=
# Multi-domínio (landing-only no domínio público)
# PUBLIC_DOMAIN=openmonetis.com
@@ -273,9 +477,62 @@ RESEND_FROM_EMAIL=
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
GOOGLE_GENERATIVE_AI_API_KEY=
MINIMAX_API_KEY=
OPENROUTER_API_KEY=
OLLAMA_BASE_URL=http://localhost:11434/v1
OLLAMA_API_KEY=
# Logo.dev (opcional, necessário para logos automáticos de estabelecimentos)
# Ambas as variáveis são runtime — basta definir no host; nenhum build arg necessário.
LOGO_DEV_TOKEN=
LOGO_DEV_SECRET_KEY=
```
### BETTER_AUTH_TRUSTED_ORIGINS
Use `BETTER_AUTH_TRUSTED_ORIGINS` quando o OpenMonetis for acessado por uma URL diferente de `BETTER_AUTH_URL`, como Cloudflare Tunnel, reverse proxy, domínio local ou subdomínios temporários. Isso evita falhas de login como `Invalid origin` sem precisar alterar a imagem Docker.
Informe apenas origins confiáveis, separadas por vírgula:
```env
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_TRUSTED_ORIGINS=https://*.trycloudflare.com,https://openmonetis.seudominio.com
```
Para Google OAuth e outros callbacks externos, mantenha `BETTER_AUTH_URL` apontando para a URL pública/canônica configurada no provedor.
### IA local com Ollama
O provider Ollama permite gerar insights usando modelos locais. Instale e suba o Ollama no host onde o modelo ficará disponível:
```bash
ollama pull llama3.2
ollama serve
```
Configure a URL OpenAI-compatible no `.env`:
```env
OLLAMA_BASE_URL=http://localhost:11434/v1
# Opcional; normalmente o Ollama local não exige chave.
OLLAMA_API_KEY=
```
Se o OpenMonetis estiver rodando dentro de um container Docker e o Ollama estiver no host, `localhost` aponta para o próprio container. Nesse caso, use uma URL acessível a partir do container, como `http://host.docker.internal:11434/v1` quando disponível, ou o endereço da rede Docker/host configurado no seu ambiente.
---
## 🎨 Design System
O OpenMonetis usa uma identidade visual própria com superfícies quentes, laranja
como cor de destaque, temas claro e escuro e tipografia Bricolage Grotesque. A
interface é construída com tokens semânticos em OKLCH, Tailwind CSS 4 e
componentes compartilhados baseados em shadcn/ui e Radix UI.
As regras de cores, tipografia, componentes, responsividade e acessibilidade
estão documentadas no [`DESIGN.md`](DESIGN.md). Use esse guia como referência ao
criar telas ou alterar componentes visuais.
---
## 🏗️ Arquitetura
@@ -310,7 +567,18 @@ openmonetis/
│ │ └── auth/ # Formulários de autenticação
│ │
│ ├── shared/ # Código reutilizado entre features
│ │ ├── components/ # UI compartilhada (shadcn/ui, navigation, skeletons...)
│ │ ├── components/ # UI compartilhada
│ │ │ ├── ui/ # shadcn/ui primitives
│ │ │ ├── navigation/ # navbar, sidebar, breadcrumbs
│ │ │ ├── brand/ # logos do app
│ │ │ ├── widgets/ # widget-card e variantes
│ │ │ ├── feedback/ # empty-state, status-dot, payment-success
│ │ │ ├── entity-avatar/ # avatares de categoria/estabelecimento
│ │ │ ├── month-picker/ # seletor de período
│ │ │ ├── logo-picker/ # seletor de logos
│ │ │ ├── calculator/ # calculadora de cálculos rápidos
│ │ │ ├── skeletons/ # loading skeletons
│ │ │ └── providers/ # React context providers
│ │ ├── hooks/ # React hooks globais
│ │ ├── lib/ # Helpers de domínio (auth, db, payers, schemas, email...)
│ │ └── utils/ # Utilitários (currency, date, period, math, string...)
@@ -326,6 +594,22 @@ openmonetis/
└── proxy.ts # Middleware (auth + multi-domínio)
```
### Estrutura interna de uma feature
Toda feature em `src/features/<nome>/` segue o mesmo padrão:
```
<feature>/
├── actions.ts # Server Actions (entry point — barrel re-export quando há actions/)
├── queries.ts # Funções de leitura do banco (entry point)
├── actions/ # (opcional) Server Actions divididas por domínio quando o volume cresce
├── components/ # Componentes de UI da feature
├── hooks/ # React hooks específicos da feature
└── lib/ # Helpers, types, sub-queries e constantes
```
A regra é: `actions.ts` e `queries.ts` são as portas de entrada da feature. Tudo que é helper interno fica em `lib/`. Componentes e hooks ficam nas pastas com nome óbvio.
---
## 🤝 Contribuindo
@@ -339,6 +623,17 @@ openmonetis/
Antes de começar, leia o [`CLAUDE.md`](CLAUDE.md) — ele documenta a arquitetura, convenções de nomenclatura, regras de queries e o checklist para novas features. Use TypeScript, commits semânticos e mantenha o `CHANGELOG.md` atualizado.
### Publicando uma versão
As validações rodam em pull requests e em cada push na `main`. A publicação só começa quando uma tag SemVer aponta para um commit validado e a versão da tag corresponde ao `package.json` e ao `CHANGELOG.md`.
```bash
git tag -a v2.7.10 -m "v2.7.10"
git push origin v2.7.10
```
O workflow da tag valida o código, publica as imagens Docker versionadas e `latest` e, somente depois, cria a GitHub Release com as notas do changelog.
---
## 💖 Apoie o Projeto
@@ -351,6 +646,18 @@ Outras formas de contribuir: ⭐ estrela no repo, reportar bugs, melhorar docs,
---
## ⭐ Star History
<a href="https://www.star-history.com/?repos=felipegcoutinho%2Fopenmonetis&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=felipegcoutinho/openmonetis&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=felipegcoutinho/openmonetis&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=felipegcoutinho/openmonetis&type=date&legend=top-left" />
</picture>
</a>
---
## 📄 Licença
**Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International** (CC BY-NC-SA 4.0).
@@ -365,12 +672,6 @@ Para o texto legal completo, consulte o arquivo [LICENSE](LICENSE) ou visite [cr
---
## 🙏 Agradecimentos
[Next.js](https://nextjs.org/) · [Better Auth](https://better-auth.com/) · [Drizzle ORM](https://orm.drizzle.team/) · [shadcn/ui](https://ui.shadcn.com/) · [Biome](https://biomejs.dev/) · [Vercel](https://vercel.com/)
---
**Desenvolvido por:** Felipe Coutinho — [@felipegcoutinho](https://github.com/felipegcoutinho)
<div align="center">

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.7/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",

View File

@@ -1,163 +1,53 @@
# Docker Compose para Next.js + PostgreSQL
name: openmonetis
# MODOS DE USO:
# 1. Banco LOCAL (PostgreSQL em container):
# - Configure DATABASE_URL com host "db" no .env
# - Execute: docker compose up --build
#
# 2. Banco REMOTO (ex: Supabase):
# - Configure DATABASE_URL com a URL do banco remoto no .env
# - Execute: docker compose up app --build (apenas o serviço app)
#
# 3. Para parar todos os serviços:
# - Execute: docker compose down
#
# 4. Para remover volumes (CUIDADO: apaga dados do banco local):
# - Execute: docker compose down -v
services:
# ============================================
# Serviço: PostgreSQL (Banco de dados local)
# ============================================
db:
image: postgres:18-alpine
container_name: openmonetis_postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-openmonetis}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-openmonetis_dev_password}
POSTGRES_DB: ${POSTGRES_DB:-openmonetis_db}
# Garante que os dados ficam no volume montado (evita perda após down/up)
PGDATA: /var/lib/postgresql/data
# Configurações de performance
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
PGDATA: /var/lib/postgresql/data
ports:
# Mapeia porta 5432 do container para 5432 do host
# Útil para conectar com ferramentas externas (ex: DBeaver, pgAdmin)
- "${DB_PORT:-5432}:5432"
volumes:
# Volume nomeado para persistência de dados
# Os dados sobrevivem ao restart do container
- postgres_data:/var/lib/postgresql/data
# Script de inicialização (cria extensão pgcrypto automaticamente)
- ./scripts/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${POSTGRES_USER:-openmonetis} -d ${POSTGRES_DB:-openmonetis_db}",
]
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-openmonetis} -d ${POSTGRES_DB:-openmonetis_db}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
- openmonetis_network
# Descomentar para ativar logs de queries (debug)
# command: ["postgres", "-c", "log_statement=all"]
# ============================================
# Serviço: Aplicação Next.js
# ============================================
app:
build:
context: .
dockerfile: Dockerfile
image: felipegcoutinho/openmonetis:latest
container_name: openmonetis_app
restart: unless-stopped
ports:
# Mapeia porta 3000 do container para 3000 do host
- "${APP_PORT:-3000}:3000"
env_file:
- path: .env
required: false
environment:
# Variáveis de ambiente da aplicação
NODE_ENV: production
# DATABASE_URL do .env
# Banco local: use host "db" (serviço Docker)
# Banco remoto: use a URL completa do provider
DATABASE_URL: ${DATABASE_URL}
# Outras variáveis de ambiente necessárias
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
DATABASE_URL: ${DATABASE_URL:-postgresql://openmonetis:openmonetis_dev_password@db:5432/openmonetis_db}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-}
BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
# Configurações de email (se usar)
RESEND_API_KEY: ${RESEND_API_KEY:-}
RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-}
# Configurações de OAuth (se usar)
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-}
# Configurações de AI providers (se usar)
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY:-}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
# Só depende do 'db' se estiver usando banco local
# Para banco remoto, comente a linha abaixo ou suba apenas: docker compose up app
BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-}
depends_on:
db:
condition: service_healthy
networks:
- openmonetis_network
# Script de inicialização: roda migrations antes de iniciar o app
# ATENÇÃO: Em produção, considere rodar migrations separadamente por segurança
entrypoint: ["/bin/sh", "-c"]
command:
- |
echo "🚀 Aguardando banco de dados..."
sleep 5
echo "📦 Rodando migrations..."
pnpm db:push || echo "⚠️ Migrations falharam ou já estão atualizadas"
echo "✅ Iniciando aplicação Next.js..."
node server.js
# Healthcheck da aplicação
required: false
healthcheck:
test:
[
"CMD",
"wget",
"--quiet",
"--tries=1",
"--spider",
"http://localhost:3000/api/health",
]
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# ============================================
# Volumes
# ============================================
volumes:
postgres_data:
name: openmonetis_postgres_data
driver: local
# ============================================
# Networks
# ============================================
networks:
openmonetis_network:
name: openmonetis_network
driver: bridge

16
docker-entrypoint.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
echo "Rodando migrations..."
MIGRATED=0
for i in 1 2 3 4 5; do
if NODE_PATH=/app/migrate/node_modules /app/migrate/node_modules/.bin/drizzle-kit push; then
MIGRATED=1
break
fi
echo "Tentativa $i/5 falhou. Aguardando 5s..."
sleep 5
done
[ "$MIGRATED" -eq 0 ] && echo "Aviso: migrations não foram aplicadas."
exec "$@"

View File

@@ -0,0 +1 @@
-- placeholder: migration aplicada via db:push, arquivo original não preservado

View File

@@ -0,0 +1,37 @@
CREATE TABLE "anexos" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"chave_arquivo" text NOT NULL,
"nome_arquivo" text NOT NULL,
"tamanho_bytes" integer NOT NULL,
"mime_type" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "anexos_chave_arquivo_unique" UNIQUE("chave_arquivo")
);
--> statement-breakpoint
CREATE TABLE "dashboard_notification_states" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"notification_key" text NOT NULL,
"fingerprint" text NOT NULL,
"read_at" timestamp with time zone,
"archived_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "lancamento_anexos" (
"lancamento_id" uuid NOT NULL,
"anexo_id" uuid NOT NULL,
CONSTRAINT "lancamento_anexos_lancamento_id_anexo_id_pk" PRIMARY KEY("lancamento_id","anexo_id")
);
--> statement-breakpoint
ALTER TABLE "anexos" ADD CONSTRAINT "anexos_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "dashboard_notification_states" ADD CONSTRAINT "dashboard_notification_states_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "lancamento_anexos" ADD CONSTRAINT "lancamento_anexos_lancamento_id_lancamentos_id_fk" FOREIGN KEY ("lancamento_id") REFERENCES "public"."lancamentos"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "lancamento_anexos" ADD CONSTRAINT "lancamento_anexos_anexo_id_anexos_id_fk" FOREIGN KEY ("anexo_id") REFERENCES "public"."anexos"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "dashboard_notification_states_user_id_key_unique" ON "dashboard_notification_states" USING btree ("user_id","notification_key");--> statement-breakpoint
CREATE INDEX "dashboard_notification_states_user_id_archived_idx" ON "dashboard_notification_states" USING btree ("user_id","archived_at");--> statement-breakpoint
CREATE INDEX "lancamento_anexos_anexo_id_idx" ON "lancamento_anexos" USING btree ("anexo_id");--> statement-breakpoint
ALTER TABLE "preferencias_usuario" DROP COLUMN "system_font";--> statement-breakpoint
ALTER TABLE "preferencias_usuario" DROP COLUMN "money_font";

View File

@@ -0,0 +1 @@
ALTER TABLE "preferencias_usuario" ADD COLUMN "attachment_max_size_mb" integer DEFAULT 50 NOT NULL;

View File

@@ -0,0 +1,24 @@
DROP INDEX "tokens_api_user_id_idx";--> statement-breakpoint
DROP INDEX "cartoes_user_id_status_idx";--> statement-breakpoint
DROP INDEX "dashboard_notification_states_user_id_archived_idx";--> statement-breakpoint
DROP INDEX "contas_user_id_status_idx";--> statement-breakpoint
DROP INDEX "antecipacoes_parcelas_series_id_idx";--> statement-breakpoint
DROP INDEX "pagadores_user_id_status_idx";--> statement-breakpoint
DROP INDEX "pagadores_user_id_role_idx";--> statement-breakpoint
CREATE INDEX "account_user_id_idx" ON "account" USING btree ("userId");--> statement-breakpoint
CREATE INDEX "anexos_user_id_idx" ON "anexos" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "orcamentos_categoria_id_idx" ON "orcamentos" USING btree ("categoria_id");--> statement-breakpoint
CREATE INDEX "cartoes_conta_id_idx" ON "cartoes" USING btree ("conta_id");--> statement-breakpoint
CREATE INDEX "import_category_mappings_category_id_idx" ON "import_category_mappings" USING btree ("category_id");--> statement-breakpoint
CREATE INDEX "pre_lancamentos_lancamento_id_idx" ON "pre_lancamentos" USING btree ("lancamento_id");--> statement-breakpoint
CREATE INDEX "antecipacoes_parcelas_lancamento_id_idx" ON "antecipacoes_parcelas" USING btree ("lancamento_id");--> statement-breakpoint
CREATE INDEX "antecipacoes_parcelas_pagador_id_idx" ON "antecipacoes_parcelas" USING btree ("pagador_id");--> statement-breakpoint
CREATE INDEX "antecipacoes_parcelas_categoria_id_idx" ON "antecipacoes_parcelas" USING btree ("categoria_id");--> statement-breakpoint
CREATE INDEX "anotacoes_user_id_idx" ON "anotacoes" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "passkey_user_id_idx" ON "passkey" USING btree ("userId");--> statement-breakpoint
CREATE INDEX "compartilhamentos_pagador_shared_with_user_id_idx" ON "compartilhamentos_pagador" USING btree ("shared_with_user_id");--> statement-breakpoint
CREATE INDEX "compartilhamentos_pagador_created_by_user_id_idx" ON "compartilhamentos_pagador" USING btree ("created_by_user_id");--> statement-breakpoint
CREATE INDEX "session_user_id_idx" ON "session" USING btree ("userId");--> statement-breakpoint
CREATE INDEX "lancamentos_conta_id_idx" ON "lancamentos" USING btree ("conta_id");--> statement-breakpoint
CREATE INDEX "lancamentos_categoria_id_idx" ON "lancamentos" USING btree ("categoria_id");--> statement-breakpoint
CREATE INDEX "lancamentos_antecipacao_id_idx" ON "lancamentos" USING btree ("antecipacao_id");

View File

@@ -0,0 +1,2 @@
ALTER TABLE "lancamentos" ADD COLUMN "split_group_id" uuid;--> statement-breakpoint
CREATE INDEX "lancamentos_user_id_split_group_id_idx" ON "lancamentos" USING btree ("user_id","split_group_id");

View File

@@ -0,0 +1,2 @@
ALTER TABLE "preferencias_usuario" DROP COLUMN "system_font";--> statement-breakpoint
ALTER TABLE "preferencias_usuario" DROP COLUMN "money_font";

View File

@@ -0,0 +1,14 @@
CREATE TABLE "dashboard_notification_states" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"notification_key" text NOT NULL,
"fingerprint" text NOT NULL,
"read_at" timestamp with time zone,
"archived_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "dashboard_notification_states" ADD CONSTRAINT "dashboard_notification_states_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "dashboard_notification_states_user_id_key_unique" ON "dashboard_notification_states" USING btree ("user_id","notification_key");--> statement-breakpoint
CREATE INDEX "dashboard_notification_states_user_id_archived_idx" ON "dashboard_notification_states" USING btree ("user_id","archived_at");

View File

@@ -0,0 +1 @@
ALTER TABLE "pagadores" ALTER COLUMN "share_code" DROP DEFAULT;

View File

@@ -0,0 +1,3 @@
ALTER TABLE "cartoes" ALTER COLUMN "limite" SET DEFAULT '0';--> statement-breakpoint
UPDATE "cartoes" SET "limite" = '0' WHERE "limite" IS NULL;--> statement-breakpoint
ALTER TABLE "cartoes" ALTER COLUMN "limite" SET NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "preferencias_usuario" ADD COLUMN "mostrar_resumo_lancamento" boolean DEFAULT true NOT NULL;

View File

@@ -0,0 +1,9 @@
CREATE TABLE "anotacao_anexos" (
"anotacao_id" uuid NOT NULL,
"anexo_id" uuid NOT NULL,
CONSTRAINT "anotacao_anexos_anotacao_id_anexo_id_pk" PRIMARY KEY("anotacao_id","anexo_id")
);
--> statement-breakpoint
ALTER TABLE "anotacao_anexos" ADD CONSTRAINT "anotacao_anexos_anotacao_id_anotacoes_id_fk" FOREIGN KEY ("anotacao_id") REFERENCES "public"."anotacoes"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "anotacao_anexos" ADD CONSTRAINT "anotacao_anexos_anexo_id_anexos_id_fk" FOREIGN KEY ("anexo_id") REFERENCES "public"."anexos"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "anotacao_anexos_anexo_id_idx" ON "anotacao_anexos" USING btree ("anexo_id");

View File

@@ -0,0 +1 @@
ALTER TABLE "preferencias_usuario" ADD COLUMN "ocultar_parcelas_antecipadas" boolean DEFAULT false NOT NULL;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -162,6 +162,69 @@
"when": 1748000000000,
"tag": "0022_import-category-mappings",
"breakpoints": true
},
{
"idx": 23,
"version": "7",
"when": 1774529878374,
"tag": "0023_sturdy_wolfpack",
"breakpoints": true
},
{
"idx": 24,
"version": "7",
"when": 1774891206703,
"tag": "0024_petite_lucky_pierre",
"breakpoints": true
},
{
"idx": 25,
"version": "7",
"when": 1776351838548,
"tag": "0025_burly_colonel_america",
"breakpoints": true
},
{
"idx": 26,
"version": "7",
"when": 1777042423451,
"tag": "0026_bored_eternity",
"breakpoints": true
},
{
"idx": 28,
"version": "7",
"when": 1777153372633,
"tag": "0028_fancy_reaper",
"breakpoints": true
},
{
"idx": 29,
"version": "7",
"when": 1777648189399,
"tag": "0029_friendly_spitfire",
"breakpoints": true
},
{
"idx": 30,
"version": "7",
"when": 1780150535055,
"tag": "0030_complete_umar",
"breakpoints": true
},
{
"idx": 31,
"version": "7",
"when": 1782051007412,
"tag": "0031_lame_cerise",
"breakpoints": true
},
{
"idx": 32,
"version": "7",
"when": 1782569103402,
"tag": "0032_bumpy_spencer_smythe",
"breakpoints": true
}
]
}

24
knip.jsonc Normal file
View File

@@ -0,0 +1,24 @@
{
"$schema": "https://unpkg.com/knip@6/schema-jsonc.json",
// Exclude shared UI primitives from dead code reporting while we focus the
// cleanup on feature and domain code first.
"ignore": [
"src/shared/components/ui/**"
],
// Runtime asset referenced by string in the PDF viewer.
"ignoreFiles": [
"public/pdf.worker.min.mjs",
"setup.mjs"
],
// PostCSS is inferred from the config file, but the project only depends on
// the Tailwind PostCSS plugin directly.
// `server-only` is provided implicitly by Next.js — no install needed.
"ignoreDependencies": [
"postcss",
"server-only"
],
"next": true,
"postcss": true,
"biome": true,
"drizzle": true
}

View File

@@ -6,16 +6,24 @@ dotenv.config();
const nextConfig: NextConfig = {
output: "standalone",
experimental: {
turbopackFileSystemCacheForDev: true,
},
cacheComponents: true,
reactCompiler: true,
images: {
remotePatterns: [new URL("https://lh3.googleusercontent.com/**")],
remotePatterns: [
new URL("https://lh3.googleusercontent.com/**"),
{ protocol: "https", hostname: "**" },
{ protocol: "http", hostname: "**" },
],
},
devIndicators: {
position: "bottom-right",
},
experimental: {
prefetchInlining: true,
turbopackFileSystemCacheForDev: true,
optimizePackageImports: ["@remixicon/react"],
},
// Headers for Safari compatibility
async headers() {
return [
@@ -39,8 +47,12 @@ const nextConfig: NextConfig = {
value: "DENY",
},
{
key: "Content-Security-Policy",
value: "frame-ancestors 'none';",
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "X-Permitted-Cross-Domain-Policies",
value: "none",
},
{
key: "Permissions-Policy",

View File

@@ -1,54 +1,62 @@
{
"name": "openmonetis",
"version": "2.0.1",
"version": "2.7.10",
"private": true,
"packageManager": "pnpm@11.1.3",
"scripts": {
"dev": "next dev --turbopack",
"dev-env": "tsx scripts/dev.ts",
"db:seed": "tsx scripts/mock-data.ts",
"build": "next build",
"start": "next start",
"lint": "biome check .",
"lint:deadcode": "knip --reporter compact",
"lint:fix": "biome check --write .",
"env:setup": "bash scripts/setup-env.sh",
"env:setup": "node setup.mjs",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:enableExtensions": "tsx scripts/postgres/enable-extensions.ts",
"db:studio": "drizzle-kit studio",
"docker:up": "docker compose up --build",
"docker:up:db": "docker compose up -d db",
"docker:up:d": "docker compose up --build -d",
"postinstall": "cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs",
"docker:up": "docker compose up -d",
"//docker:up": "Sobe app (Docker Hub) + banco PostgreSQL em background",
"docker:db": "docker compose up -d db",
"//docker:db": "Sobe apenas o banco em background (para usar com pnpm dev)",
"docker:down": "docker compose down",
"docker:down:volumes": "docker compose down -v",
"//docker:down": "Para e remove os containers",
"docker:logs": "docker compose logs -f",
"docker:logs:app": "docker compose logs -f app",
"docker:logs:db": "docker compose logs -f db",
"docker:restart": "docker compose restart",
"docker:rebuild": "docker compose up --build --force-recreate",
"backup": "bash scripts/backup.sh"
"//docker:logs": "Acompanha logs de todos os containers em tempo real",
"docker:update": "docker compose pull && docker compose up -d",
"//docker:update": "Atualiza para a imagem mais recente do Docker Hub e reinicia",
"backup": "bash scripts/backup.sh",
"mockup": "tsx scripts/mock-data.ts"
},
"dependencies": {
"@ai-sdk/anthropic": "^3.0.63",
"@ai-sdk/google": "^3.0.52",
"@ai-sdk/openai": "^3.0.47",
"@better-auth/passkey": "^1.5.5",
"@ai-sdk/anthropic": "^3.0.88",
"@ai-sdk/google": "^3.0.85",
"@ai-sdk/openai": "^3.0.76",
"@ai-sdk/openai-compatible": "^2.0.53",
"@aws-sdk/client-s3": "^3.1075.0",
"@aws-sdk/s3-request-presigner": "^3.1075.0",
"@better-auth/passkey": "^1.6.22",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@openrouter/ai-sdk-provider": "^2.3.3",
"@openrouter/ai-sdk-provider": "^2.10.0",
"@radix-ui/react-alert-dialog": "1.1.15",
"@radix-ui/react-avatar": "1.1.11",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",
"@radix-ui/react-dialog": "1.1.15",
"@radix-ui/react-dropdown-menu": "2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-hover-card": "^1.1.17",
"@radix-ui/react-label": "2.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.16",
"@radix-ui/react-popover": "^1.1.17",
"@radix-ui/react-progress": "1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-radio-group": "^1.4.1",
"@radix-ui/react-select": "2.2.6",
"@radix-ui/react-separator": "1.1.8",
"@radix-ui/react-slider": "^1.4.1",
"@radix-ui/react-slot": "1.2.4",
"@radix-ui/react-switch": "1.2.6",
"@radix-ui/react-tabs": "1.1.13",
@@ -56,47 +64,50 @@
"@radix-ui/react-toggle-group": "1.1.11",
"@radix-ui/react-tooltip": "1.2.8",
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "^5.101.1",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "^3.13.23",
"@vercel/analytics": "^2.0.1",
"@vercel/speed-insights": "^2.0.0",
"ai": "^6.0.134",
"better-auth": "1.5.5",
"@tanstack/react-virtual": "^3.14.4",
"ai": "^6.0.213",
"better-auth": "1.6.22",
"canvas-confetti": "^1.9.4",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"drizzle-orm": "0.45.1",
"date-fns": "^4.4.0",
"drizzle-orm": "0.45.2",
"exceljs": "^4.4.0",
"jspdf": "^4.2.1",
"jspdf-autotable": "^5.0.7",
"next": "16.1.7",
"jspdf-autotable": "^5.0.8",
"next": "16.2.7",
"next-themes": "0.4.6",
"pg": "8.20.0",
"radix-ui": "^1.4.3",
"react": "19.2.4",
"react-day-picker": "^9.14.0",
"react-dom": "19.2.4",
"recharts": "3.8.0",
"resend": "^6.9.4",
"pdfjs-dist": "^6.0.227",
"pg": "8.21.0",
"react": "19.2.7",
"react-day-picker": "^10.0.1",
"react-dom": "19.2.7",
"recharts": "3.8.1",
"resend": "^6.16.0",
"sonner": "2.0.7",
"tailwind-merge": "3.5.0",
"tailwind-merge": "3.6.0",
"tw-animate-css": "^1.4.0",
"vaul": "1.1.2",
"xlsx": "^0.18.5",
"zod": "4.3.6"
"vercel-minimax-ai-provider": "^0.0.2",
"zod": "4.4.3"
},
"devDependencies": {
"@biomejs/biome": "2.4.8",
"@tailwindcss/postcss": "4.2.2",
"@biomejs/biome": "2.4.16",
"@tailwindcss/postcss": "4.3.0",
"@types/canvas-confetti": "^1.9.0",
"@types/node": "25.5.0",
"@types/node": "25.9.1",
"@types/pg": "^8.20.0",
"@types/react": "19.2.14",
"@types/react": "19.2.16",
"@types/react-dom": "19.2.3",
"dotenv": "^17.3.1",
"babel-plugin-react-compiler": "^1.0.0",
"dotenv": "^17.4.2",
"drizzle-kit": "0.31.10",
"tailwindcss": "4.2.2",
"tsx": "4.21.0",
"typescript": "5.9.3"
"knip": "^6.22.0",
"tailwindcss": "4.3.0",
"tsx": "4.22.4",
"typescript": "6.0.3"
}
}

6342
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,13 @@
onlyBuiltDependencies:
- core-js
- esbuild
- sharp
- unrs-resolver
packages:
- '.'
allowBuilds:
core-js: true
esbuild: true
sharp: true
unrs-resolver: true
minimumReleaseAge: 0
overrides:
defu: 6.1.7

View File

@@ -0,0 +1,4 @@
Contact: https://github.com/felipegcoutinho/openmonetis/security/advisories
Expires: 2027-04-04T00:00:00.000Z
Preferred-Languages: pt-BR, en
Canonical: https://openmonetis.com/.well-known/security.txt

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

View File

@@ -1,15 +1,10 @@
import localFont from "next/font/local";
import { Bricolage_Grotesque } from "next/font/google";
export const america = localFont({
src: [
{
path: "./america-regular.woff2",
weight: "400",
style: "normal",
},
],
export const bricolage = Bricolage_Grotesque({
subsets: ["latin"],
display: "swap",
variable: "--font-america",
variable: "--font-bricolage",
fallback: ["arial", "ui-sans-serif", "system-ui"],
weight: ["500", "600", "700"],
preload: true,
});
export const americaFontVariable = america.variable;

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" aria-label="OpenMonetis" role="img" viewBox="0 0 200 200"><path fill="#f73" d="M 77.66,165.64 L 37.77,141.54 L 63.30,108.72 L 27.13,97.44 L 46.81,50.77 L 77.66,63.08 L 81.91,30.26 L 126.40,29.23 L 126.06,33.85 L 122.87,67.69 L 158.51,50.77 L 178.19,90.26 L 140.96,104.62 L 162.23,127.18 L 132.98,162.56 L 103.19,131.79 Z"/></svg>

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

37
public/llms.txt Normal file
View File

@@ -0,0 +1,37 @@
# OpenMonetis
> OpenMonetis is a self-hosted personal finance web app for manual financial control. It helps users manage accounts, cards, invoices, budgets, notes, reports, attachments, and AI-generated insights. The product UI is in Brazilian Portuguese, the codebase uses English folder and import names, and there is no hosted SaaS version.
>
> **Stack:** Next.js 16, React 19, PostgreSQL, Drizzle ORM, Better Auth, Tailwind CSS 4, shadcn/ui. Package manager: pnpm. Linter: Biome.
OpenMonetis is meant to be deployed by the user on their own machine or server.
There is no Open Finance or automatic bank synchronization.
Transactions can be entered manually or imported from OFX and XLS/XLSX files.
Attachments are optional and require S3-compatible storage.
The public website is mainly a landing page; the main technical documentation lives in the GitHub repository.
## Docs
- [Landing page](/): Public homepage and high-level product overview
- [README](https://github.com/felipegcoutinho/openmonetis/blob/main/README.md): Main project documentation covering features, installation, Docker, environment variables, architecture, contributing, and license
- [CHANGELOG](https://github.com/felipegcoutinho/openmonetis/blob/main/CHANGELOG.md): Release history and notable changes
- [LICENSE](https://github.com/felipegcoutinho/openmonetis/blob/main/LICENSE): CC BY-NC-SA 4.0 license terms
## Setup
- [Setup script](https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/setup.mjs): Interactive installer for local or self-hosted setup
- [Environment example](https://github.com/felipegcoutinho/openmonetis/blob/main/.env.example): Required and optional environment variables
- [Docker Compose](https://github.com/felipegcoutinho/openmonetis/blob/main/docker-compose.yml): Local app and PostgreSQL stack definition
## Architecture
- [CLAUDE.md](https://github.com/felipegcoutinho/openmonetis/blob/main/CLAUDE.md): Project architecture, naming rules, query rules, and feature checklist
## Optional
- [robots.txt](/robots.txt): Crawl policy for the public site
## Related Projects
- [OpenMonetis Companion](https://github.com/felipegcoutinho/openmonetis-companion): Android app that captures bank notifications and sends them to the OpenMonetis inbox for review

BIN
public/logos/bipa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
public/logos/dinheiro.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

29
public/pdf.worker.min.mjs Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" style="flex:none;line-height:1" width="1em" viewBox="0 0 24 24"><title>MiniMax</title><defs><linearGradient id="lobe-icons-minimax-gradient" x1="0%" x2="100.182%" y1="50.057%" y2="50.057%"><stop offset="0%" stop-color="#E2167E"/><stop offset="100%" stop-color="#FE603C"/></linearGradient></defs><path fill="url(#lobe-icons-minimax-gradient)" fill-rule="nonzero" d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -58,21 +58,26 @@ DATA_FILE="$BACKUP_DIR/openmonetis_${TIMESTAMP}.data.sql.gz"
log "Iniciando backup (modo: $DB_MODE)..."
# Schemas relevantes do OpenMonetis (descarta lixo Supabase: auth, realtime, storage, vault, graphql, etc.)
SCHEMA_FLAGS=(--schema=public --schema=drizzle)
# --- Dump ---
if [[ "$DB_MODE" == "remote" ]]; then
# --no-owner --no-privileges: necessário no Supabase (roles gerenciados internamente)
pg_dump --format=custom --no-owner --no-privileges \
"${SCHEMA_FLAGS[@]}" \
"$REMOTE_DB_URL" > "$DUMP_FILE"
pg_dump --no-owner --no-privileges \
"${SCHEMA_FLAGS[@]}" \
"$REMOTE_DB_URL" | gzip > "$SQL_FILE"
elif [[ "$DB_MODE" == "docker" ]]; then
docker exec "$DOCKER_CONTAINER" pg_dump \
-U "$DOCKER_DB_USER" -Fc "$DOCKER_DB_NAME" > "$DUMP_FILE"
-U "$DOCKER_DB_USER" -Fc "${SCHEMA_FLAGS[@]}" "$DOCKER_DB_NAME" > "$DUMP_FILE"
docker exec "$DOCKER_CONTAINER" pg_dump \
-U "$DOCKER_DB_USER" "$DOCKER_DB_NAME" | gzip > "$SQL_FILE"
-U "$DOCKER_DB_USER" "${SCHEMA_FLAGS[@]}" "$DOCKER_DB_NAME" | gzip > "$SQL_FILE"
else
log "ERRO: DB_MODE inválido ('$DB_MODE'). Use 'remote' ou 'docker'."
@@ -81,7 +86,7 @@ fi
# Extrai dados puros do dump custom (sem nova conexão ao banco)
pg_restore --data-only --schema=public --no-owner --no-privileges \
"$DUMP_FILE" | gzip > "$DATA_FILE"
-f - "$DUMP_FILE" | gzip > "$DATA_FILE"
log "Dump concluído: $(du -sh "$DUMP_FILE" | cut -f1) (.dump) | $(du -sh "$SQL_FILE" | cut -f1) (.sql.gz) | $(du -sh "$DATA_FILE" | cut -f1) (.data.sql.gz)"

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env tsx
import { execSync } from "node:child_process";
import { config } from "dotenv";
// Carregar variáveis de ambiente
config();
const port = process.env.PORT || "3000";
console.log(`Starting Next.js development server on port ${port}...`);
// Executar next dev com a porta especificada
execSync(`npx next dev --turbopack --port ${port}`, {
stdio: "inherit",
env: { ...process.env, PORT: port },
});

245
scripts/install-deps.sh Executable file
View File

@@ -0,0 +1,245 @@
#!/bin/sh
# install-deps.sh — Instala pré-requisitos do OpenMonetis
# Testado apenas em Ubuntu Server 24.04 LTS
# Uso: curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/scripts/install-deps.sh -o install-deps.sh
# sudo sh install-deps.sh
set -e
LOG_FILE="/tmp/openmonetis-install.log"
> "$LOG_FILE"
# Suprimir prompt interativo do corepack ao chamar pnpm/node versioning
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
# ── Cores ──────────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
ok() { printf "${GREEN}${RESET} %s\n" "$1"; }
warn() { printf "${YELLOW}!${RESET} %s\n" "$1"; }
info() { printf "${CYAN}${RESET} %s\n" "$1"; }
fail() { printf "${RED}${RESET} %s\n" "$1"; exit 1; }
# ── Contador de etapas ─────────────────────────────────────────────────────────
_STEP=0
_TOTAL=5
section() {
_STEP=$((_STEP + 1))
printf "\n${BOLD}[%d/%d] %s${RESET}\n" "$_STEP" "$_TOTAL" "$1"
}
# ── Spinner ────────────────────────────────────────────────────────────────────
_spin_pid=""
spinner_start() {
_spin_label="$1"
( i=0
while true; do
case $((i % 4)) in
0) d=" " ;; 1) d=". " ;; 2) d=".. " ;; *) d="..." ;;
esac
printf "\r${CYAN}${RESET} %s%s" "$_spin_label" "$d"
i=$((i + 1))
sleep 0.4
done
) &
_spin_pid=$!
}
spinner_stop() {
if [ -n "$_spin_pid" ]; then
kill "$_spin_pid" 2>/dev/null || true
wait "$_spin_pid" 2>/dev/null || true
_spin_pid=""
printf "\r\033[2K"
fi
}
# ── Executores silenciosos com spinner ─────────────────────────────────────────
# run_quiet "label" cmd [args...] — roda comando com spinner, falha mostra log
run_quiet() {
_rq_label="$1"; shift
spinner_start "$_rq_label"
if ! "$@" >> "$LOG_FILE" 2>&1; then
spinner_stop
printf "${RED}✗ Falha em: %s${RESET}\n" "$_rq_label"
printf " Log completo: %s\n\n" "$LOG_FILE"
tail -20 "$LOG_FILE"
exit 1
fi
spinner_stop
}
# run_as_user "label" "comando_shell" — roda comando como $CURRENT_USER com spinner
run_as_user() {
_ru_label="$1"; shift
spinner_start "$_ru_label"
if ! su - "$CURRENT_USER" -c "$*" >> "$LOG_FILE" 2>&1; then
spinner_stop
printf "${RED}✗ Falha em: %s${RESET}\n" "$_ru_label"
printf " Log completo: %s\n\n" "$LOG_FILE"
tail -20 "$LOG_FILE"
exit 1
fi
spinner_stop
}
# ── Cleanup no Ctrl+C ──────────────────────────────────────────────────────────
cleanup() {
spinner_stop
printf "\n${YELLOW}Instalação interrompida.${RESET} Log em: %s\n" "$LOG_FILE"
exit 1
}
trap cleanup INT TERM
# ── Tempo total ────────────────────────────────────────────────────────────────
_START=$(date +%s)
elapsed() {
_secs=$(( $(date +%s) - _START ))
printf "%dm%ds" $((_secs / 60)) $((_secs % 60))
}
# ── Root check ─────────────────────────────────────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
fail "Execute como root ou com sudo: sudo sh install-deps.sh"
fi
CURRENT_USER="${SUDO_USER:-$(whoami)}"
printf "\n${BOLD}OpenMonetis — Instalação de Dependências${RESET}\n"
printf "Usuário: ${CYAN}%s${RESET} | Log: %s\n" "$CURRENT_USER" "$LOG_FILE"
# ── [1/5] Dependências base ────────────────────────────────────────────────────
section "Dependências base"
run_quiet "Atualizando lista de pacotes" apt-get update -qq
run_quiet "Instalando git, curl, ca-certificates" apt-get install -y -qq ca-certificates curl git
ok "git $(git --version | cut -d' ' -f3) · curl · ca-certificates"
# ── [2/5] Docker ───────────────────────────────────────────────────────────────
section "Docker"
if command -v docker > /dev/null 2>&1; then
ok "Docker já instalado: $(docker --version | cut -d',' -f1)"
else
info "Adicionando repositório oficial do Docker..."
install -m 0755 -d /etc/apt/keyrings
run_quiet "Baixando chave GPG do Docker" \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
. /etc/os-release
mkdir -p /etc/apt/sources.list.d
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu %s stable\n' \
"$(dpkg --print-architecture)" "$VERSION_CODENAME" \
> /etc/apt/sources.list.d/docker.list
run_quiet "Atualizando lista de pacotes" apt-get update -qq
run_quiet "Instalando Docker Engine (pode levar alguns minutos)" \
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable docker > /dev/null 2>&1 || true
systemctl start docker > /dev/null 2>&1 || true
ok "Docker $(docker --version | cut -d',' -f1 | cut -d' ' -f3) instalado"
fi
if docker compose version > /dev/null 2>&1; then
ok "Docker Compose $(docker compose version | cut -d' ' -f4)"
else
run_quiet "Instalando Docker Compose plugin" \
sh -c 'mkdir -p /usr/local/lib/docker/cli-plugins && curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" -o /usr/local/lib/docker/cli-plugins/docker-compose && chmod +x /usr/local/lib/docker/cli-plugins/docker-compose'
ok "Docker Compose $(docker compose version | cut -d' ' -f4) instalado"
fi
if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then
if ! groups "$CURRENT_USER" | grep -q docker; then
usermod -aG docker "$CURRENT_USER"
warn "Usuário '$CURRENT_USER' adicionado ao grupo docker — faça logout/login para aplicar"
else
ok "Usuário '$CURRENT_USER' já está no grupo docker"
fi
fi
# ── [3/5] Homebrew ─────────────────────────────────────────────────────────────
section "Homebrew"
if command -v brew > /dev/null 2>&1; then
ok "Homebrew já instalado: $(brew --version | head -1)"
else
warn "Esta etapa pode levar de 5 a 10 minutos."
run_quiet "Instalando dependências de compilação" \
apt-get install -y -qq build-essential procps file
if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then
run_as_user "Instalando Homebrew" \
'NONINTERACTIVE=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
BREW_PROFILE="/home/$CURRENT_USER/.bashrc"
BREW_EVAL='eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"'
grep -qxF "$BREW_EVAL" "$BREW_PROFILE" 2>/dev/null || echo "$BREW_EVAL" >> "$BREW_PROFILE"
export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 2>/dev/null || true
else
fail "Homebrew não pode ser instalado como root. Use sudo com um usuário normal."
fi
ok "Homebrew instalado"
fi
# ── [4/5] Node.js 22 ───────────────────────────────────────────────────────────
section "Node.js 22"
NODE_MAJOR=0
if command -v node > /dev/null 2>&1; then
NODE_MAJOR=$(node -e "process.stdout.write(String(parseInt(process.versions.node)))")
fi
if [ "$NODE_MAJOR" -ge 22 ] 2>/dev/null; then
ok "Node.js já instalado: $(node --version)"
else
warn "Node.js via Homebrew pode levar alguns minutos."
if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then
run_as_user "Instalando Node.js 22" \
'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && brew install node@22 && brew link node@22 --force --overwrite'
export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 2>/dev/null || true
else
fail "Node.js via Homebrew não pode ser instalado como root."
fi
ok "Node.js $(node --version) instalado"
fi
# ── [5/5] pnpm ─────────────────────────────────────────────────────────────────
section "pnpm"
if command -v pnpm > /dev/null 2>&1; then
ok "pnpm já instalado: $(pnpm --version)"
else
if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then
run_as_user "Instalando pnpm via corepack" \
'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack prepare pnpm@latest --activate'
else
run_quiet "Instalando pnpm via corepack" \
sh -c 'corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack prepare pnpm@latest --activate'
fi
ok "pnpm instalado"
fi
# ── Resumo ─────────────────────────────────────────────────────────────────────
# Garantir que node/pnpm do brew estejam no PATH para o resumo
export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 2>/dev/null || true
printf "\n${BOLD}Concluído em $(elapsed)${RESET}\n"
ok "git: $(git --version | cut -d' ' -f3)"
ok "docker: $(docker --version | cut -d',' -f1 | cut -d' ' -f3)"
ok "docker compose: $(docker compose version | cut -d' ' -f4)"
ok "node: $(node --version)"
ok "pnpm: $(pnpm --version)"

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +0,0 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { config } from "dotenv";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
// Load environment variables from .env
config();
async function initDatabase() {
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
console.error("DATABASE_URL environment variable is required");
process.exit(1);
}
const pool = new Pool({ connectionString: databaseUrl });
const db = drizzle(pool);
try {
console.log("🔧 Initializing database extensions...");
// Read and execute init.sql as a single query
const initSqlPath = path.join(
process.cwd(),
"scripts",
"postgres",
"init.sql",
);
const initSql = fs.readFileSync(initSqlPath, "utf-8");
console.log("Executing init.sql...");
await db.execute(initSql);
console.log("✅ Database initialization completed");
} catch (error) {
console.error("❌ Database initialization failed:", error);
process.exit(1);
} finally {
await pool.end();
}
}
initDatabase();

View File

@@ -1,10 +0,0 @@
-- Script de inicialização do PostgreSQL para Docker
-- Este script é executado automaticamente quando o banco é criado pela primeira vez
-- Habilitar extensão pgcrypto (necessária para gen_random_bytes usado pelo Drizzle)
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Log de sucesso
DO $$
BEGIN
RAISE NOTICE '✅ Extensão pgcrypto habilitada com sucesso';
END $$;

View File

@@ -1,49 +0,0 @@
#!/bin/bash
# Script para configurar ambiente de forma segura
# Cria backup do .env atual antes de sobrescrever
set -e
echo "🔧 Configurando ambiente..."
# Se .env já existe, criar backup
if [ -f .env ]; then
BACKUP_FILE=".env.backup.$(date +%Y%m%d_%H%M%S)"
echo "⚠️ Arquivo .env existente detectado!"
echo "📦 Criando backup em: $BACKUP_FILE"
cp .env "$BACKUP_FILE"
echo "✅ Backup criado com sucesso!"
echo ""
read -p "Deseja sobrescrever o .env atual com .env.example? (s/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Ss]$ ]]; then
echo "❌ Operação cancelada. Seu .env não foi modificado."
exit 0
fi
fi
# Copiar .env.example para .env
if [ -f .env.example ]; then
cp .env.example .env
echo "✅ Arquivo .env criado a partir de .env.example"
else
echo "❌ Erro: .env.example não encontrado!"
exit 1
fi
# Gerar BETTER_AUTH_SECRET automaticamente
if command -v openssl &> /dev/null; then
SECRET=$(openssl rand -base64 32)
sed -i.bak "s|BETTER_AUTH_SECRET=.*|BETTER_AUTH_SECRET=$SECRET|" .env && rm -f .env.bak
echo "✅ BETTER_AUTH_SECRET gerado automaticamente"
else
echo "⚠️ openssl não encontrado — configure BETTER_AUTH_SECRET manualmente:"
echo " openssl rand -base64 32"
fi
echo ""
echo "⚠️ IMPORTANTE: Edite o arquivo .env e configure:"
echo " - DATABASE_URL"
echo " - BETTER_AUTH_URL"
echo " - Demais variáveis opcionais (OAuth, e-mail, IA)"

View File

@@ -21,6 +21,7 @@ const c = {
red: "\x1b[31m",
yellow: "\x1b[33m",
cyan: "\x1b[36m",
orange: "\x1b[38;5;214m",
};
const sym = {
@@ -81,10 +82,38 @@ function abort(msg) {
// ─── Header ──────────────────────────────────────────────────────────────────
console.log(`
${c.bold}${c.cyan} OpenMonetis — Setup${c.reset}
${c.dim}Gestão financeira self-hosted${c.reset}
`);
const logoLines = [
".............................+@@@@@@@@@@=.............................",
".............................@@@@@@@@@@@:.............................",
"...................+@@@@@@*-:@@@@@@@@@@%...=@@@@@@-...................",
"..................@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%..................",
"................=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+................",
"...................-=+%@@@@@@@@@@@@@@@@@@@@@*:........................",
".......................#@@@@@@@@@@@@@@@@@@@@@@@+......................",
"....................%@@@@@@@@@@@@@%#@@@@@@@@@@@@*.....................",
"....................+@@@@@@@@@@@......*@@@@@@#........................",
".........................:#@@=...........+#...........................",
];
const nameLines = [
" ___ __ __ _ _ ",
" / _ \\ _ __ ___ _ __ | \\/ | ___ _ __ ___| |_(_)___ ",
" | | | | '_ \\ / _ \\ '_ \\| |\\/| |/ _ \\| '_ \\ / _ \\ __| / __|",
" | |_| | |_) | __/ | | | | | | (_) | | | | __/ |_| \\__ \\",
" \\___/| .__/ \\___|_| |_|_| |_|\\___/|_| |_|\\___|\\__|_|___/",
" |_| ",
];
const nameStart = Math.floor((logoLines.length - nameLines.length) / 2);
console.log();
for (let i = 0; i < logoLines.length; i++) {
const logoCol = c.orange + logoLines[i].replaceAll(".", " ").substring(14, 56).padEnd(42) + c.reset;
const nameIdx = i - nameStart;
const nameCol = nameIdx >= 0 && nameIdx < nameLines.length ? nameLines[nameIdx] : "";
console.log(logoCol + " " + nameCol);
}
console.log(`\n${" ".repeat(46)}${c.dim}Gestão financeira · self-hosted${c.reset}\n`);
// ─── ETAPA 1: Verificações do sistema ────────────────────────────────────────
@@ -200,14 +229,22 @@ if (await askYesNo(" E-mail via Resend (notificações e convites)?")) {
let anthropicKey = "";
let openaiKey = "";
let googleAiKey = "";
let minimaxKey = "";
let openrouterKey = "";
if (await askYesNo(" Insights com IA (Claude, GPT, Gemini, OpenRouter)?")) {
let ollamaBaseUrl = "";
let ollamaApiKey = "";
if (await askYesNo(" Insights com IA (Claude, GPT, Gemini, MiniMax, OpenRouter)?")) {
console.log(` ${c.dim}Deixe em branco o que não for usar${c.reset}`);
anthropicKey = await ask(" ANTHROPIC_API_KEY: ");
openaiKey = await ask(" OPENAI_API_KEY: ");
googleAiKey = await ask(" GOOGLE_GENERATIVE_AI_API_KEY: ");
minimaxKey = await ask(" MINIMAX_API_KEY: ");
openrouterKey = await ask(" OPENROUTER_API_KEY: ");
}
if (await askYesNo(" Insights locais com Ollama?")) {
ollamaBaseUrl = await askDefault(" OLLAMA_BASE_URL", "http://localhost:11434/v1");
ollamaApiKey = await ask(" OLLAMA_API_KEY (opcional): ");
}
// Domínio público
let publicDomain = "";
@@ -256,6 +293,9 @@ const envContent = [
"# === Better Auth ===",
`BETTER_AUTH_SECRET=${authSecret}`,
`BETTER_AUTH_URL=${betterAuthUrl}`,
"DISABLE_SIGNUP=false",
"AUTH_SESSION_EXPIRES_IN_DAYS=30",
"AUTH_SESSION_UPDATE_AGE_HOURS=24",
"",
"# === Portas ===",
"APP_PORT=3000",
@@ -281,7 +321,10 @@ const envContent = [
opt("ANTHROPIC_API_KEY", anthropicKey),
opt("OPENAI_API_KEY", openaiKey),
opt("GOOGLE_GENERATIVE_AI_API_KEY", googleAiKey),
opt("MINIMAX_API_KEY", minimaxKey),
opt("OPENROUTER_API_KEY", openrouterKey),
opt("OLLAMA_BASE_URL", ollamaBaseUrl),
opt("OLLAMA_API_KEY", ollamaApiKey),
].join("\n");
writeFileSync(join(targetDir, ".env"), envContent);
@@ -329,7 +372,7 @@ if (useLocalDocker) {
// Extensões
s = spinner("Habilitando extensões do banco...");
try {
run("pnpm db:enableExtensions", { cwd: targetDir });
run("pnpm db:extensions", { cwd: targetDir });
s.stop("Extensões habilitadas");
} catch {
s.fail("Falha ao habilitar extensões");

23
src/app/(auth)/layout.tsx Normal file
View File

@@ -0,0 +1,23 @@
import { Logo } from "@/shared/components/brand/logo";
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="relative flex min-h-svh flex-col items-center justify-center overflow-hidden bg-linear-to-b from-background via-background to-muted/20 px-5 py-8 md:px-8 md:py-10">
<div className="pointer-events-none absolute inset-0 overflow-hidden flex items-center justify-center">
<div className="absolute -right-32 top-0 h-96 w-96 rounded-full bg-primary/10 blur-3xl animate-blob mix-blend-multiply" />
<div className="absolute -left-32 bottom-0 h-96 w-96 rounded-full bg-primary/7 blur-3xl animate-blob animation-delay-2000 mix-blend-multiply" />
<div className="absolute -bottom-32 left-1/2 h-80 w-80 rounded-full bg-secondary/30 blur-3xl animate-blob animation-delay-4000 mix-blend-multiply" />
</div>
<div className="relative mb-6 flex md:hidden z-20">
<Logo variant="compact" colorIcon />
</div>
<div className="relative w-full max-w-sm md:max-w-5xl">{children}</div>
</div>
);
}

View File

@@ -1,11 +1,6 @@
import { LoginForm } from "@/features/auth/components/login-form";
import { isSignupDisabled } from "@/shared/lib/auth/signup";
export default function LoginPage() {
return (
<div className="flex min-h-svh flex-col items-center justify-center bg-linear-to-b from-background via-background to-muted/20 px-5 py-8 md:px-8 md:py-10">
<div className="w-full max-w-sm md:max-w-5xl">
<LoginForm />
</div>
</div>
);
return <LoginForm signupDisabled={isSignupDisabled()} />;
}

View File

@@ -1,11 +1,11 @@
import { redirect } from "next/navigation";
import { SignupForm } from "@/features/auth/components/signup-form";
import { isSignupDisabled } from "@/shared/lib/auth/signup";
export default function Page() {
return (
<div className="flex min-h-svh flex-col items-center justify-center bg-linear-to-b from-background via-background to-muted/20 px-5 py-8 md:px-8 md:py-10">
<div className="w-full max-w-sm md:max-w-5xl">
<SignupForm />
</div>
</div>
);
export default function SignupPage() {
if (isSignupDisabled()) {
redirect("/login");
}
return <SignupForm />;
}

View File

@@ -1,12 +1,15 @@
import { RiPencilLine } from "@remixicon/react";
import { notFound } from "next/navigation";
import { connection } from "next/server";
import { AccountDialog } from "@/features/accounts/components/account-dialog";
import { AccountStatementCard } from "@/features/accounts/components/account-statement-card";
import { AddYieldDialog } from "@/features/accounts/components/add-yield-dialog";
import { AdjustBalanceDialog } from "@/features/accounts/components/adjust-balance-dialog";
import type { Account } from "@/features/accounts/components/types";
import {
fetchAccountData,
fetchAccountLancamentosPage,
fetchAccountSummary,
fetchAccountTransactionsPage,
} from "@/features/accounts/statement-queries";
import { fetchUserPreferences } from "@/features/settings/queries";
import { TransactionsPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
@@ -20,7 +23,7 @@ import {
mapTransactionsData,
type ResolvedSearchParams,
resolveTransactionPagination,
} from "@/features/transactions/page-helpers";
} from "@/features/transactions/lib/page-helpers";
import {
fetchRecentEstablishments,
fetchTransactionFilterSources,
@@ -29,6 +32,7 @@ import MonthNavigation from "@/shared/components/month-picker/month-navigation";
import { Button } from "@/shared/components/ui/button";
import { getUserId } from "@/shared/lib/auth/server";
import { loadLogoOptions } from "@/shared/lib/logo/options";
import { getBusinessDateString } from "@/shared/utils/date";
import { parsePeriodParam } from "@/shared/utils/period";
type PageSearchParams = Promise<ResolvedSearchParams>;
@@ -41,7 +45,28 @@ type PageProps = {
const capitalize = (value: string) =>
value.length > 0 ? value[0]?.toUpperCase().concat(value.slice(1)) : value;
const resolveDefaultPaymentMethod = (
accountType: string | null | undefined,
) => {
if (accountType === "Dinheiro") return "Dinheiro";
if (accountType === "Pré-Pago | VR/VA") return "Pré-Pago | VR/VA";
return "Pix";
};
const resolveDefaultYieldDate = (period: string) => {
const today = getBusinessDateString();
if (today.startsWith(period)) return today;
const [year, month] = period.split("-").map((part) => Number(part));
if (!year || !month) return today;
const lastDay = new Date(year, month, 0).getDate();
return `${period}-${String(lastDay).padStart(2, "0")}`;
};
export default async function Page({ params, searchParams }: PageProps) {
await connection();
const { accountId } = await params;
const userId = await getUserId();
const resolvedSearchParams = searchParams ? await searchParams : undefined;
@@ -84,9 +109,11 @@ export default async function Page({ params, searchParams }: PageProps) {
filters: searchFilters,
slugMaps,
accountId: account.id,
hideAnticipatedInstallments:
userPreferences?.hideAnticipatedInstallments ?? false,
});
const transactionsPage = await fetchAccountLancamentosPage(
const transactionsPage = await fetchAccountTransactionsPage(
filters,
pagination,
);
@@ -97,6 +124,7 @@ export default async function Page({ params, searchParams }: PageProps) {
accountSummary;
const periodLabel = `${capitalize(monthName)} de ${year}`;
const defaultYieldDate = resolveDefaultYieldDate(selectedPeriod);
const accountDialogData: Account = {
id: account.id,
@@ -139,6 +167,19 @@ export default async function Page({ params, searchParams }: PageProps) {
totalIncomes={totalIncomes}
totalExpenses={totalExpenses}
logo={account.logo}
balanceAdjustment={
<>
<AddYieldDialog
accountId={account.id}
defaultDate={defaultYieldDate}
/>
<AdjustBalanceDialog
accountId={account.id}
period={selectedPeriod}
currentBalance={currentBalance}
/>
</>
}
actions={
<AccountDialog
mode="update"
@@ -187,9 +228,14 @@ export default async function Page({ params, searchParams }: PageProps) {
accountId: account.id,
settledOnly: true,
}}
allowCreate={false}
allowCreate
defaultAccountId={account.id}
defaultPaymentMethod={resolveDefaultPaymentMethod(
account.accountType,
)}
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50}
/>
</section>
</main>

View File

@@ -11,13 +11,12 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<section className="space-y-6 pt-4">
<section className="space-y-6">
<PageDescription
icon={<RiBankLine />}
title="Contas"
subtitle="Acompanhe todas as contas do mês selecionado incluindo receitas,
despesas e transações previstas. Use o seletor abaixo para navegar pelos
meses e visualizar as movimentações correspondentes."
despesas e transações previstas."
/>
{children}
</section>

View File

@@ -1,8 +1,10 @@
import { connection } from "next/server";
import { AccountsPage } from "@/features/accounts/components/accounts-page";
import { fetchAllAccountsForUser } from "@/features/accounts/queries";
import { getUserId } from "@/shared/lib/auth/server";
export default async function Page() {
await connection();
const userId = await getUserId();
const { activeAccounts, archivedAccounts, logoOptions } =
await fetchAllAccountsForUser(userId);

View File

@@ -0,0 +1,25 @@
import { RiAttachmentLine } from "@remixicon/react";
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
import PageDescription from "@/shared/components/page-description";
export const metadata = {
title: "Anexos",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section className="space-y-6">
<PageDescription
icon={<RiAttachmentLine />}
title="Anexos"
subtitle="Gerencie os anexos das suas transações"
/>
<MonthNavigation />
{children}
</section>
);
}

View File

@@ -0,0 +1,38 @@
import { Skeleton } from "@/shared/components/ui/skeleton";
export default function AnexosLoading() {
return (
<main className="flex flex-col gap-6">
<div className="w-full space-y-6">
{/* Header */}
<Skeleton className="h-10 w-40 rounded-md bg-foreground/10" />
{/* Month navigation */}
<Skeleton className="h-10 w-64 rounded-md bg-foreground/10" />
{/* Count */}
<Skeleton className="h-4 w-20 rounded-md bg-foreground/10" />
{/* Grid */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
{Array.from({ length: 10 }).map((_, i) => (
<div
key={i}
className="flex flex-col overflow-hidden rounded-lg border"
>
<Skeleton className="aspect-square w-full bg-foreground/10" />
<div className="space-y-1.5 p-2.5">
<Skeleton className="h-3 w-3/4 rounded bg-foreground/10" />
<Skeleton className="h-3 w-full rounded bg-foreground/10" />
<div className="flex justify-between">
<Skeleton className="h-3 w-16 rounded bg-foreground/10" />
<Skeleton className="h-3 w-12 rounded bg-foreground/10" />
</div>
</div>
</div>
))}
</div>
</div>
</main>
);
}

Some files were not shown because too many files have changed in this diff Show More