Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ DIRECT_URL=postgresql://postgres.YOUR_REF:PASSWORD@aws-0-sa-east-1.pooler.supaba
# Auth — lista de emails permitidos separados por vírgula
ALLOWED_EMAILS=giordanna@example.com,john@example.com

# App
NEXT_PUBLIC_APP_URL=https://archtime.netlify.app
# App — em produção é https://archtime.app; no dev local use a origem local
NEXT_PUBLIC_APP_URL=http://localhost:3000

# Security — segredo legado/transitório usado para assinar hashes de ponto
ENTRY_HASH_SECRET=replace-with-a-long-random-secret
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/build-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: build-image

# Builda a imagem de container e publica no GitHub Container Registry (ghcr.io),
# de onde o Azure App Service (Brazil South) puxa. Roda em push na main (ignorando
# mudanças só de documentação) e sob demanda. NÃO afeta a produção atual da Netlify.
# mudanças só de documentação) e sob demanda. Em push na main, dispara o deploy de
# produção no Azure App Service via webhook de CD (passo final deste workflow).
#
# Os NEXT_PUBLIC_* são valores públicos inlinados no bundle client em build time
# (secrets do repo). Os segredos de RUNTIME (DATABASE_URL, ENTRY_HASH_SECRET,
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ jobs:
# módulos client. Nenhum segredo real é necessário — os testes mockam o Prisma.
NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY: placeholder-anon-key
NEXT_PUBLIC_APP_URL: https://archtime.netlify.app
NEXT_PUBLIC_APP_URL: https://archtime.app
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22 # espelha NODE_VERSION do netlify.toml
node-version: 22 # mesma major do runtime de produção (Dockerfile: node:22-bookworm-slim)
cache: npm
- run: npm ci
- run: npx prisma generate
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Observações:

- Todas as escritas devem passar por API Routes. Não escreva direto no banco a partir de Client Components.
- Escritas relevantes devem preservar auditoria em `AuditLog`.
- Clock-out calcula hash SHA-256 da entrada.
- Clock-out e edição de sessão calculam um HMAC-SHA256 sobre os campos de integridade. Com o keyring configurado (produção), o formato é versionado por `keyId` (`hmac-v1:<keyId>:<digest>`); sem keyring, o fallback legado emite `hmac-v1:<digest>`. Ver ADR 0005.
- Leituras simples podem usar Supabase client com RLS.
- Agregações, joins e lógica sensível devem usar API Route com Prisma.
- `src/app/layout.tsx` é Server Component.
Expand Down Expand Up @@ -97,10 +97,10 @@ Regras duras:

## Infraestrutura e Performance

- Deploy em **Azure App Service** (container Linux B1, região Brazil South); produção em `https://archtime.app`. Imagem em `ghcr.io/johnlaff/archtime`, publicada pelo workflow `build-image` (push na `main`) e puxada pelo App Service via webhook de continuous deployment.
- Deploy em **Azure App Service** (container Linux B1, região Brazil South); produção em `https://archtime.app`. Imagem em `ghcr.io/johnlaff/archtime`, publicada pelo workflow `build-image` (push na `main`, exceto mudanças só de docs) e puxada pelo App Service via webhook de continuous deployment.
- Banco em Supabase PostgreSQL (`sa-east-1`/São Paulo — mesma região do App Service, latência app↔banco baixa).
- Há latência relevante entre funções serverless e banco; evite colocar reads simples no caminho crítico de navegação quando client-direct com RLS for suficiente.
- Cold starts podem afetar navegação. Prefira cache, lazy loading, code splitting e leituras client-direct quando seguro.
- App e banco estão co-locados no Brasil (App Service Brazil South + Supabase São Paulo) e o B1 roda com **Always On** — a instância fica sempre carregada, sem cold start de ociosidade. Em regime, nem a latência ao banco nem cold start são gargalos de navegação; o custo residual de um read no servidor é o hop extra (cliente→app→banco) e a disputa pelo worker único do B1. Prefira client-direct com RLS para reads simples que dispensam o servidor.
- Cold start só aparece após deploy/restart (a imagem nova recicla a instância), não em navegação estável. Cache, lazy loading e code splitting continuam valendo como boa prática.
- Performance de navegação não pode degradar.
- Para bibliotecas pesadas, prefira lazy-load/code-split em vez de reimplementar tudo custom por reflexo.

