From b0bf09afa2fd4013092f3a46304d3eae3556ad71 Mon Sep 17 00:00:00 2001 From: guuszz Date: Sun, 2 Aug 2026 12:07:57 -0300 Subject: [PATCH] =?UTF-8?q?consolida=20API=20em=20uma=20fun=C3=A7=C3=A3o?= =?UTF-8?q?=20Vercel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vercelignore | 4 +- README.md | 3 +- api/[...route].ts | 69 +++++++++++++++++++ e2e/server.ts | 32 ++++----- {api => server}/_lib/auth.test.ts | 0 {api => server}/_lib/auth.ts | 0 {api => server}/_lib/authorization.ts | 0 {api => server}/_lib/http.ts | 0 {api => server}/_lib/observability.test.ts | 0 {api => server}/_lib/observability.ts | 0 {api => server}/_lib/order-status.ts | 0 {api => server}/_lib/organization.ts | 0 {api => server}/_lib/pagination.ts | 0 {api => server}/_lib/prisma.ts | 0 {api => server}/_lib/rate-limit.test.ts | 0 {api => server}/_lib/rate-limit.ts | 0 {api => server}/auth/login.ts | 0 {api => server}/auth/logout.ts | 0 {api => server}/auth/me.ts | 0 {api => server}/auth/register.ts | 0 {api => server}/clientes/index.ts | 0 {api => server}/health.ts | 0 {api => server}/invitations/[id].ts | 0 {api => server}/invitations/accept.ts | 0 {api => server}/invitations/index.ts | 0 {api => server}/ordens/[id]/status.ts | 0 {api => server}/ordens/index.ts | 0 {api => server}/organizations/current.ts | 0 {api => server}/organizations/members/[id].ts | 0 .../organizations/members/index.ts | 0 {api => server}/organizations/switch.ts | 0 {api => server}/test/database.ts | 0 {api => server}/test/global-setup.ts | 0 {api => server}/test/http.ts | 0 {api => server}/veiculos/[id]/foto.ts | 0 {api => server}/veiculos/index.ts | 0 {api => server}/workflows.integration.test.ts | 0 tsconfig.api.json | 1 + vitest.config.ts | 6 +- vitest.integration.config.ts | 6 +- 40 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 api/[...route].ts rename {api => server}/_lib/auth.test.ts (100%) rename {api => server}/_lib/auth.ts (100%) rename {api => server}/_lib/authorization.ts (100%) rename {api => server}/_lib/http.ts (100%) rename {api => server}/_lib/observability.test.ts (100%) rename {api => server}/_lib/observability.ts (100%) rename {api => server}/_lib/order-status.ts (100%) rename {api => server}/_lib/organization.ts (100%) rename {api => server}/_lib/pagination.ts (100%) rename {api => server}/_lib/prisma.ts (100%) rename {api => server}/_lib/rate-limit.test.ts (100%) rename {api => server}/_lib/rate-limit.ts (100%) rename {api => server}/auth/login.ts (100%) rename {api => server}/auth/logout.ts (100%) rename {api => server}/auth/me.ts (100%) rename {api => server}/auth/register.ts (100%) rename {api => server}/clientes/index.ts (100%) rename {api => server}/health.ts (100%) rename {api => server}/invitations/[id].ts (100%) rename {api => server}/invitations/accept.ts (100%) rename {api => server}/invitations/index.ts (100%) rename {api => server}/ordens/[id]/status.ts (100%) rename {api => server}/ordens/index.ts (100%) rename {api => server}/organizations/current.ts (100%) rename {api => server}/organizations/members/[id].ts (100%) rename {api => server}/organizations/members/index.ts (100%) rename {api => server}/organizations/switch.ts (100%) rename {api => server}/test/database.ts (100%) rename {api => server}/test/global-setup.ts (100%) rename {api => server}/test/http.ts (100%) rename {api => server}/veiculos/[id]/foto.ts (100%) rename {api => server}/veiculos/index.ts (100%) rename {api => server}/workflows.integration.test.ts (100%) diff --git a/.vercelignore b/.vercelignore index 415abb3..1d1d058 100644 --- a/.vercelignore +++ b/.vercelignore @@ -1,6 +1,6 @@ # Testes sob api/ seriam interpretados como funções serverless pela Vercel. -api/**/*.test.ts -api/test/ +server/**/*.test.ts +server/test/ # Artefatos e infraestrutura usados apenas no pipeline local/CI. e2e/ diff --git a/README.md b/README.md index 082abfc..2c8dbcf 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,8 @@ curl -X POST http://localhost:3000/api/clientes \ ## 📂 Estrutura ```txt -api/ # Vercel Serverless Functions +api/ # roteador serverless único da Vercel +server/ # handlers, domínio e infraestrutura da API ├── _lib/ # auth, prisma e helpers compartilhados ├── auth/ # register, login, me ├── clientes/ # endpoints de clientes diff --git a/api/[...route].ts b/api/[...route].ts new file mode 100644 index 0000000..3261818 --- /dev/null +++ b/api/[...route].ts @@ -0,0 +1,69 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node'; +import type { IncomingMessage, ServerResponse } from 'node:http'; +import loginHandler from '../server/auth/login.js'; +import registerHandler from '../server/auth/register.js'; +import meHandler from '../server/auth/me.js'; +import logoutHandler from '../server/auth/logout.js'; +import clientesHandler from '../server/clientes/index.js'; +import veiculosHandler from '../server/veiculos/index.js'; +import fotoHandler from '../server/veiculos/[id]/foto.js'; +import ordensHandler from '../server/ordens/index.js'; +import statusHandler from '../server/ordens/[id]/status.js'; +import healthHandler from '../server/health.js'; +import currentOrganizationHandler from '../server/organizations/current.js'; +import switchOrganizationHandler from '../server/organizations/switch.js'; +import membersHandler from '../server/organizations/members/index.js'; +import memberHandler from '../server/organizations/members/[id].js'; +import invitationsHandler from '../server/invitations/index.js'; +import invitationHandler from '../server/invitations/[id].js'; +import acceptInvitationHandler from '../server/invitations/accept.js'; + +type Handler = (req: VercelRequest, res: VercelResponse) => unknown | Promise; + +const staticRoutes: Record = { + '/api/auth/login': loginHandler, + '/api/auth/register': registerHandler, + '/api/auth/me': meHandler, + '/api/auth/logout': logoutHandler, + '/api/clientes': clientesHandler, + '/api/veiculos': veiculosHandler, + '/api/ordens': ordensHandler, + '/api/health': healthHandler, + '/api/organizations/current': currentOrganizationHandler, + '/api/organizations/switch': switchOrganizationHandler, + '/api/organizations/members': membersHandler, + '/api/invitations': invitationsHandler, + '/api/invitations/accept': acceptInvitationHandler, +}; + +const dynamicRoutes: Array<{ + pattern: RegExp; + handler: Handler; +}> = [ + { pattern: /^\/api\/veiculos\/([^/]+)\/foto$/, handler: fotoHandler }, + { pattern: /^\/api\/ordens\/([^/]+)\/status$/, handler: statusHandler }, + { pattern: /^\/api\/organizations\/members\/([^/]+)$/, handler: memberHandler }, + { pattern: /^\/api\/invitations\/([^/]+)$/, handler: invitationHandler }, +]; + +export default async function router(req: VercelRequest, res: VercelResponse) { + const pathname = new URL(req.url ?? '/', 'http://localhost').pathname.replace(/\/$/, ''); + const staticHandler = staticRoutes[pathname]; + if (staticHandler) return staticHandler(req, res); + + for (const route of dynamicRoutes) { + const match = pathname.match(route.pattern); + if (!match) continue; + req.query = { ...req.query, id: decodeURIComponent(match[1]) }; + return route.handler(req, res); + } + + return res.status(404).json({ + message: 'Rota não encontrada', + error: { code: 'NOT_FOUND', message: 'Rota não encontrada' }, + }); +} + +// Mantém compatibilidade de tipos com o adaptador Node usado localmente. +export type NodeRequest = IncomingMessage; +export type NodeResponse = ServerResponse; diff --git a/e2e/server.ts b/e2e/server.ts index 16c59ba..0b24999 100644 --- a/e2e/server.ts +++ b/e2e/server.ts @@ -3,22 +3,22 @@ import { stat } from 'node:fs/promises'; import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'; import { extname, resolve } from 'node:path'; import type { VercelRequest, VercelResponse } from '@vercel/node'; -import loginHandler from '../api/auth/login.js'; -import registerHandler from '../api/auth/register.js'; -import meHandler from '../api/auth/me.js'; -import logoutHandler from '../api/auth/logout.js'; -import clientesHandler from '../api/clientes/index.js'; -import veiculosHandler from '../api/veiculos/index.js'; -import ordensHandler from '../api/ordens/index.js'; -import statusHandler from '../api/ordens/[id]/status.js'; -import healthHandler from '../api/health.js'; -import currentOrganizationHandler from '../api/organizations/current.js'; -import switchOrganizationHandler from '../api/organizations/switch.js'; -import membersHandler from '../api/organizations/members/index.js'; -import memberHandler from '../api/organizations/members/[id].js'; -import invitationsHandler from '../api/invitations/index.js'; -import invitationHandler from '../api/invitations/[id].js'; -import acceptInvitationHandler from '../api/invitations/accept.js'; +import loginHandler from '../server/auth/login.js'; +import registerHandler from '../server/auth/register.js'; +import meHandler from '../server/auth/me.js'; +import logoutHandler from '../server/auth/logout.js'; +import clientesHandler from '../server/clientes/index.js'; +import veiculosHandler from '../server/veiculos/index.js'; +import ordensHandler from '../server/ordens/index.js'; +import statusHandler from '../server/ordens/[id]/status.js'; +import healthHandler from '../server/health.js'; +import currentOrganizationHandler from '../server/organizations/current.js'; +import switchOrganizationHandler from '../server/organizations/switch.js'; +import membersHandler from '../server/organizations/members/index.js'; +import memberHandler from '../server/organizations/members/[id].js'; +import invitationsHandler from '../server/invitations/index.js'; +import invitationHandler from '../server/invitations/[id].js'; +import acceptInvitationHandler from '../server/invitations/accept.js'; type Handler = (req: VercelRequest, res: VercelResponse) => unknown | Promise; diff --git a/api/_lib/auth.test.ts b/server/_lib/auth.test.ts similarity index 100% rename from api/_lib/auth.test.ts rename to server/_lib/auth.test.ts diff --git a/api/_lib/auth.ts b/server/_lib/auth.ts similarity index 100% rename from api/_lib/auth.ts rename to server/_lib/auth.ts diff --git a/api/_lib/authorization.ts b/server/_lib/authorization.ts similarity index 100% rename from api/_lib/authorization.ts rename to server/_lib/authorization.ts diff --git a/api/_lib/http.ts b/server/_lib/http.ts similarity index 100% rename from api/_lib/http.ts rename to server/_lib/http.ts diff --git a/api/_lib/observability.test.ts b/server/_lib/observability.test.ts similarity index 100% rename from api/_lib/observability.test.ts rename to server/_lib/observability.test.ts diff --git a/api/_lib/observability.ts b/server/_lib/observability.ts similarity index 100% rename from api/_lib/observability.ts rename to server/_lib/observability.ts diff --git a/api/_lib/order-status.ts b/server/_lib/order-status.ts similarity index 100% rename from api/_lib/order-status.ts rename to server/_lib/order-status.ts diff --git a/api/_lib/organization.ts b/server/_lib/organization.ts similarity index 100% rename from api/_lib/organization.ts rename to server/_lib/organization.ts diff --git a/api/_lib/pagination.ts b/server/_lib/pagination.ts similarity index 100% rename from api/_lib/pagination.ts rename to server/_lib/pagination.ts diff --git a/api/_lib/prisma.ts b/server/_lib/prisma.ts similarity index 100% rename from api/_lib/prisma.ts rename to server/_lib/prisma.ts diff --git a/api/_lib/rate-limit.test.ts b/server/_lib/rate-limit.test.ts similarity index 100% rename from api/_lib/rate-limit.test.ts rename to server/_lib/rate-limit.test.ts diff --git a/api/_lib/rate-limit.ts b/server/_lib/rate-limit.ts similarity index 100% rename from api/_lib/rate-limit.ts rename to server/_lib/rate-limit.ts diff --git a/api/auth/login.ts b/server/auth/login.ts similarity index 100% rename from api/auth/login.ts rename to server/auth/login.ts diff --git a/api/auth/logout.ts b/server/auth/logout.ts similarity index 100% rename from api/auth/logout.ts rename to server/auth/logout.ts diff --git a/api/auth/me.ts b/server/auth/me.ts similarity index 100% rename from api/auth/me.ts rename to server/auth/me.ts diff --git a/api/auth/register.ts b/server/auth/register.ts similarity index 100% rename from api/auth/register.ts rename to server/auth/register.ts diff --git a/api/clientes/index.ts b/server/clientes/index.ts similarity index 100% rename from api/clientes/index.ts rename to server/clientes/index.ts diff --git a/api/health.ts b/server/health.ts similarity index 100% rename from api/health.ts rename to server/health.ts diff --git a/api/invitations/[id].ts b/server/invitations/[id].ts similarity index 100% rename from api/invitations/[id].ts rename to server/invitations/[id].ts diff --git a/api/invitations/accept.ts b/server/invitations/accept.ts similarity index 100% rename from api/invitations/accept.ts rename to server/invitations/accept.ts diff --git a/api/invitations/index.ts b/server/invitations/index.ts similarity index 100% rename from api/invitations/index.ts rename to server/invitations/index.ts diff --git a/api/ordens/[id]/status.ts b/server/ordens/[id]/status.ts similarity index 100% rename from api/ordens/[id]/status.ts rename to server/ordens/[id]/status.ts diff --git a/api/ordens/index.ts b/server/ordens/index.ts similarity index 100% rename from api/ordens/index.ts rename to server/ordens/index.ts diff --git a/api/organizations/current.ts b/server/organizations/current.ts similarity index 100% rename from api/organizations/current.ts rename to server/organizations/current.ts diff --git a/api/organizations/members/[id].ts b/server/organizations/members/[id].ts similarity index 100% rename from api/organizations/members/[id].ts rename to server/organizations/members/[id].ts diff --git a/api/organizations/members/index.ts b/server/organizations/members/index.ts similarity index 100% rename from api/organizations/members/index.ts rename to server/organizations/members/index.ts diff --git a/api/organizations/switch.ts b/server/organizations/switch.ts similarity index 100% rename from api/organizations/switch.ts rename to server/organizations/switch.ts diff --git a/api/test/database.ts b/server/test/database.ts similarity index 100% rename from api/test/database.ts rename to server/test/database.ts diff --git a/api/test/global-setup.ts b/server/test/global-setup.ts similarity index 100% rename from api/test/global-setup.ts rename to server/test/global-setup.ts diff --git a/api/test/http.ts b/server/test/http.ts similarity index 100% rename from api/test/http.ts rename to server/test/http.ts diff --git a/api/veiculos/[id]/foto.ts b/server/veiculos/[id]/foto.ts similarity index 100% rename from api/veiculos/[id]/foto.ts rename to server/veiculos/[id]/foto.ts diff --git a/api/veiculos/index.ts b/server/veiculos/index.ts similarity index 100% rename from api/veiculos/index.ts rename to server/veiculos/index.ts diff --git a/api/workflows.integration.test.ts b/server/workflows.integration.test.ts similarity index 100% rename from api/workflows.integration.test.ts rename to server/workflows.integration.test.ts diff --git a/tsconfig.api.json b/tsconfig.api.json index 388ff63..80be97b 100644 --- a/tsconfig.api.json +++ b/tsconfig.api.json @@ -14,6 +14,7 @@ }, "include": [ "api/**/*.ts", + "server/**/*.ts", "e2e/**/*.ts", "playwright.config.ts", "prisma/**/*.ts", diff --git a/vitest.config.ts b/vitest.config.ts index 02554a5..ce2440d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ exclude: [ '**/node_modules/**', '**/dist/**', - 'api/**/*.integration.test.ts', + 'server/**/*.integration.test.ts', 'e2e/**/*.spec.ts', ], coverage: { @@ -30,8 +30,8 @@ export default defineConfig({ // A cobertura publicada mede apenas unidades com suíte dedicada. // Fluxos CRUD completos entram na futura suíte E2E/integração. include: [ - 'api/_lib/auth.ts', - 'api/_lib/observability.ts', + 'server/_lib/auth.ts', + 'server/_lib/observability.ts', 'src/lib/utils.ts', 'src/lib/useNumberTicker.ts', 'src/lib/useTheme.ts', diff --git a/vitest.integration.config.ts b/vitest.integration.config.ts index ae8b788..1d6af00 100644 --- a/vitest.integration.config.ts +++ b/vitest.integration.config.ts @@ -11,10 +11,10 @@ export default defineConfig({ test: { environment: 'node', include: [ - 'api/*.integration.test.ts', - 'api/**/*.integration.test.ts', + 'server/*.integration.test.ts', + 'server/**/*.integration.test.ts', ], - globalSetup: ['./api/test/global-setup.ts'], + globalSetup: ['./server/test/global-setup.ts'], fileParallelism: false, sequence: { concurrent: false,