diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..ee2e497
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,74 @@
+# AGENTS.md
+
+## Cursor Cloud specific instructions
+
+### Overview
+
+OrderFlow Commerce is a monorepo with two services:
+
+| Service | Path | Tech | Dev Port |
+|---------|------|------|----------|
+| API (backend) | `api/` | Spring Boot 3.2 / Java 21 / Maven | 8080 |
+| Web (frontend) | `web/` | React 19 / Vite 8 / TypeScript | 5173 |
+
+Infrastructure (PostgreSQL 15, RabbitMQ 3) runs via Docker Compose.
+
+### Starting infrastructure
+
+```bash
+sudo dockerd &>/tmp/dockerd.log & # if Docker daemon is not already running
+sudo docker compose up -d postgres rabbitmq
+```
+
+Wait for both containers to become healthy before starting the API.
+
+### Running the API locally
+
+The default `application.properties` uses Docker Compose hostnames (`postgres`, `rabbitmq`). For local dev outside Docker, use the `local` Spring profile (which reads `application-local.properties` with `localhost` hostnames):
+
+```bash
+cd api && ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
+```
+
+### Running the Web frontend
+
+```bash
+cd web && npm run dev
+```
+
+Vite proxies `/api` requests to `http://localhost:8080` (see `vite.config.ts`).
+
+### Tests and Lint
+
+- **API tests**: `cd api && ./mvnw test`
+- **Web lint**: `cd web && npm run lint`
+- **Web build**: `cd web && npm run build`
+
+### Key endpoints when running
+
+- API: http://localhost:8080
+- Swagger UI: http://localhost:8080/swagger-ui/index.html
+- Web: http://localhost:5173
+- RabbitMQ Management: http://localhost:15672 (user: `orderflow` / pass: `orderflow123`)
+
+### Full Docker development (with hot reload)
+
+```bash
+sudo docker compose up --build
+```
+
+This starts all services with hot reload enabled:
+- **API**: `dev-entrypoint.sh` uses `inotifywait` to watch `src/` for `.java`/`.properties`/`.xml` changes, triggers `mvn compile`, and DevTools auto-restarts the app (~0.5s).
+- **Web**: Runs Vite dev server (not nginx) with full HMR via volume-mounted source files.
+- **Debug port**: JDWP on port 5005 (controlled by `SPRING_BOOT_JVM_ARGS` env var).
+
+The `vite.config.ts` reads `API_PROXY_TARGET` env var (defaults to `http://localhost:8080`). In Docker Compose it's set to `http://app:8080`.
+
+### Non-obvious notes
+
+- Security is currently set to `permitAll()` — no auth required for any endpoint.
+- The `mvnw` wrapper must be `chmod +x` before first use (it's committed without execute bit).
+- Hibernate `ddl-auto=update` auto-creates schema on first run — no migration step needed.
+- The `application-local.properties` file (added for Cloud dev) overrides DB/RabbitMQ hosts to `localhost`. If you need to run the API inside Docker Compose, use `-Dspring-boot.run.profiles=docker` instead.
+- `spring-boot-devtools` is enabled: the API auto-restarts on class changes, but not on `pom.xml` changes.
+- The original `web/Dockerfile` is for production (build + nginx). `web/Dockerfile.dev` is for development with HMR.
diff --git a/README.md b/README.md
index 29d4937..0482d43 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,490 @@
-# Monorepo — frontend e API
-
+# OrderFlow Commerce
-Este repositório contém:
+
+
+
-| Pasta | Descrição |
-|-------|-----------|
-| [`api/`](api/) | Spring Boot (OrderFlow Commerce) |
-| [`web/`](web/) | Frontend Vite + React |
+
+
+
+
+
+
+
+
+
-## Subir tudo com Docker
+
+ Event-driven e-commerce platform — plataforma de e-commerce orientada a eventos
+ Built with Java 21, Spring Boot, RabbitMQ, and React
+
-Na **raiz** do repositório:
+---
+
+## What is OrderFlow? · O que é o OrderFlow?
+
+OrderFlow Commerce is an **event-driven e-commerce REST API** that processes orders asynchronously.
+When a customer checks out, an `OrderCreated` event is published to RabbitMQ — inventory reservation and email notifications happen **in the background**, without blocking the client.
+
+O OrderFlow demonstra uma arquitetura **moderna e escalável**: mensageria assíncrona, API RESTful documentada com Swagger, e um monorepo com frontend React + backend Spring Boot, tudo orquestrado via Docker Compose com **hot reload** para desenvolvimento.
+
+---
+
+## Architecture · Arquitetura
+
+> Diagramas seguem o [**C4 Model**](https://c4model.com/) — do contexto geral até o código.
+
+### C1 — System Context · Contexto do Sistema
+
+*Who interacts with the system? What are the external dependencies?*
+
+```mermaid
+graph TB
+ subgraph External
+ USER["🧑 User / Cliente"]
+ PG["🐘 PostgreSQL 15
Relational Database"]
+ RMQ["🐇 RabbitMQ 3
Message Broker"]
+ end
+
+ subgraph OrderFlow["⚡ OrderFlow Commerce"]
+ API["Spring Boot API
REST + Event Publishing"]
+ WEB["React SPA
Vite + TypeScript + Tailwind"]
+ end
+
+ USER -- "HTTP / Browser" --> WEB
+ WEB -- "REST /api/*" --> API
+ API -- "JDBC" --> PG
+ API -- "AMQP" --> RMQ
+ RMQ -- "consume events" --> API
+
+ style OrderFlow fill:#1a1a2e,stroke:#16213e,color:#eee
+ style API fill:#6DB33F,stroke:#4a8a2a,color:#fff
+ style WEB fill:#61DAFB,stroke:#3a8fb7,color:#000
+ style PG fill:#4169E1,stroke:#2a4494,color:#fff
+ style RMQ fill:#FF6600,stroke:#cc5200,color:#fff
+ style USER fill:#f5f5f5,stroke:#999,color:#333
+```
+
+### C2 — Containers · Contêineres
+
+*What applications/services run? How do they communicate?*
+
+```mermaid
+graph LR
+ subgraph Docker Compose
+ direction TB
+
+ PG[("🐘 PostgreSQL 15
Port 5432
orderflow DB")]
+ RMQ["🐇 RabbitMQ 3
AMQP 5672 · UI 15672
Exchange: order.events"]
+
+ subgraph API["⚡ orderflow-app · Port 8080"]
+ direction TB
+ REST["REST Controllers
/categories · /products · /test"]
+ PUB["OrderEventPublisher
Publishes OrderCreated"]
+ INV["InventoryConsumer
Queue: order.inventory"]
+ EMAIL["EmailConsumer
Queue: order.email"]
+ end
+
+ subgraph WEB["🌐 orderflow-web · Port 5173"]
+ VITE["Vite Dev Server
React 19 + Tailwind 4"]
+ end
+ end
+
+ VITE -- "/api/* proxy" --> REST
+ REST -- "JPA / Hibernate" --> PG
+ PUB -- "AMQP publish
routing key: order.created" --> RMQ
+ RMQ -- "consume" --> INV
+ RMQ -- "consume" --> EMAIL
+
+ style PG fill:#4169E1,stroke:#2a4494,color:#fff
+ style RMQ fill:#FF6600,stroke:#cc5200,color:#fff
+ style API fill:#1b4332,stroke:#2d6a4f,color:#eee
+ style WEB fill:#0d1b2a,stroke:#1b3a5c,color:#eee
+ style REST fill:#6DB33F,stroke:#4a8a2a,color:#fff
+ style PUB fill:#81b29a,stroke:#588b76,color:#000
+ style INV fill:#e07a5f,stroke:#c25a3f,color:#fff
+ style EMAIL fill:#e07a5f,stroke:#c25a3f,color:#fff
+ style VITE fill:#61DAFB,stroke:#3a8fb7,color:#000
+```
+
+### C3 — Components · Componentes da API
+
+*Internal modules of the Spring Boot application.*
+
+```mermaid
+graph TB
+ subgraph Controllers["🎯 Controllers (REST)"]
+ CC["CategoryController
/categories"]
+ PC["ProductController
/products"]
+ TC["TestController
/test"]
+ end
+
+ subgraph Messaging["📨 Messaging (RabbitMQ)"]
+ OEP["OrderEventPublisher"]
+ IC["InventoryConsumer"]
+ EC["EmailConsumer"]
+ RMC["RabbitMQConfig
Exchange + Queues + Bindings"]
+ end
+
+ subgraph Data["💾 Data Layer"]
+ CR["CategoryRepository"]
+ PR["ProductRepository"]
+ OR["OrderRepository"]
+ OIR["OrderItemRepository"]
+ end
+
+ subgraph Entities["📦 Domain Entities"]
+ CAT["Category"]
+ PROD["Product"]
+ ORD["Order"]
+ OI["OrderItem"]
+ end
+
+ subgraph Config["⚙️ Cross-Cutting"]
+ SC["SecurityConfig
CSRF off · Stateless · permitAll"]
+ OAC["OpenApiConfig
Swagger UI"]
+ GEH["GlobalExceptionHandler
400 · 404 · 500"]
+ end
+
+ CC --> CR --> CAT
+ PC --> PR --> PROD
+ TC --> OEP
+ OEP --> RMC
+ CR --> CAT
+ PR --> PROD
+ OR --> ORD
+ OIR --> OI
+ PROD -.->|"@ManyToOne"| CAT
+ OI -.->|"@ManyToOne"| ORD
+ OI -.->|"@ManyToOne"| PROD
+
+ style Controllers fill:#6DB33F,stroke:#4a8a2a,color:#fff
+ style Messaging fill:#FF6600,stroke:#cc5200,color:#fff
+ style Data fill:#4169E1,stroke:#2a4494,color:#fff
+ style Entities fill:#2d6a4f,stroke:#1b4332,color:#fff
+ style Config fill:#555,stroke:#333,color:#eee
+```
+
+### C4 — Entity Relationship · Modelo de Dados
+
+```mermaid
+erDiagram
+ tb_category {
+ bigint id PK "IDENTITY"
+ varchar name UK "NOT NULL"
+ }
+
+ tb_product {
+ bigint id PK "IDENTITY"
+ varchar name "NOT NULL"
+ text description
+ decimal price "NOT NULL"
+ int stock_quantity
+ bigint category_id FK
+ }
+
+ tb_order {
+ bigint id PK "IDENTITY"
+ timestamp created_at "auto @PrePersist"
+ varchar status "PENDING | CONFIRMED | SHIPPED | DELIVERED | CANCELLED"
+ decimal total "sum(items)"
+ }
+
+ tb_order_item {
+ bigint id PK "IDENTITY"
+ int quantity
+ decimal unit_price "snapshot do preco"
+ bigint order_id FK "NOT NULL"
+ bigint product_id FK "NOT NULL"
+ }
+
+ tb_category ||--o{ tb_product : "has many"
+ tb_product ||--o{ tb_order_item : "referenced by"
+ tb_order ||--o{ tb_order_item : "contains"
+```
+
+---
+
+## Event Flow · Fluxo de Eventos
+
+```mermaid
+sequenceDiagram
+ actor Client
+ participant API as Spring Boot API
+ participant PG as PostgreSQL
+ participant RMQ as RabbitMQ
+ participant INV as InventoryConsumer
+ participant MAIL as EmailConsumer
+
+ Client->>API: GET /test/publish-sample-order
+ API->>RMQ: publish OrderCreatedEvent
exchange: order.events
routing key: order.created
+
+ par Async Processing
+ RMQ->>INV: queue: order.inventory
+ INV->>INV: Reserve stock (simulated)
+ and
+ RMQ->>MAIL: queue: order.email
+ MAIL->>MAIL: Send confirmation (simulated)
+ end
+
+ API-->>Client: 200 { published: true, orderId: 1000 }
+```
+
+---
+
+## REST API Endpoints
+
+| Method | Endpoint | Description | Request Body |
+|--------|----------|-------------|--------------|
+| `GET` | `/categories` | List all categories | — |
+| `GET` | `/categories/{id}` | Get category by ID | — |
+| `POST` | `/categories` | Create category | `{ "name": "..." }` |
+| `PUT` | `/categories/{id}` | Update category | `{ "name": "..." }` |
+| `DELETE` | `/categories/{id}` | Delete category | — |
+| `GET` | `/products` | List all products | — |
+| `GET` | `/products/{id}` | Get product by ID | — |
+| `POST` | `/products` | Create product | `{ "name", "description", "price", "stockQuantity", "category": {"id": n} }` |
+| `PUT` | `/products/{id}` | Update product | same as create |
+| `DELETE` | `/products/{id}` | Delete product | — |
+| `GET` | `/test/ping` | Health check | — |
+| `GET` | `/test/publish-sample-order` | Publish test event to RabbitMQ | — |
+
+> 📖 **Interactive docs:** [Swagger UI](http://localhost:8080/swagger-ui/index.html) (after starting the API)
+
+---
+
+## Tech Stack
+
+| Layer | Technology | Purpose |
+|-------|-----------|---------|
+| **Language** | Java 21 | Backend runtime |
+| **Framework** | Spring Boot 3.2 | REST API, DI, auto-config |
+| **Database** | PostgreSQL 15 | Persistent storage |
+| **ORM** | Spring Data JPA / Hibernate 6 | Object-relational mapping |
+| **Messaging** | RabbitMQ 3 | Async event processing (AMQP) |
+| **Security** | Spring Security | Auth framework (currently `permitAll`) |
+| **API Docs** | SpringDoc OpenAPI 2.5 | Swagger UI generation |
+| **Frontend** | React 19 + TypeScript | Single Page Application |
+| **Styling** | Tailwind CSS 4 | Utility-first CSS |
+| **Build (FE)** | Vite 8 | Dev server + bundler with HMR |
+| **Build (BE)** | Maven (wrapper) | Dependency management + build |
+| **DevOps** | Docker Compose | Local orchestration |
+| **Testing** | JUnit 5 | Unit tests |
+| **Dev Tools** | spring-boot-devtools + inotifywait | Hot reload in Docker |
+
+---
+
+## Quick Start · Como Rodar
+
+### Prerequisites · Pré-requisitos
+
+- **Docker Engine 24+** & **Docker Compose**
+- **Git**
+
+### Option 1: Docker Compose (recommended · recomendado)
```bash
+git clone https://github.com/dbfcode/commerce-async-platform.git
+cd commerce-async-platform
+
docker compose up --build
```
-- API: http://localhost:8080
-- Swagger: http://localhost:8080/swagger-ui.html
-- Web (nginx): http://localhost:4173
-- RabbitMQ UI: http://localhost:15672
+| Service | URL | Credentials |
+|---------|-----|-------------|
+| **API** | http://localhost:8080 | — |
+| **Swagger UI** | http://localhost:8080/swagger-ui/index.html | — |
+| **Web (Vite)** | http://localhost:5173 | — |
+| **RabbitMQ UI** | http://localhost:15672 | `orderflow` / `orderflow123` |
+| **PostgreSQL** | `localhost:5432` | `orderflow` / `orderflow123` / db: `orderflow` |
+| **Debug (JDWP)** | `localhost:5005` | — |
+
+> ♻️ **Hot reload** is enabled for both API (auto-recompile + DevTools restart) and Web (Vite HMR).
+
+### Option 2: Local Development · Desenvolvimento Local
+
+Start only infrastructure via Docker, run API and Web natively:
+
+```bash
+# Infrastructure
+docker compose up -d postgres rabbitmq
+
+# API (terminal 1)
+cd api && ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
+
+# Web (terminal 2)
+cd web && npm install && npm run dev
+```
+
+### Stopping · Parando
+
+```bash
+docker compose down
+```
+
+---
+
+## Project Structure · Estrutura do Projeto
+
+```
+commerce-async-platform/
+├── docker-compose.yml # Orchestrates all services
+├── AGENTS.md # Dev environment instructions
+│
+├── api/ # ⚡ Spring Boot Backend
+│ ├── Dockerfile # Dev image (JDK + Maven + inotify)
+│ ├── dev-entrypoint.sh # File watcher for hot reload
+│ ├── pom.xml # Maven dependencies
+│ └── src/
+│ ├── main/java/com/orderflow/ecommerce/
+│ │ ├── Application.java
+│ │ ├── config/ # Security, OpenAPI, RabbitMQ
+│ │ ├── controllers/ # REST endpoints
+│ │ ├── dtos/ # Data transfer objects
+│ │ ├── entities/ # JPA entities + enums
+│ │ ├── exceptions/ # Global error handler
+│ │ ├── messaging/ # Publisher, consumers, events
+│ │ └── repositories/ # Spring Data JPA interfaces
+│ └── main/resources/
+│ ├── application.properties
+│ ├── application-docker.properties
+│ └── application-local.properties
+│
+└── web/ # 🌐 React Frontend
+ ├── Dockerfile # Production build (nginx)
+ ├── Dockerfile.dev # Development (Vite HMR)
+ ├── package.json
+ ├── vite.config.ts # Proxy /api → backend
+ └── src/
+ ├── App.tsx # Main component
+ ├── lib/api.ts # API client
+ └── index.css # Tailwind imports
+```
+
+---
+
+## RabbitMQ Topology · Topologia de Mensageria
+
+| Resource | Name | Type | Notes |
+|----------|------|------|-------|
+| **Exchange** | `order.events` | Topic (durable) | Central event hub |
+| **Queue** | `order.inventory` | Durable | Inventory reservation |
+| **Queue** | `order.email` | Durable | Email notifications |
+| **Routing Key** | `order.created` | — | Binds both queues |
+
+Both queues receive the same `OrderCreatedEvent` (fan-out via shared routing key), enabling **independent, parallel processing**.
+
+---
+
+## Roadmap · Evolução Planejada
+
+> Detalhes em [`api/docs/microservices-migration.md`](api/docs/microservices-migration.md)
+
+| Phase | Description | Status |
+|-------|-------------|--------|
+| 1 | **Baseline** — Monolith with event-driven order processing | ✅ Done |
+| 2 | **Contracts** — Shared event schemas (`orderflow-contracts`) | 🔜 Planned |
+| 3 | **Extract Workers** — Inventory & notification as separate services | 🔜 Planned |
+| 4 | **Extract APIs** — Catalog & orders as independent microservices | 🔜 Planned |
+| 5 | **Hardening** — DLQ, idempotency, observability, CI pipeline | 🔜 Planned |
+
+**Planned integrations:** Redis caching (cart), JWT authentication, Resilience4j circuit breakers.
+
+---
+
+## Tests · Testes
+
+```bash
+# Backend unit tests
+cd api && ./mvnw test
+
+# Frontend lint
+cd web && npm run lint
+
+# Frontend build check
+cd web && npm run build
+```
+
+---
+
+## Contribution Workflow · Como contribuir
+
+Para manter o monorepo organizado e o histórico do Git limpo, adotamos padrões estritos para a nomenclatura de **branches** e **mensagens de commit** (baseado em Conventional Commits).
+
+---
+
+### Branch Naming · Padrão de Branches
+
+Toda nova alteração deve partir da branch principal utilizando a seguinte estrutura em **inglês** e com letras **minúsculas**:
+
+#### Formato
+```
+padrão: [tipo-abreviado]/[escopo-opcional]-[breve-descrição]
+```
+
+#### Tipos Permitidos (Prefixos)
+* `feat/` : Nova funcionalidade (ex: `feat/cart-page`)
+* `fix/` : Correção de bug (ex: `fix/rabbitmq-retry`)
+* `docs/` : Alterações exclusivas de documentação (ex: `docs/separate-swagger-docs`)
+* `refactor/` : Refatoração de código que não altera o comportamento (ex: `refactor/clean-controllers`)
+* `chore/` : Atualizações de build, dependências ou ferramentas (ex: `chore/update-docker-compose`)
+
+---
+
+### Semantic Commits · Commits Semânticos
+
+As mensagens de commit devem ser escritas obrigatoriamente em **inglês**, utilizando letras **minúsculas** e o verbo no **imperativo** (ex: *add*, *fix*, *remove*, em vez de *added*, *fixed*, *removing*).
+
+#### Formato
+```
+padrão: [tipo-abreviado](escopo):
+```
+
+#### Tabela de Tipos e Escopos
+
+| Tipo | Uso | Escopo | Significado |
+|:-----|:----| :--- | :--- |
+| **feat** | Nova funcionalidade | **auth** | Autenticação, JWT |
+| **fix** | Correção de bug | **produto** | CRUD de produtos |
+| **docs** | Documentação | **categoria** | CRUD de categorias |
+| **style** | Formatação, espaços, lint (não altera código) | **usuario** | CRUD de usuários |
+| **refactor** | Refatoração de código | **carrinho** | Carrinho de compras |
+| **test** | Adicionar ou corrigir testes | **pedido** | Checkout e pedidos |
+| **chore** | Configuração, dependências, build | **messaging** | Fila, RabbitMQ, consumidores |
+| **chore** | Configuração, dependências, build | **docker** | Dockerfile, docker-compose |
+| **chore** | Configuração, dependências, build | **infra** | Configurações gerais |
+
+#### Exemplos Práticos · Examples
+
+* **Funcionalidades e Correções:**
+ * `feat(auth): implement login with JWT`
+ * `feat(messaging): configure RabbitMQ and publish order event`
+ * `fix(carrinho): avoid duplicate items in cart`
+ * `fix(auth): fix expired token validation`
+
+* **Refatoração, Testes e Outros:**
+ * `refactor(produto): extract validation logic to service`
+ * `test(pedido): add integration tests with testcontainers`
+ * `docs: add architecture diagram to README`
+ * `chore: configure docker-compose with PostgreSQL and RabbitMQ`
+
+#### Regras de Ouro
+1. **Inglês sempre!**
+2. **Minúsculo** – Tudo em letras minúsculas.
+3. **Imperativo** – "add" e não "added" ou "adding".
+4. **Curto** – Até 50 caracteres na mensagem principal.
+5. **Sem ponto final** – Não termine a linha de resumo com ponto `.`.
+
+## Contributors · Colaboradores
+
+| Name | Role | Contributions |
+|------|------|---------------|
+| **Diego Ferreira** | Advanced Developer | RabbitMQ, checkout, Docker, architecture |
+| **Giovanna Caxias** | Junior Developer | CRUD, JWT auth, cart, Swagger |
-## Desenvolvimento local
+---
-- **API:** `cd api && ./mvnw spring-boot:run` (Windows: `mvnw.cmd`)
-- **Web:** `cd web && npm install && npm run dev`
+## License · Licença
-Detalhes da API, stack e variáveis: [`api/README.md`](api/README.md).
+Portfolio project. Not licensed for commercial use.
+Projeto de portfólio. Não licenciado para uso comercial.
diff --git a/api/Dockerfile b/api/Dockerfile
index 21ca42e..a5c06f9 100644
--- a/api/Dockerfile
+++ b/api/Dockerfile
@@ -2,13 +2,13 @@ FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
-RUN apk add --no-cache maven
+RUN apk add --no-cache maven inotify-tools
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
+COPY dev-entrypoint.sh /app/dev-entrypoint.sh
+RUN chmod +x /app/dev-entrypoint.sh
-CMD ["mvn", "spring-boot:run", \
- "-Dspring-boot.run.profiles=docker", \
- "-Dspring-boot.run.jvmArguments=-Dspring.devtools.restart.poll-interval=2000 -Dspring.devtools.restart.quiet-period=1000"]
\ No newline at end of file
+CMD ["/app/dev-entrypoint.sh"]
\ No newline at end of file
diff --git a/api/dev-entrypoint.sh b/api/dev-entrypoint.sh
new file mode 100755
index 0000000..b63841b
--- /dev/null
+++ b/api/dev-entrypoint.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+set -e
+
+echo "[dev] Compiling project..."
+mvn compile -q -B
+
+echo "[dev] Starting file watcher on src/..."
+(
+ while inotifywait -r -q -e modify,create,delete,move \
+ --include '\.java$|\.properties$|\.yml$|\.yaml$|\.xml$' \
+ src/ 2>/dev/null; do
+ echo "[dev] Source change detected — recompiling..."
+ mvn compile -q -B 2>&1 || echo "[dev] Compilation failed (check logs above)."
+ done
+) &
+
+echo "[dev] Starting Spring Boot..."
+exec mvn spring-boot:run \
+ -Dspring-boot.run.profiles=${SPRING_PROFILES_ACTIVE:-docker} \
+ -Dspring-boot.run.jvmArguments="${SPRING_BOOT_JVM_ARGS:--agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005}"
diff --git a/api/mvnw b/api/mvnw
old mode 100644
new mode 100755
diff --git a/api/src/main/resources/application-local.properties b/api/src/main/resources/application-local.properties
new file mode 100644
index 0000000..85b077f
--- /dev/null
+++ b/api/src/main/resources/application-local.properties
@@ -0,0 +1,10 @@
+spring.config.activate.on-profile=local
+
+spring.datasource.url=jdbc:postgresql://localhost:5432/orderflow
+spring.datasource.username=orderflow
+spring.datasource.password=orderflow123
+
+spring.rabbitmq.host=localhost
+spring.rabbitmq.port=5672
+spring.rabbitmq.username=orderflow
+spring.rabbitmq.password=orderflow123
diff --git a/docker-compose.yml b/docker-compose.yml
index d065089..4fa4dda 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: '3.8'
-
services:
postgres:
image: postgres:15
@@ -8,8 +6,6 @@ services:
POSTGRES_DB: orderflow
POSTGRES_USER: orderflow
POSTGRES_PASSWORD: orderflow123
- SPRING_DEVTOOLS_RESTART_POLL_INTERVAL: "2000"
- SPRING_DEVTOOLS_RESTART_QUIET_PERIOD: "1000"
ports:
- "5432:5432"
volumes:
@@ -57,8 +53,7 @@ services:
SPRING_RABBITMQ_PASSWORD: orderflow123
SPRING_DEVTOOLS_RESTART_ENABLED: "true"
SPRING_DEVTOOLS_LIVERELOAD_ENABLED: "true"
- # Debug apenas no processo filho (fork do spring-boot:run)
- MAVEN_OPTS: "-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
+ SPRING_BOOT_JVM_ARGS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
ports:
- "8080:8080"
- "5005:5005"
@@ -79,12 +74,17 @@ services:
web:
build:
context: ./web
- dockerfile: Dockerfile
- args:
- VITE_API_BASE_URL: /api
+ dockerfile: Dockerfile.dev
container_name: orderflow-web
+ environment:
+ VITE_API_BASE_URL: /api
+ API_PROXY_TARGET: http://app:8080
ports:
- - "4173:80"
+ - "5173:5173"
+ volumes:
+ - ./web/src:/app/src
+ - ./web/index.html:/app/index.html
+ - ./web/vite.config.ts:/app/vite.config.ts
depends_on:
app:
condition: service_started
diff --git a/scripts/test-hot-reload.sh b/scripts/test-hot-reload.sh
new file mode 100755
index 0000000..68cf62e
--- /dev/null
+++ b/scripts/test-hot-reload.sh
@@ -0,0 +1,328 @@
+#!/usr/bin/env bash
+# ===========================================================================
+# OrderFlow Commerce — E2E Hot-Reload Test
+# ===========================================================================
+# Validates the full hot-reload pipeline in Docker:
+#
+# 1. API is healthy and responds with the ORIGINAL message.
+# 2. A Java source file is modified (PingResponse message changed).
+# 3. inotifywait detects the change → mvn compile → DevTools restarts.
+# 4. API now responds with the MODIFIED message.
+# 5. The source file is reverted to the original.
+# 6. inotifywait detects the revert → mvn compile → DevTools restarts.
+# 7. API responds with the ORIGINAL message again.
+#
+# Usage:
+# ./scripts/test-hot-reload.sh # uses running containers
+# ./scripts/test-hot-reload.sh --start # docker compose up first
+# ./scripts/test-hot-reload.sh --full # up, test, then down
+#
+# Exit codes:
+# 0 = all assertions passed
+# 1 = assertion failed
+# 2 = environment error (API unreachable, timeout, etc.)
+# ===========================================================================
+set -euo pipefail
+
+# ── Configuration ──────────────────────────────────────────────────────────
+API_URL="${API_URL:-http://localhost:8080}"
+PING_ENDPOINT="${API_URL}/test/ping"
+
+TARGET_FILE="api/src/main/java/com/orderflow/ecommerce/controllers/TestController.java"
+ORIGINAL_MSG='versão 1.'
+MODIFIED_MSG='versão 2 — hot reload e2e test'
+
+STARTUP_TIMEOUT=120 # max seconds to wait for API to be ready
+HOT_RELOAD_TIMEOUT=60 # max seconds to wait for hot-reload cycle
+POLL_INTERVAL=2 # seconds between health polls
+
+# ── Colors ─────────────────────────────────────────────────────────────────
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+# ── Helpers ────────────────────────────────────────────────────────────────
+info() { echo -e "${CYAN}[INFO]${NC} $*"; }
+ok() { echo -e "${GREEN}[PASS]${NC} $*"; }
+fail() { echo -e "${RED}[FAIL]${NC} $*"; }
+warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
+step() { echo -e "\n${BOLD}── $* ──${NC}"; }
+divider() { echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}"; }
+
+TESTS_RUN=0
+TESTS_PASSED=0
+TESTS_FAILED=0
+
+assert_eq() {
+ local label="$1" expected="$2" actual="$3"
+ TESTS_RUN=$((TESTS_RUN + 1))
+ if [ "$expected" = "$actual" ]; then
+ ok "$label"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+ else
+ fail "$label"
+ echo " expected: \"$expected\""
+ echo " actual: \"$actual\""
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+ fi
+}
+
+assert_contains() {
+ local label="$1" needle="$2" haystack="$3"
+ TESTS_RUN=$((TESTS_RUN + 1))
+ if echo "$haystack" | grep -qF "$needle"; then
+ ok "$label"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+ else
+ fail "$label"
+ echo " expected to contain: \"$needle\""
+ echo " actual: \"$haystack\""
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+ fi
+}
+
+get_ping_message() {
+ curl -sf "$PING_ENDPOINT" 2>/dev/null \
+ | python3 -c "import sys,json; print(json.load(sys.stdin).get('message',''))" 2>/dev/null \
+ || echo ""
+}
+
+wait_for_api() {
+ local timeout=$1
+ local elapsed=0
+ while [ $elapsed -lt $timeout ]; do
+ if curl -sf "$PING_ENDPOINT" > /dev/null 2>&1; then
+ return 0
+ fi
+ sleep "$POLL_INTERVAL"
+ elapsed=$((elapsed + POLL_INTERVAL))
+ done
+ return 1
+}
+
+wait_for_message() {
+ local expected="$1" timeout="$2"
+ local elapsed=0
+ while [ $elapsed -lt $timeout ]; do
+ local msg
+ msg=$(get_ping_message)
+ if [ "$msg" = "$expected" ]; then
+ info "Detected in ${elapsed}s"
+ return 0
+ fi
+ sleep "$POLL_INTERVAL"
+ elapsed=$((elapsed + POLL_INTERVAL))
+ done
+ return 1
+}
+
+cleanup() {
+ if [ -f "$TARGET_FILE" ]; then
+ if grep -qF "$MODIFIED_MSG" "$TARGET_FILE" 2>/dev/null; then
+ warn "Reverting source file (cleanup)..."
+ sed -i "s|$MODIFIED_MSG|$ORIGINAL_MSG|g" "$TARGET_FILE"
+ fi
+ fi
+}
+trap cleanup EXIT
+
+# ── Parse arguments ────────────────────────────────────────────────────────
+DO_START=false
+DO_STOP=false
+
+for arg in "$@"; do
+ case "$arg" in
+ --start) DO_START=true ;;
+ --full) DO_START=true; DO_STOP=true ;;
+ --help|-h)
+ echo "Usage: $0 [--start | --full]"
+ echo " --start run 'docker compose up --build -d' before testing"
+ echo " --full start before and stop after testing"
+ exit 0
+ ;;
+ *) warn "Unknown argument: $arg" ;;
+ esac
+done
+
+# ── Main ───────────────────────────────────────────────────────────────────
+divider
+echo -e "${BOLD} OrderFlow Commerce — E2E Hot-Reload Test${NC}"
+divider
+
+cd "$(git rev-parse --show-toplevel 2>/dev/null || echo /workspace)"
+
+# Optionally start Docker Compose
+if $DO_START; then
+ step "Starting Docker Compose"
+ docker compose up --build -d 2>&1 | tail -5
+fi
+
+# ── Phase 1: Wait for API ──────────────────────────────────────────────────
+step "Phase 1 · Waiting for API to be ready"
+info "Endpoint: $PING_ENDPOINT (timeout: ${STARTUP_TIMEOUT}s)"
+
+if ! wait_for_api "$STARTUP_TIMEOUT"; then
+ fail "API did not become ready within ${STARTUP_TIMEOUT}s"
+ exit 2
+fi
+ok "API is responding"
+
+# ── Phase 2: Verify original state ────────────────────────────────────────
+step "Phase 2 · Verify original response"
+
+CURRENT_MSG=$(get_ping_message)
+assert_eq "Ping message is original" "$ORIGINAL_MSG" "$CURRENT_MSG"
+
+FULL_RESPONSE=$(curl -sf "$PING_ENDPOINT" 2>/dev/null || echo "{}")
+assert_contains "Response has status field" '"status"' "$FULL_RESPONSE"
+assert_contains "Response has timestamp field" '"timestamp"' "$FULL_RESPONSE"
+assert_contains "Status is ok" '"ok"' "$FULL_RESPONSE"
+
+if [ $TESTS_FAILED -gt 0 ]; then
+ fail "Original state verification failed — aborting"
+ exit 1
+fi
+
+# ── Phase 3: Modify source file ───────────────────────────────────────────
+step "Phase 3 · Modify Java source (trigger hot reload)"
+
+if [ ! -f "$TARGET_FILE" ]; then
+ fail "Target file not found: $TARGET_FILE"
+ exit 2
+fi
+
+info "Changing message: \"$ORIGINAL_MSG\" → \"$MODIFIED_MSG\""
+sed -i "s|$ORIGINAL_MSG|$MODIFIED_MSG|g" "$TARGET_FILE"
+
+if grep -qF "$MODIFIED_MSG" "$TARGET_FILE"; then
+ ok "Source file modified successfully"
+else
+ fail "Source file modification failed"
+ exit 2
+fi
+
+# ── Phase 4: Wait for hot reload with modified message ─────────────────────
+step "Phase 4 · Waiting for hot reload (timeout: ${HOT_RELOAD_TIMEOUT}s)"
+info "Expecting message: \"$MODIFIED_MSG\""
+
+RELOAD_START=$(date +%s)
+
+if wait_for_message "$MODIFIED_MSG" "$HOT_RELOAD_TIMEOUT"; then
+ RELOAD_END=$(date +%s)
+ RELOAD_TIME=$((RELOAD_END - RELOAD_START))
+ assert_eq "Hot reload delivered modified message" "$MODIFIED_MSG" "$(get_ping_message)"
+ ok "Hot reload completed in ${RELOAD_TIME}s ⚡"
+else
+ ACTUAL=$(get_ping_message)
+ fail "Hot reload did not deliver modified message within ${HOT_RELOAD_TIMEOUT}s"
+ echo " expected: \"$MODIFIED_MSG\""
+ echo " actual: \"$ACTUAL\""
+ TESTS_RUN=$((TESTS_RUN + 1))
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+fi
+
+# ── Phase 5: Revert source file ───────────────────────────────────────────
+step "Phase 5 · Revert source file (trigger second hot reload)"
+
+info "Changing message: \"$MODIFIED_MSG\" → \"$ORIGINAL_MSG\""
+sed -i "s|$MODIFIED_MSG|$ORIGINAL_MSG|g" "$TARGET_FILE"
+
+if grep -qF "$ORIGINAL_MSG" "$TARGET_FILE"; then
+ ok "Source file reverted successfully"
+else
+ fail "Source file revert failed"
+ exit 2
+fi
+
+# ── Phase 6: Wait for hot reload with original message ─────────────────────
+step "Phase 6 · Waiting for second hot reload (timeout: ${HOT_RELOAD_TIMEOUT}s)"
+info "Expecting message: \"$ORIGINAL_MSG\""
+
+RELOAD_START=$(date +%s)
+
+if wait_for_message "$ORIGINAL_MSG" "$HOT_RELOAD_TIMEOUT"; then
+ RELOAD_END=$(date +%s)
+ RELOAD_TIME=$((RELOAD_END - RELOAD_START))
+ assert_eq "Hot reload delivered original message" "$ORIGINAL_MSG" "$(get_ping_message)"
+ ok "Second hot reload completed in ${RELOAD_TIME}s ⚡"
+else
+ ACTUAL=$(get_ping_message)
+ fail "Second hot reload did not deliver original message within ${HOT_RELOAD_TIMEOUT}s"
+ echo " expected: \"$ORIGINAL_MSG\""
+ echo " actual: \"$ACTUAL\""
+ TESTS_RUN=$((TESTS_RUN + 1))
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+fi
+
+# ── Phase 7: Verify API is still healthy ───────────────────────────────────
+step "Phase 7 · Post-reload health check"
+
+if curl -sf "$PING_ENDPOINT" > /dev/null 2>&1; then
+ TESTS_RUN=$((TESTS_RUN + 1))
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+ ok "API is still healthy after two reload cycles"
+else
+ TESTS_RUN=$((TESTS_RUN + 1))
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+ fail "API is not responding after reload cycles"
+fi
+
+# Verify full CRUD still works after reloads
+CATEGORY_RESPONSE=$(curl -sf -X POST "$API_URL/categories" \
+ -H "Content-Type: application/json" \
+ -d '{"name": "HotReloadTest"}' 2>/dev/null || echo "")
+
+if echo "$CATEGORY_RESPONSE" | grep -qF "HotReloadTest"; then
+ TESTS_RUN=$((TESTS_RUN + 1))
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+ ok "CRUD operations work after hot reload (POST /categories)"
+
+ CATEGORY_ID=$(echo "$CATEGORY_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "")
+ if [ -n "$CATEGORY_ID" ]; then
+ curl -sf -X DELETE "$API_URL/categories/$CATEGORY_ID" > /dev/null 2>&1 || true
+ fi
+else
+ TESTS_RUN=$((TESTS_RUN + 1))
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+ fail "CRUD operations broken after hot reload"
+fi
+
+# Verify RabbitMQ integration still works
+RABBIT_RESPONSE=$(curl -sf "$API_URL/test/publish-sample-order" 2>/dev/null || echo "")
+if echo "$RABBIT_RESPONSE" | grep -qF '"published":true'; then
+ TESTS_RUN=$((TESTS_RUN + 1))
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+ ok "RabbitMQ event publishing works after hot reload"
+else
+ TESTS_RUN=$((TESTS_RUN + 1))
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+ fail "RabbitMQ event publishing broken after hot reload"
+fi
+
+# ── Optionally stop Docker Compose ─────────────────────────────────────────
+if $DO_STOP; then
+ step "Stopping Docker Compose"
+ docker compose down 2>&1 | tail -3
+fi
+
+# ── Results ────────────────────────────────────────────────────────────────
+divider
+echo -e "${BOLD} Results${NC}"
+divider
+echo -e " Tests run: ${BOLD}$TESTS_RUN${NC}"
+echo -e " Passed: ${GREEN}${BOLD}$TESTS_PASSED${NC}"
+if [ $TESTS_FAILED -gt 0 ]; then
+ echo -e " Failed: ${RED}${BOLD}$TESTS_FAILED${NC}"
+fi
+divider
+
+if [ $TESTS_FAILED -eq 0 ]; then
+ echo -e "\n${GREEN}${BOLD}✅ All hot-reload E2E tests passed!${NC}\n"
+ exit 0
+else
+ echo -e "\n${RED}${BOLD}❌ $TESTS_FAILED test(s) failed.${NC}\n"
+ exit 1
+fi
diff --git a/web/Dockerfile.dev b/web/Dockerfile.dev
new file mode 100644
index 0000000..f4ecb48
--- /dev/null
+++ b/web/Dockerfile.dev
@@ -0,0 +1,12 @@
+FROM node:22-alpine
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
+
+EXPOSE 5173
+
+CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
diff --git a/web/package-lock.json b/web/package-lock.json
index bf516aa..ecf3cd9 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -59,7 +59,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -269,6 +268,29 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
@@ -1127,7 +1149,6 @@
"integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -1138,7 +1159,6 @@
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1198,7 +1218,6 @@
"integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.59.1",
"@typescript-eslint/types": "8.59.1",
@@ -1429,7 +1448,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1520,7 +1538,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -1670,7 +1687,6 @@
"integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
@@ -2586,7 +2602,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -2648,7 +2663,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2833,7 +2847,6 @@
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -2920,7 +2933,6 @@
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
@@ -3045,7 +3057,6 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/web/vite.config.ts b/web/vite.config.ts
index e8c19bb..0f20c6e 100644
--- a/web/vite.config.ts
+++ b/web/vite.config.ts
@@ -2,13 +2,15 @@ import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
+const apiTarget = process.env.API_PROXY_TARGET || 'http://localhost:8080'
+
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
port: 5173,
proxy: {
'/api': {
- target: 'http://localhost:8080',
+ target: apiTarget,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},