Getting Started with Development - ericfitz/tmi GitHub Wiki
Getting Started with Development
This guide walks you through setting up your local development environment for both the TMI server (Go) and the TMI web application (Angular/TypeScript).
Overview
TMI consists of two main components:
- TMI Server - Go-based RESTful API server with WebSocket support
- TMI-UX - Angular-based web application for threat modeling
Prerequisites
Required Tools
For TMI Server (Go)
- Go 1.26+ - Download
- Docker - Builds the images (on Docker Desktop it also hosts the cluster node)
- kubectl - The dev environment deploys into a local Kubernetes cluster
- A local cluster - Docker Desktop's built-in Kubernetes (
CLUSTER=docker-desktop) or a dedicated k3s cluster (CLUSTER=k3s) - uv - The
make dev-*targets runscripts/devenv.pythroughuv run - Make - Build automation (pre-installed on macOS/Linux)
For TMI-UX (Angular)
- Node.js 22.x (>=22.0.0 <23.0.0) - Download
- pnpm - Fast, disk space efficient package manager - Install
Optional Tools
- Newman - API testing --
pnpm install -g newman - Grype - Container vulnerability scanning --
brew install grype - golangci-lint - Go code linting
Quick Start
β οΈ Breaking change:
make dev-upnow deploys into a local Kubernetes cluster β it no longer runs the server and workers as host Go processes. See Local Development Cluster for the full cluster onramp (prerequisites, Docker Desktop/k3s setup, database configuration, andDB=oracle). The summary below is the short version.
Option 1: Server Only (Go Backend)
# Clone the repository
git clone https://github.com/ericfitz/tmi.git
cd tmi
# One-time: create your dev config
cp config-example.yml config-development.yml # then edit database.url, OAuth, JWT secret
# Build images, deploy the platform, and port-forward to :8080
# CLUSTER is required: docker-desktop or k3s (DB=postgres|oracle, default postgres)
make dev-up CLUSTER=docker-desktop
# Server will be running on http://localhost:8080
The make dev-up command:
- Builds the server + controller + worker images and delivers them to the cluster (imported into the Docker Desktop node, or pushed to the in-cluster registry on k3s)
- Deploys the server, Redis (TLS + password), NATS (mTLS), KEDA, the component controller, and the workers into the
tmi-platformnamespace, installing cert-manager and Reloader as needed - With
DB=postgres(the default), deploys an in-cluster Postgres StatefulSet - Delivers your
config-development.ymlinto the cluster as a ConfigMap; apostgres://user:pass@localhostβ¦database.urlis rewritten to the in-clusterpostgresService - Waits for the rollout and starts a managed
kubectl port-forwardtohttp://localhost:8080
go is also required: dev-up/dev-down/dev-reset/dev-nuke build bin/tmi-dbtool to snapshot and restore operational settings. See Local Development Cluster β Database configuration for cloud Postgres and the Oracle (DB=oracle, Docker Desktop only) path.
Option 2: Full Stack (Server + Web App)
Terminal 1 - Start Server:
cd tmi
make dev-up CLUSTER=docker-desktop
Terminal 2 - Start Web Application:
cd tmi-ux
pnpm install
pnpm run dev
The web application will be available at http://localhost:4200
TMI Server Setup (Detailed)
1. Environment Configuration
TMI uses YAML configuration files with environment variable overrides. On first setup, copy config-example.yml to config-development.yml:
# Copy example config and customize
cp config-example.yml config-development.yml
Environment variables use the TMI_ prefix and override any YAML value. Key variables:
TMI_DATABASE_URL- Database connection URL (required), e.g.,postgres://user:pass@localhost:5432/tmi?sslmode=disableTMI_SERVER_PORT- Server port (default: 8080)TMI_JWT_SECRET- JWT signing secret
The configuration file covers:
- Database connection via URL (PostgreSQL, MySQL, SQLite, SQL Server, Oracle)
- Redis connection (caching and sessions)
- OAuth provider credentials
- Server settings (ports, timeouts, TLS)
- Cookie-based authentication settings
2. Database Setup
With DB=postgres (the default), make dev-up deploys Postgres inside the cluster (StatefulSet + PVC) and the server connects to it. Keep database.url pointing at localhost in config-development.yml (as host-side tools expect); when dev-up renders the ConfigMap it rewrites a postgres://user:pass@localhost⦠(or @127.0.0.1) URL to the in-cluster postgres Service. The schema is built automatically by GORM AutoMigrate() on the server's first successful connect; there is no separate manual migration step in the dev loop.
# config-development.yml
database:
url: "postgres://tmi_dev:dev123@localhost:5432/tmi_dev?sslmode=disable"
To reach the in-cluster Postgres from the host: kubectl port-forward -n tmi-platform svc/postgres 5432:5432. The standalone make start-database container still exists but dev-up does not use it. A remote database.url is left as written (works, untested), and DB=oracle targets an external OCI ADB (Docker Desktop only).
For cloud Postgres or OCI ADB (DB=oracle + wallet), see Local Development Cluster β Database configuration.
3. OAuth Provider Configuration
To enable authentication, configure OAuth providers in config-development.yml. TMI supports Google, GitHub, and Microsoft providers.
Google OAuth Setup
- Go to Google Cloud Console
- Create OAuth 2.0 credentials
- Add redirect URI:
http://localhost:8080/oauth2/callback - Set environment variables or update
config-development.yml:
# Via environment variables (recommended):
export OAUTH_PROVIDERS_GOOGLE_CLIENT_ID=YOUR_CLIENT_ID.apps.googleusercontent.com
export OAUTH_PROVIDERS_GOOGLE_CLIENT_SECRET=YOUR_CLIENT_SECRET
# Or via config-development.yml under auth.oauth.providers.google:
auth:
oauth:
callback_url: "http://localhost:8080/oauth2/callback"
providers:
google:
id: "google"
name: "Google"
enabled: true
client_id: "YOUR_CLIENT_ID.apps.googleusercontent.com"
client_secret: "YOUR_CLIENT_SECRET"
authorization_url: "https://accounts.google.com/o/oauth2/auth"
token_url: "https://oauth2.googleapis.com/token"
userinfo:
- url: "https://www.googleapis.com/oauth2/v3/userinfo"
claims: {}
scopes:
- "openid"
- "profile"
- "email"
TMI Test OAuth Provider
For development, you can use the built-in TMI provider (no configuration needed):
# Access TMI OAuth provider (creates random user)
curl "http://localhost:8080/oauth2/authorize?idp=tmi"
The TMI provider creates users like: [email protected]
For predictable test users, use login hints:
# Create specific test user '[email protected]'
curl "http://localhost:8080/oauth2/authorize?idp=tmi&login_hint=alice"
4. Available Make Commands
# Development environment (CLUSTER=docker-desktop|k3s is REQUIRED on every dev-* target; DB=postgres|oracle, default postgres)
make dev-cluster-up CLUSTER=<c> # Only switches the kube context; creates nothing
make dev-cluster-down CLUSTER=<c> # No-op (TMI does not own the cluster)
make dev-up CLUSTER=<c> # Build images + deploy the platform into the cluster
make dev-restart CLUSTER=<c> # Rebuild all four images, re-apply the platform, roll the pods
make dev-down CLUSTER=<c> # Tear down what dev-up deployed; keeps DB data and the cluster
make dev-reset CLUSTER=<c> # dev-down + dev-up with fresh images; keeps DB data
make dev-nuke CLUSTER=<c> # Delete the tmi-platform namespace (including DB data) and rebuild
# Building
make build-server # Build server binary
make build-migrate # Build migration tool
make clean-build # Clean build artifacts
make generate-api # Generate API code from OpenAPI spec
# Database (optional local Postgres container)
make start-database # Start a local PostgreSQL container
make stop-database # Stop the PostgreSQL container
make clean-database # Remove container and data
make reset-database # Drop and recreate database (DESTRUCTIVE)
# Testing
make test-unit # Run Go unit tests
make test-integration # Run integration tests (PostgreSQL)
make test-integration-pg # Run integration tests (PostgreSQL, explicit)
make test-api # Run Newman API tests
make test-coverage # Generate coverage reports
The schema is built by GORM
AutoMigrate()on first connect β there is no separatemake migrate-databasestep in the cluster dev loop. See Local Development Cluster for the full command reference.
5. Verify Server Installation
# Check server status
curl http://localhost:8080/
# Expected response
{
"api": {
"version": "1.0",
"build": "0.9.0"
},
"service": {
"name": "TMI API Server",
"build": "0.9.0-abc123"
}
}
TMI-UX Setup (Detailed)
1. Install Dependencies
cd tmi-ux
pnpm install
2. Environment Configuration
TMI-UX supports multiple environment configurations:
# Default development (uses environment.ts)
pnpm run dev
# Test environment
pnpm run dev:test
# Production environment (local)
pnpm run dev:prod
Creating Custom Environment
- Copy example:
cp src/environments/environment.example.ts src/environments/environment.local.ts - Configure
environment.local.ts:
import { Environment } from './environment.interface';
export const environment: Environment = {
production: false,
logLevel: 'DEBUG',
apiUrl: 'http://localhost:8080',
authTokenExpiryMinutes: 1440, // 24 hours for dev
operatorName: 'TMI Development',
operatorContact: '[email protected]',
operatorJurisdiction: '',
suppressAboutLink: false,
suppressPrivacyTosLinks: false,
serverPort: 4200,
serverInterface: 'localhost',
enableTLS: false,
tlsKeyPath: undefined,
tlsCertPath: undefined,
tlsSubjectName: undefined,
enableConfidentialThreatModels: false,
defaultThreatModelFramework: 'STRIDE',
};
- Update
angular.jsonto add configuration:
"configurations": {
"local": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.local.ts"
}
]
}
}
3. Available pnpm Scripts
# Development
pnpm run dev # Start dev server on localhost:4200 (opens browser)
pnpm run dev:test # Start with test config
pnpm run dev:prod # Start with production config
pnpm run shannon # Start on 0.0.0.0 (accessible from network)
# Building
pnpm run build # Development build
pnpm run build:prod # Production build
pnpm run build:test # Test build
# Testing (Vitest)
pnpm run test # Run Vitest unit tests
pnpm run test:watch # Run tests in watch mode
pnpm run test:ui # Run tests with Vitest UI
pnpm run test:coverage # Generate coverage report
# E2E Testing (Playwright)
pnpm run test:e2e # Run Playwright E2E tests
pnpm run test:e2e:ui # Open Playwright UI
pnpm run test:e2e:debug # Run E2E tests in debug mode
pnpm run test:e2e:headed # Run E2E tests in headed browser
# Code Quality
pnpm run lint # Lint TypeScript/HTML files
pnpm run lint:scss # Lint SCSS files
pnpm run lint:all # Lint everything
pnpm run format # Format code with Prettier
pnpm run format:check # Check formatting
pnpm run check # Run format:check + lint:all
# Validation
pnpm run validate-json # Validate JSON files
pnpm run validate-json:i18n # Validate i18n files
pnpm run validate-jsonc # Validate JSONC files
pnpm run validate-all # Run all validation checks
4. Verify Web Application
# Start dev server
pnpm run dev
# Open browser to http://localhost:4200
# You should see the TMI login page
Development Workflow
1. Server Development
# Terminal 1: Start services
cd tmi
make dev-up CLUSTER=docker-desktop
# Terminal 2: Run tests on code changes
make test-unit
# Terminal 3: Watch server logs (from the pod)
kubectl -n tmi-platform logs deploy/tmi-server -f
To pick up server code changes, run
make dev-restart CLUSTER=<c>(rebuilds all four images and rolls the pods).
2. Web Application Development
# Terminal 1: Start server
cd tmi
make dev-up CLUSTER=docker-desktop
# Terminal 2: Start web app
cd tmi-ux
pnpm run dev
# Hot reload is enabled - changes reflect immediately
3. Full Stack Testing
# Terminal 1: Start server
cd tmi
make dev-up CLUSTER=docker-desktop
# Terminal 2: Start web app
cd tmi-ux
pnpm run dev
# Terminal 3: Run E2E tests
cd tmi-ux
pnpm run test:e2e
Project Structure
TMI Server (Go)
tmi/
βββ api/ # API handlers and types
βββ api-schema/ # OpenAPI spec (tmi-openapi.json), AsyncAPI, Arazzo
βββ auth/ # Authentication services
βββ cmd/
β βββ server/ # Server entry point
β βββ migrate/ # Database migration tool
β βββ dbtool/ # Database administration tool (seeding, CATS test data, etc.)
β βββ dedup-group-members/ # Group member deduplication
βββ config/ # Configuration loading
βββ internal/ # Internal packages
βββ docs/
β βββ developer/ # Developer documentation
βββ scripts/ # Build and utility scripts
βββ test/
β βββ postman/ # Newman API test collections
βββ terraform/ # Infrastructure as code
βββ config-example.yml # Example configuration
βββ config-development.yml # Dev configuration (create from example)
βββ Makefile # Build automation
TMI-UX (Angular)
tmi-ux/
βββ src/
β βββ app/
β β βββ auth/ # Authentication components
β β βββ core/ # Core services, guards
β β βββ generated/ # Generated API types
β β βββ i18n/ # Internationalization setup
β β βββ pages/ # Page components
β β βββ shared/ # Shared components
β β βββ types/ # TypeScript type definitions
β βββ assets/ # Static assets, i18n JSON files
β βββ environments/ # Environment configs
βββ e2e/ # Playwright E2E tests
βββ docs/
β βββ mockups/ # UI mockups
β βββ reference/ # Architecture docs
βββ scripts/ # Build and utility scripts
βββ playwright.config.ts # Playwright configuration
βββ package.json # Dependencies and scripts
File Naming Conventions
This section outlines the file naming conventions for the TMI-UX project.
File Naming Standards
| Type | Pattern | Example | Usage |
|---|---|---|---|
| Components | *.component.ts |
user-preferences-dialog.component.ts |
All Angular components |
| Services | *.service.ts |
threat-model.service.ts |
All @Injectable() classes that provide business logic |
| Guards | *.guard.ts |
auth.guard.ts |
All route guards |
| Interceptors | *.interceptor.ts |
jwt.interceptor.ts |
All HTTP interceptors |
| Resolvers | *.resolver.ts |
threat-model.resolver.ts |
All route resolvers |
| Models | *.model.ts |
threat-model.model.ts |
Data models and interface definitions |
| Interfaces | *.interface.ts |
auth.interface.ts |
Interface definitions (alternative to .model.ts) |
| Types | *.types.ts |
websocket-message.types.ts |
Type definitions and type aliases |
| Constants | *-constants.ts |
styling-constants.ts |
Constant values and configuration objects |
| Utilities | *.util.ts or *.utils.ts |
cell-normalization.util.ts |
Utility functions and helper methods |
| Adapters | *.adapter.ts |
infra-x6-graph.adapter.ts |
Infrastructure adapters for external libraries |
| Tests | *.spec.ts |
auth.service.spec.ts |
Unit tests |
| Routes | *.routes.ts |
tm.routes.ts |
Route configuration files |
| State | *.state.ts |
dfd.state.ts |
State management files |
| Barrel Exports | index.ts |
src/app/shared/index.ts |
Re-exporting multiple items from a directory |
DFD Module Structure (Domain-Driven Design)
The DFD (Data Flow Diagram) module uses a layered DDD architecture:
src/app/pages/dfd/
βββ application/ # Application services (orchestration, coordinators)
β βββ executors/ # Operation executors
β βββ services/ # app-*.service.ts files
β βββ validators/ # Operation validators
βββ domain/ # Domain models and events
β βββ entities/ # Domain entities
β βββ events/ # Domain events
β βββ value-objects/ # Value objects
βββ infrastructure/ # External library adapters
β βββ adapters/ # infra-*.adapter.ts files
β βββ constants/ # Infrastructure configuration
β βββ interfaces/ # Adapter interfaces
β βββ services/ # infra-*.service.ts files
βββ presentation/ # UI components and services
β βββ components/ # UI components
β βββ services/ # ui-*.service.ts files
βββ constants/ # Shared constants
βββ interfaces/ # Shared interfaces
βββ state/ # State management
βββ types/ # Type definitions
βββ utils/ # Utility functions
Naming Rules
-
Use kebab-case for file names
- Correct:
threat-model.service.ts - Incorrect:
ThreatModelService.tsorthreat_model_service.ts
- Correct:
-
Be descriptive in naming
- Correct:
threat-model-authorization.service.ts - Incorrect:
tm-auth.service.ts
- Correct:
-
Group related files by feature
- Keep related components, services, and models together
- Use barrel exports (index.ts) for clean imports
-
Match class names to file names
- File:
threat-model.service.ts - Class:
ThreatModelService
- File:
-
Use layer prefixes in DFD module
- Application layer:
app-*.service.ts - Infrastructure layer:
infra-*.service.ts,infra-*.adapter.ts - Presentation layer:
ui-*.service.ts
- Application layer:
Migration Notes
When renaming files:
- Update all imports in the codebase
- Update any barrel exports (index.ts files)
- Check for impacts on tests
- Run build and tests to verify
Common Development Tasks
Adding a New API Endpoint (Server)
- Define endpoint in OpenAPI spec:
api-schema/tmi-openapi.json - Generate API code:
make generate-api - Implement handler in
api/ - Add database operations if needed
- Write unit tests
- Write integration tests in
api/*_integration_test.go - Add Newman tests in
test/postman/
Adding a New UI Feature (Web App)
- Generate component:
pnpm exec ng generate component pages/my-feature - Add to routing:
src/app/app.routes.ts - Create service if needed:
pnpm exec ng generate service core/services/my-service - Add i18n keys:
src/assets/i18n/en-US.json - Write unit tests:
*.spec.ts - Write E2E tests:
e2e/tests/*.spec.ts(Playwright)
Troubleshooting
Server Won't Start
# Check the rollout / pod status
kubectl -n tmi-platform get pods
kubectl -n tmi-platform rollout status deploy/tmi-server
# Read the server logs from the pod
kubectl -n tmi-platform logs deploy/tmi-server -f
# With DB=postgres, a postgres://user:pass@localhost... database.url is rewritten
# to the in-cluster Postgres; other URLs must be reachable from inside a pod.
# Re-deliver config + roll the pods after editing config-development.yml
make dev-restart CLUSTER=docker-desktop
See Local Development Cluster β Troubleshooting for ImagePullBackOff, context-guard, and port-forward issues.
Database Issues
The schema is built by GORM AutoMigrate() on the server's first successful connect β there is no manual migration step. Since server 1.18.0, the server retries the database and Redis connections at startup with exponential backoff (0.5s, doubling to at most 5s) for up to 30 seconds of wall-clock time. It does not retry database errors a retry cannot fix, such as bad or locked credentials, an unknown TNS alias, wallet or certificate failures, a missing Oracle client, or a missing Postgres database. Redis connection errors are always retried until the 30 seconds run out. If it still can't connect, it exits, the pod logs the connection error, and Kubernetes restarts it. A common cause in the cluster is a database.url host that is unreachable from a pod (localhost is only rewritten for postgres://user:pass@localhostβ¦ URLs with DB=postgres).
# Reset a local Postgres container (DESTRUCTIVE - deletes all data)
make reset-database
Web App Won't Connect to API
- Check server is running:
curl http://localhost:8080/ - Verify
apiUrlin environment file - Check browser console for CORS errors
- Ensure you're authenticated (valid JWT token)
OAuth Not Working
- Check OAuth config in
config-development.yml - Verify redirect URI matches OAuth provider
- Use TMI provider for development:
?idp=tmi - Check server logs:
tail -f logs/server.log
Next Steps
- Architecture-and-Design -- Understand system architecture
- API-Integration -- Learn to integrate with TMI APIs
- Testing -- Testing strategies and tools
- Contributing -- Contribution guidelines
Getting Help
- Documentation: See
/docsin both repositories - Issues: https://github.com/ericfitz/tmi/issues
- API Reference: http://localhost:8080/ (when server is running)
- OpenAPI Spec:
api-schema/tmi-openapi.json