Bootstrap EDEN: Fase 0 (arquitetura) e Fase 1 (monorepo + infra)

Fase 0 — descoberta e arquitetura:
- Inventário do projeto, glossário de domínio, arquitetura com bounded
  contexts e topologia de containers, threat model inicial.
- 12 ADRs cobrindo modular monolith, topologia de containers (Postgres
  isolado + eden-core/parceiros/assinante em containers e portas
  distintos), auth/sessões, modelo de permissões, criptografia/segredos,
  contrato first-class, stock ledger, separação billing/finance/fiscal,
  outbox transacional, adapters SaperX e Focus NFe, e identidade
  compartilhada entre as 3 apps.
- 14 subagentes e 7 skills especializados por domínio em .claude/.
- Hooks de segurança (PreToolUse/PostToolUse/Stop) testados via pipe.

Fase 1 — plataforma (em andamento):
- Monorepo pnpm workspaces + Turborepo: apps/{api,worker,core-web,
  reseller-web,subscriber-web} + 9 packages compartilhados.
- apps/api: NestJS mínimo com /health/live e /health/ready (checando
  Postgres real via @eden/database).
- 3 frontends Vite + React + TypeScript + Tailwind, com o favicon
  oficial do EDEN.
- packages/database: migration baseline (node-pg-migrate) criando
  roles/role_permissions/applications/users/user_applications/sessions/
  audit_log — audit log append-only com hash-chain, testado ao vivo
  (UPDATE/DELETE bloqueados pelo trigger).
- compose.yaml implementando a topologia da ADR-0002, validada de ponta
  a ponta: os 6 containers sobem e ficam saudáveis com um único
  `docker compose up`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 08:01:14 -03:00
commit 44510bd019
149 changed files with 13006 additions and 0 deletions

29
apps/api/package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "@eden/api",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node dist/main.js",
"dev": "tsx watch src/main.ts",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "echo 'no linter configured yet'",
"test": "echo 'no tests yet'"
},
"dependencies": {
"@eden/database": "workspace:*",
"@nestjs/common": "^10.4.15",
"@nestjs/core": "^10.4.15",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/terminus": "^10.2.3",
"express": "^4.21.2",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/node": "^22.10.5",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from "@nestjs/common";
import { TerminusModule } from "@nestjs/terminus";
import { HealthController } from "./health/health.controller";
@Module({
imports: [TerminusModule],
controllers: [HealthController],
})
export class AppModule {}

View File

@@ -0,0 +1,36 @@
import { Controller, Get } from "@nestjs/common";
import {
HealthCheck,
HealthCheckError,
HealthCheckService,
HealthIndicatorResult,
} from "@nestjs/terminus";
import { query } from "@eden/database";
@Controller("health")
export class HealthController {
constructor(private readonly health: HealthCheckService) {}
@Get("live")
live() {
// Liveness never depends on external services — only "is the process up".
return { status: "ok" };
}
@Get("ready")
@HealthCheck()
ready() {
return this.health.check([(): Promise<HealthIndicatorResult> => this.checkPostgres()]);
}
private async checkPostgres(): Promise<HealthIndicatorResult> {
try {
await query("SELECT 1");
return { postgres: { status: "up" } };
} catch (err) {
throw new HealthCheckError("postgres check failed", {
postgres: { status: "down", message: (err as Error).message },
});
}
}
}

13
apps/api/src/main.ts Normal file
View File

@@ -0,0 +1,13 @@
import "reflect-metadata";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const port = Number(process.env.EDEN_API_PORT ?? 8080);
await app.listen(port, "0.0.0.0");
// eslint-disable-next-line no-console
console.log(`[eden-api] listening on :${port}`);
}
bootstrap();

15
apps/api/tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"target": "ES2022",
"outDir": "dist",
"rootDir": "src",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictPropertyInitialization": false
},
"include": ["src"],
"exclude": ["dist", "node_modules"]
}