Expand Down
7 changes: 3 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# syntax=docker/dockerfile:1
#
# Imagem de deploy do ArchTime para Azure App Service (container, região Brazil South).
# A Netlify NÃO usa este arquivo — o build de produção lá segue inalterado. O output
# `standalone` só é ligado aqui via BUILD_STANDALONE=true (o build da Netlify não seta
# essa var), então a produção atual não sofre regressão.
# Imagem de deploy do ArchTime para Azure App Service (container, região Brazil South) —
# o hosting de produção. O output `standalone` só é ligado no build da imagem via
# BUILD_STANDALONE=true; fora dela, o output permanece o padrão do Next.

FROM node:22-bookworm-slim AS base
# NODE_ENV NÃO é setado aqui de propósito: `deps` herda de `base`, e `npm ci` com
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ sem rede** — o registro entra numa fila local e sincroniza sozinho quando a co
| 🎨 | **Projetos com cores** | Cada sessão é alocada a um projeto; cores dão leitura visual imediata no histórico e nos gráficos. |
| 📊 | **Insights** | Heatmap anual e barras semanais **relativos à jornada** — enxerga padrões de esforço, não só totais brutos. |
| 🗂️ | **Histórico auditável** | Edição de registros com trilha de auditoria; filtros por projeto e atividade sem deslocar a UI. |
| 🔐 | **Integridade dos registros** | Cada sessão fechada carrega um **HMAC-SHA256** com `keyId`; `/api/integrity` distingue formato inválido, adulteração e uma chave histórica indisponível. |
| 🔐 | **Integridade dos registros** | Cada sessão fechada carrega um **HMAC-SHA256** (versionado por `keyId` quando o keyring está configurado, como em produção); `/api/integrity` distingue formato inválido, adulteração e uma chave histórica indisponível. |
| 📲 | **PWA instalável** | Service worker (Serwist), ícone/manifest e experiência de app nativo no celular. |

