Skip to content

Development Setup ​

Prerequisites ​

  • Java 21 (all modules compile and run with Java 21)
  • Maven 3.8+ (Maven wrapper mvnw is included in the repo)
  • Docker and Docker Compose

Quick Start (Full Stack) ​

Starting the complete stack requires three repositories and multiple terminal sessions.

Backend (schemastack-be) ​

bash
# Terminal 1 — Quarkus REST + infrastructure (Docker, RabbitMQ, PostgreSQL)
./scripts/dev-start.sh

# Terminal 2 — Spring Boot Processor (DDL/migrations)
./scripts/processor-start.sh

# Terminal 3 — Spring Boot Workspace API (dynamic CRUD)
./scripts/workspace-api-start.sh

# Terminal 4 — ngrok tunnel for LemonSqueezy webhooks
ngrok http 8080

Frontend (schemastack-fe) ​

bash
# Terminal 5 — App (Data Platform)
ng serve --port=4200 app

# Terminal 6 — Admin Console
ng serve --port=4201 admin

Docs (schemastack-docs) ​

bash
npm run dev        # Public docs
npm run dev:dev    # Dev docs

Backend Tests ​

bash
# Run full test suite in an isolated copy (non-blocking)
./scripts/run-tests-background.sh

# Run only a specific test module
./scripts/run-tests-background.sh workspace-api
./scripts/run-tests-background.sh metadata
./scripts/run-tests-background.sh organisation
./scripts/run-tests-background.sh processor

# Check progress / follow output
./scripts/run-tests-background.sh --status
./scripts/run-tests-background.sh --watch

# Auto-run tests on file changes (requires: brew install fswatch)
./scripts/run-tests-watch.sh                    # Watch all tests
./scripts/run-tests-watch.sh workspace-api      # Watch single module
./scripts/run-tests-watch.sh --idle 5           # Custom idle seconds
./scripts/run-tests-watch.sh --stop             # Stop watcher

Infrastructure Services ​

Docker Compose starts two services:

ServicePortCredentials
PostgreSQL 18localhost:5432postgres / your-password, database dynamicdb
RabbitMQ AMQPlocalhost:5673guest / guest
RabbitMQ Managementlocalhost:15673guest / guest

Non-standard RabbitMQ port

RabbitMQ runs on 5673 (not the default 5672) to avoid conflicts with other local services. The management UI is on 15673.

Environment Files ​

FilePurpose
.envBase configuration (database, JWT, mailer, RabbitMQ)
.env.localLocal development overrides
.env.testTest configuration with TestContainers and in-memory messaging
~/.docker-java.propertiesRequired for Docker Desktop 4.52+ (Engine 29.x) — see Testing > Testcontainers compatibility

OAuth Provider Setup (Google & GitHub) ​

OAuth social login requires client credentials from each provider. These are not needed to run the app locally unless you're testing OAuth flows.

Google ​

  1. Go to Google Cloud Console → Credentials
  2. Create a project (or select existing)
  3. Click Create Credentials → OAuth 2.0 Client ID
  4. Application type: Web application
  5. Add Authorized redirect URIs:
    • Development: http://localhost:4200/auth/callback/google
    • Production: https://app.schemastack.io/auth/callback/google
  6. Copy the Client ID and Client Secret

GitHub ​

  1. Go to GitHub → Settings → Developer settings → OAuth Apps
  2. Click New OAuth App
  3. Set Authorization callback URL:
    • Development: http://localhost:4200/auth/callback/github
    • Production: https://app.schemastack.io/auth/callback/github
  4. Copy the Client ID and Client Secret

Configuration ​

Add to .env.local:

bash
OAUTH_GOOGLE_CLIENT_ID=your-google-client-id
OAUTH_GOOGLE_CLIENT_SECRET=your-google-client-secret
OAUTH_GITHUB_CLIENT_ID=your-github-client-id
OAUTH_GITHUB_CLIENT_SECRET=your-github-client-secret

The backend endpoint is POST /api/auth/oauth/{provider} (where provider is google or github). The frontend sends the authorization code received from the provider callback.

Timezone (UTC Everywhere) ​

All services, databases, and JVMs are configured to use UTC. This prevents timezone mismatch bugs between layers.

Where it's configured ​