13
apps/core-web/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EDEN Core</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
{
"name": "@eden/core-web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json --noEmit && vite build",
"dev": "vite --port ${EDEN_CORE_PORT:-3001}",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "echo 'no linter configured yet'",
"test": "echo 'no tests yet'"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3",
"vite": "^6.0.7"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

12
apps/core-web/src/App.tsx Normal file
View File

@@ -0,0 +1,12 @@
export function App() {
return (
<main className="flex min-h-screen items-center justify-center bg-slate-950 text-slate-100">
<div className="text-center">
<h1 className="text-2xl font-semibold">EDEN Core</h1>
<p className="mt-2 text-slate-400">
Bootstrap da Fase 1 Design System ainda não portado do tema DreamsERP.
</p>
</div>
</main>
);
}

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

View File

@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"noEmit": true,
"isolatedModules": true
},
"include": ["src"]
}

View File

@@ -0,0 +1,10 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
host: true,
port: Number(process.env.EDEN_CORE_PORT ?? 3001),
},
});

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EDEN Parceiros</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
{
"name": "@eden/reseller-web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json --noEmit && vite build",
"dev": "vite --port ${EDEN_PARCEIROS_PORT:-3002}",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "echo 'no linter configured yet'",
"test": "echo 'no tests yet'"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3",
"vite": "^6.0.7"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

View File

@@ -0,0 +1,12 @@
export function App() {
return (
<main className="flex min-h-screen items-center justify-center bg-slate-950 text-slate-100">
<div className="text-center">
<h1 className="text-2xl font-semibold">EDEN Parceiros</h1>
<p className="mt-2 text-slate-400">
Bootstrap da Fase 1 Design System ainda não portado do tema DreamsERP.
</p>
</div>
</main>
);
}

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

View File

@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"noEmit": true,
"isolatedModules": true
},
"include": ["src"]
}

View File

@@ -0,0 +1,10 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
host: true,
port: Number(process.env.EDEN_PARCEIROS_PORT ?? 3002),
},
});

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EDEN Assinante</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
{
"name": "@eden/subscriber-web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json --noEmit && vite build",
"dev": "vite --port ${EDEN_ASSINANTE_PORT:-3003}",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "echo 'no linter configured yet'",
"test": "echo 'no tests yet'"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3",
"vite": "^6.0.7"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

View File

@@ -0,0 +1,12 @@
export function App() {
return (
<main className="flex min-h-screen items-center justify-center bg-slate-950 text-slate-100">
<div className="text-center">
<h1 className="text-2xl font-semibold">EDEN Assinante</h1>
<p className="mt-2 text-slate-400">
Bootstrap da Fase 1 Design System ainda não portado do tema DreamsERP.
</p>
</div>
</main>
);
}

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

View File

@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"noEmit": true,
"isolatedModules": true
},
"include": ["src"]
}

View File

@@ -0,0 +1,10 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
host: true,
port: Number(process.env.EDEN_ASSINANTE_PORT ?? 3003),
},
});

21
apps/worker/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "@eden/worker",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node dist/main.js",
"dev": "tsx watch src/main.ts",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "echo 'no linter configured yet'",
"test": "echo 'no tests yet'"
},
"dependencies": {
"@eden/database": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.10.5",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
}
}

25
apps/worker/src/main.ts Normal file
View File

@@ -0,0 +1,25 @@
import { query } from "@eden/database";
/**
* No real async jobs exist yet (BullMQ/Redis are only added when the first
* job with a genuine need shows up — see docs/adr/0002-container-topology.md
* and Master Prompt §4.1). This entrypoint exists so the compose topology
* matches the documented architecture from day one and proves the worker
* container can reach Postgres.
*/
async function main() {
await query("SELECT 1");
// eslint-disable-next-line no-console
console.log("[eden-worker] up, database reachable, no jobs configured yet");
// Keep the container alive; replace with a real job queue consumer
// (BullMQ) once Fase 5/6 introduces async jobs (billing runs, PDF
// generation, fiscal retries, etc.).
setInterval(() => {}, 1 << 30);
}
main().catch((err) => {
// eslint-disable-next-line no-console
console.error("[eden-worker] fatal:", err);
process.exit(1);
});

12
apps/worker/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"target": "ES2022",
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["dist", "node_modules"]
}