## Arquitetura
Expand Down Expand Up @@ -115,7 +115,7 @@ src/
Produção em **Azure App Service** (container Linux B1, Brazil South), servida em
**[archtime.app](https://archtime.app)** (TLS gerenciado; `www` → apex). A imagem é publicada no
**GitHub Container Registry** (`ghcr.io/johnlaff/archtime`) pelo workflow `build-image` a cada push
na `main`, e o App Service faz o pull automático via **webhook** de continuous deployment.
na `main` (exceto mudanças só de documentação), e o App Service faz o pull automático via **webhook** de continuous deployment.

> [!IMPORTANT]
> O banco (Supabase) é **compartilhado entre ambientes** — cuidado com dados reais ao validar
Expand Down
5 changes: 2 additions & 3 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ const CSP_REPORT_ONLY = [
].join('; ')

const nextConfig: NextConfig = {
// Standalone só é ligado no build do container (Azure), via BUILD_STANDALONE=true.
// O build da Netlify NÃO seta essa var, então lá o output permanece o padrão
// (o @netlify/plugin-nextjs gerencia o output) — zero regressão na produção atual.
// Standalone só é ligado no build do container (Azure, o hosting de produção) via
// BUILD_STANDALONE=true. Sem essa var, o output permanece o padrão do Next.
output: process.env.BUILD_STANDALONE === 'true' ? 'standalone' : undefined,
cacheComponents: true,
experimental: {
Expand Down
13 changes: 7 additions & 6 deletions plans/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ foi re-despachado; os achados de RLS (#2, #3) e Host (#7) vieram da 2ª.
| 011 | Higiene: README real, SVGs órfãos, Node 22 | P3 | S | 001 | DONE |
| 012 | Guard de resposta obsoleta no useSupabaseQuery | P3 | S | — | DONE |
| 013 | Spike de design: faturamento (horas × hourlyRate) | P3 | M | 006 | DONE (design em docs/plans/2026-07-03-faturamento-design.md; 6 questões abertas aguardando o mantenedor) |
| 014 | Sincronizar schema Prisma + completar lockdown RLS (migration 0007) | P1 | S | — | DONE (migration 0007 criada; APLICAÇÃO em produção pendente do operador — ver STOP condition) |
| 014 | Sincronizar schema Prisma + completar lockdown RLS (migration 0007) | P1 | S | — | DONE (migration 0007 aplicada e verificada em produção em 2026-07-10 — colunas/índices de drift presentes; RLS de `users`/`user_settings` sem policy INSERT/UPDATE) |
| 015 | Tratar falha do IndexedDB no clock-in offline | P1 | S | — | DONE |
| 016 | Guarda condicional no UPDATE de clock-out (race) | P2 | M | — | DONE |
| 017 | Remover código morto e duplicado (sweep de hygiene) | P3 | S | — | DONE (migration 0008 criada; APLICAÇÃO em produção pendente do operador — ver STOP condition) |
| 017 | Remover código morto e duplicado (sweep de hygiene) | P3 | S | — | DONE (migration 0008 aplicada e verificada em produção em 2026-07-10 — coluna `audit_log.ip_address` removida) |
| 018 | Corrigir env var fantasma e docs de setup/segurança | P3 | S | 014 (recomendado) | DONE |
| 019 | Gate de react-doctor no CI | P2 | S | — | DONE |
| 020 | Adicionar linter (Biome) e remover `eslint-disable` mortos | P3 | M | — | DONE (linter-only: formatter e recommended desligados p/ evitar reformatar 342 linhas / 95 violações de estilo — full recommended fica p/ sweep separado) |
Expand All @@ -58,9 +58,9 @@ Status values: TODO | IN PROGRESS | DONE | BLOCKED (com motivo em uma linha) | R
e o 005 renomeia o mock de `@/lib/hour-bank` usado neles.
- **003 antes de 008 (recomendado)**: a verificação de hash assume que escrita client-direct já
não existe; sem o lockdown ela ainda funciona, mas o cenário limpo é pós-003.
- **011 requer 001**: o bump de Node atualiza `netlify.toml` e o `ci.yml` juntos.
- **011 requer 001**: à época, o bump de Node atualizava `netlify.toml` e o `ci.yml` juntos.
- **013 requer 006**: o spike de faturamento consome o contrato numérico de `hourlyRate` na API.
- **003 e 008 tocam produção** (banco único, previews compartilham o DB — ADR 0003): a APLICAÇÃO
- **003 e 008 tocam produção** (banco único; à época, os previews da Netlify compartilhavam o DB — ADR 0003): a APLICAÇÃO
da migration do 003 é do operador, nunca do executor (STOP condition explícita no plano).

### Rodada 2
Expand All @@ -76,8 +76,9 @@ Status values: TODO | IN PROGRESS | DONE | BLOCKED (com motivo em uma linha) | R
conflitam (gates diferentes); podem ir em qualquer ordem ou em paralelo.
- **023 e 024 são aditivos**: só criam test files, não tocam código de produção. Podem rodar em
paralelo entre si e com qualquer outro plano.
- **014 e 017 tocam produção** (migrations 0007/0008): a APLICAÇÃO em produção é do operador,
nunca do executor (STOP conditions explícitas em ambos).
- **014 e 017 tocaram produção** (migrations 0007/0008): a aplicação foi do operador, nunca do
executor (STOP conditions explícitas em ambos). Ambas já estão aplicadas e verificadas em
produção (ledger `_prisma_migrations` com 0000–0008 completas em 2026-07-10).
- **015 é independente e LOW risk**: toca só `src/hooks/use-clock.ts` (cliente); pode rodar a
qualquer momento, inclusive antes de 016 (que toca o server).

Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const fraunces = Fraunces({
axes: ['opsz'],
})

const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? 'https://archtime.netlify.app'
const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? 'https://archtime.app'

export const metadata: Metadata = {
title: 'ArchTime',
Expand Down
2 changes: 1 addition & 1 deletion src/lib/server/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function validateMutationOrigin(req: NextRequest): NextResponse | null {
if (appOrigin) allowed.add(appOrigin)

// req.nextUrl.origin deriva do header Host, que só é confiável quando a infra o
// saneia (o edge do Netlify o faz). Só confie nele sozinho quando casa com
// saneia (o App Service roteia por hostname e rejeita Host não-configurado). Só confie nele sozinho quando casa com
// NEXT_PUBLIC_APP_URL ou é um origin local (dev) — nunca incondicionalmente, senão
// um Host spoofado (Host: evil.com) passaria no check de CSRF. Ver ADR 0004.
const nextUrlOrigin = normalizeOrigin(req.nextUrl.origin)
Expand Down
Loading