LayerFileSetting
PostgreSQL containerdocker-compose.override.ymlcommand: -c timezone=UTC + TZ: UTC env
Quarkus JVMscripts/dev-start.shTZ=UTC env + -Duser.timezone=UTC JVM arg
Quarkus Hibernate (metadata)quarkus/metadata/metadata-persistence/.../application.propertiesquarkus.hibernate-orm.jdbc.timezone=UTC
Quarkus Hibernate (organisation)quarkus/organisation/organisation-persistence/.../application.propertiesquarkus.hibernate-orm.jdbc.timezone=UTC
Processor JVMscripts/processor-start.shTZ=UTC env + -Duser.timezone=UTC JVM arg
Processor Hibernatespring-boot/processor/processor-service/.../application.propertiesspring.jpa.properties.hibernate.jdbc.time_zone=UTC
Processor Jacksonspring-boot/processor/processor-service/.../application.propertiesspring.jackson.time-zone=UTC
Workspace API JVMscripts/workspace-api-start.shTZ=UTC env + -Duser.timezone=UTC JVM arg
Workspace API Jacksonspring-boot/workspace-api/workspace-api-service/.../application.propertiesspring.jackson.time-zone=UTC
Workspace API dynamic SessionFactoriesDynamicEntitySessionFactoryBuilder.javahibernate.jdbc.time_zone=UTC

Why three layers per service ​

  • TZ env var — sets the OS-level timezone for the process (affects LocalDateTime.now(), log timestamps)
  • -Duser.timezone=UTC — sets the JVM timezone (affects java.util.Date, JDBC drivers)
  • hibernate.jdbc.time_zone=UTC — tells Hibernate to normalize all Timestamp/Instant values to UTC when reading/writing to the database

All three are needed because each layer can independently default to the host machine's local timezone.

Production ​

Production Docker containers set TZ: UTC in their Compose/ECS task definitions and pass -Duser.timezone=UTC as a JVM argument. The Hibernate and Jackson properties are baked into the application properties files.

Building ​

bash
# Full recompile without tests (fast)
./mvnw clean install -DskipTests=true

# Full recompile with tests (slow)
./mvnw clean install -DskipTests=false

# Build a specific module
./mvnw clean install -pl quarkus/metadata/metadata-rest -am

Service URLs ​

ServiceURL
Quarkus REST APIhttp://localhost:8080
Spring Boot Processorhttp://localhost:8082
Spring Boot Workspace APIhttp://localhost:8083
RabbitMQ Managementhttp://localhost:15673

Data Persistence ​

Docker volumes persist data between restarts:

  • postgres_data — PostgreSQL data
  • rabbitmq_data — RabbitMQ state
  • rabbitmq_logs — RabbitMQ logs

To reset all data: docker compose down -v

RabbitMQ Consumer Timeout ​

The Docker Compose config sets a 30-second consumer timeout (consumer_timeout 30000). This prevents zombie consumers from accumulating during Quarkus dev mode hot-reloads.

Scripts Reference ​

All scripts live in the scripts/ directory.

Startup & Shutdown ​

ScriptDescription
dev-start.shStart Docker services + Quarkus dev mode. Pass --reset to drop and recreate dynamicdb. Also purges dev RabbitMQ queues and cleans up failed migrations.
dev-stop.shKill Maven processes (graceful, then force) and docker compose down.
processor-start.shStart the Spring Boot Processor on port 8082. Checks that Quarkus is running on 8080 first.
workspace-api-start.shStart the Spring Boot Workspace API on port 8083. Checks that Quarkus is running on 8080 first.

Build Helpers ​

ScriptDescription
install-contracts.shBuild and install messaging-contracts to local Maven (~/.m2). Run this after changing shared message DTOs so both Quarkus and Spring Boot pick them up.

Testing ​

ScriptDescription
run-tests-background.shRsync to isolated directory and run tests in background. Pass a module name (metadata, organisation, processor, workspace-api) to run only that module's tests. Use --watch to follow output, --status to check progress. Sends macOS notifications.
run-tests-watch.shWatch for file changes using fswatch and auto-run tests after idle (default 10s, --idle <seconds>). Accepts optional module name to scope tests. --stop to stop. Requires brew install fswatch.

Diagnostics ​

ScriptDescription
rabbitmq-status.shQuery RabbitMQ Management API. Subcommands: status (queue overview), messages [queue] (peek at messages), dlq (dead letter queue), purge <queue> (empty a queue).

Database Diagnostics (scripts/diagnostics/) ​

Run via diagnostics/run-all-diagnostics.sh (interactive — prompts for credentials and runs all checks), or individually:

ScriptRun againstDescription
check-duplicate-columns.sqlMetadata DB (dynamicdb)Find duplicate columns in the metadata tables. Drift detection crashes with "Duplicate key" errors when these exist.
cleanup-duplicate-columns.sqlMetadata DB (dynamicdb)Delete duplicate columns, keeping the first occurrence (lowest ID). Runs in a transaction — any error rolls back.
check-workspace-catalog.sqlWorkspace DB (customer)Check PostgreSQL pg_attribute system catalog for duplicate columns. Rows found = real database corruption (not just a code bug).

Liquibase Migrations ​

Three independent Liquibase changelogs (the metadata master includes the other two):

ModuleMaster FileChangesets
Metadatametadata-persistence/.../changelog-metadata.xml46 (1.0 — 1.47)
Organisationorganisation-persistence/.../changelog-organisation.xml11 (1.0 — 6.0)
Messagingmessaging-infrastructure/.../changelog-messaging.xml1 (1.0)
Total3 masters58 production changesets

Key migration landmarks:

VersionWhat
1.0Initial schema (entities, views, columns)
1.3RBAC permissions
1.20Relationship column support
1.21-1.22Bulk action jobs
1.23-1.25Filter presets + sorts
1.26Sync operation tracking
1.27Outbox messages
1.32View guest access
1.40Workspace API keys
1.47Usage tracking
org-4.4Two-factor authentication
org-6.0Subscription tiers

Dev seed data files (context="dev") provide local development fixtures: test users, organisations, workspace memberships.

Deployment ​

Two deployment patterns:

Lambda (REST API) ​

dockerfile
FROM public.ecr.aws/lambda/provided:al2
COPY target/function/bootstrap /var/task/bootstrap
CMD ["bootstrap"]

Quarkus native binary built for Lambda. Stateless, cold start optimized.

Fargate (Consumer Worker) ​

Multi-stage Maven build → JVM-based Quarkus fast-jar:

  • Build: maven:3.9.9-eclipse-temurin-21 with Maven cache mount
  • Runtime: eclipse-temurin:21-jre-alpine
  • Build arg: QUARKUS_PROFILE=worker
  • Copies cert.pem for TLS
  • Port 8080

Claude Code — Chrome DevTools MCP Setup ​

When Claude Code runs inside Docker, it can control a browser on the host machine via the Chrome DevTools Protocol (CDP). This enables visual testing, screenshot capture, and UI interaction directly from Claude.

1. Launch Brave with Remote Debugging ​

On the host machine (macOS), start Brave with CDP enabled:

bash
/Applications/Brave\ Browser.app/Contents/MacOS/Brave\ Browser \
  --remote-debugging-port=9222 \
  --remote-allow-origins="*" \
  --remote-debugging-address=0.0.0.0 \
  --user-data-dir="$HOME/.config/quarkus-mcp"

Key flags:

  • --remote-debugging-port=9222 — CDP WebSocket port
  • --remote-allow-origins="*" — allow connections from Docker
  • --remote-debugging-address=0.0.0.0 — listen on all interfaces (not just localhost)
  • --user-data-dir — separate profile so it doesn't interfere with your main browser

2. Get the WebSocket Endpoint ​

After Brave launches, fetch the CDP WebSocket URL:

bash
curl -s http://localhost:9222/json/version | jq -r '.webSocketDebuggerUrl'

This returns something like:

ws://localhost:9222/devtools/browser/e0f483c8-d843-4d3d-98d4-9c8877a76136

3. Configure .mcp.json ​

In the workspace root (.mcp.json), add the chrome-devtools server. Replace the WebSocket URL with the one from step 2:

json
{
  "mcpServers": {
    "chrome-devtools": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "chrome-devtools-mcp@latest",
        "--wsEndpoint",
        "ws://host.docker.internal:9222/devtools/browser/<browser-id>",
        "--wsHeaders",
        "{\"Host\":\"localhost\"}"
      ],
      "env": {}
    }
  }
}

Key details:

  • host.docker.internal — Docker's magic hostname that resolves to the host machine
  • --wsHeaders '{"Host":"localhost"}' — required because Brave validates the Host header against its allowed origins; without this, the connection is rejected
  • The browser ID in the WebSocket URL changes each time Brave restarts — update it after restarting

4. Available Capabilities ​

Once connected, Claude Code can:

  • Navigate — open URLs, go back/forward, reload
  • Take snapshots — get the accessibility tree (DOM structure with UIDs)
  • Take screenshots — capture the viewport as an image
  • Click/fill/type — interact with elements by UID
  • Evaluate scripts — run JavaScript in the page context
  • Inspect network/console — view requests and console messages
  • Emulate — set viewport size, dark mode, network throttling

SchemaStack Internal Developer Documentation