Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .env.prod.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Production Environment Variables
# Copy to .env.prod and set actual values before deployment

# Server
PORT=8080
ENV=production

# Database (use strong passwords in production!)
DATABASE_URL=postgres://intent:YOUR_PROD_PASSWORD@postgres:5432/intent?sslmode=require

# Redis
REDIS_URL=redis://redis:6379

# Circle API (required for wallet + settlement)
CIRCLE_API_KEY=your_circle_production_api_key
107 changes: 107 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: CI

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: intent
POSTGRES_PASSWORD: intent_test
POSTGRES_DB: intent_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26'

- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Download dependencies
run: go mod download

- name: Run tests
run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
env:
DATABASE_URL: postgres://intent:intent_test@localhost:5432/intent_test?sslmode=disable
REDIS_URL: redis://localhost:6379

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage.txt

lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26'

- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

- name: Run golangci-lint
run: golangci-lint run --timeout=5m

build:
runs-on: ubuntu-latest
needs: [test, lint]

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26'

- name: Build
run: go build -v -o bin/server ./cmd/server
env:
CGO_ENABLED: 0

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: server-${{ runner.os }}
path: bin/server
retention-days: 5
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ bin/
*.sqlite
*.sqlite3
*.bak
*.backup
*.backup
tmp/
vendor/
coverage.txt
.air.toml
19 changes: 19 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
run:
timeout: 5m
tests: true

linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- typecheck
- unused

issues:
exclude-rules:
- path: _test\.go
linters:
- errcheck
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM golang:1.26-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server

# Final image
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=builder /app/server .
COPY .env.example .env

EXPOSE 8080

CMD ["./server"]
110 changes: 106 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,81 @@ make clean # Remove build artifacts

---

## CI/CD

GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every push and PR:

**Jobs:**
1. **Test** — `go test -race` with PostgreSQL + Redis services, coverage reported to Codecov
2. **Lint** — golangci-lint (errcheck, govet, unused, gofmt, goimports, etc.)
3. **Build** — Static binary compilation (CGO disabled), artifacts retained 5 days

**Docker:**
Build and run production image:
```bash
# Build image
docker build -t intent-backend:v1 .

# Run with docker-compose (recommended)
docker-compose -f docker-compose.prod.yml up

# Or run standalone
docker run -p 8080:8080 \
-e DATABASE_URL="postgres://..." \
-e REDIS_URL="redis://..." \
intent-backend:v1
```

**Production Setup:**
- `docker-compose.prod.yml` orchestrates backend + PostgreSQL + Redis
- `Dockerfile` packages the Go binary (44MB Alpine image)
- `.env.prod.example` shows required production configuration
- See [Docker](#docker) section below for details

---

## Docker

### Development (local)
```bash
make db-up # Start postgres + redis only
make dev # Run Go server locally with live reload
```

### Production (containerized)
```bash
# Build image
docker build -t intent-backend:v1 .

# Run orchestrated stack
docker-compose -f docker-compose.prod.yml up
```

**docker-compose.prod.yml** provisions:
- **Backend container** — from Dockerfile
- **PostgreSQL 16** — persistent volume, health checks
- **Redis 7** — persistent volume, health checks
- Auto-restart on crash, ready for systemd/K8s

**Configuration:**
```bash
cp .env.prod.example .env.prod
# Edit .env.prod with:
# - DATABASE_URL (use sslmode=require in prod)
# - REDIS_URL
# - CIRCLE_API_KEY
docker-compose -f docker-compose.prod.yml up
```

### Image Details
- **Base:** Alpine Linux (7MB)
- **Binary:** Static Go (no CGO, fully portable)
- **Size:** ~44MB (uncompressed)
- **Includes:** CA certificates for HTTPS/TLS
- **Port:** 8080 (configurable)

---

## Development Workflow

### Adding new API endpoints
Expand Down Expand Up @@ -282,14 +357,41 @@ func (h *Handler) CreateIntent(w http.ResponseWriter, r *http.Request) {
}
```

### Broadcasting real-time updates
### API Endpoints

**Intents:**
- `POST /api/v1/intents` — submit new intent
- `GET /api/v1/intents/{intentID}` — fetch intent status
- `GET /api/v1/intents/{intentID}/proposals` — list proposals for intent

**Proposals:**
- `POST /api/v1/intents/{intentID}/proposals` — agent submits proposal
- `POST /api/v1/intents/{intentID}/select` — score proposals and select winner

**Execution:**
- `POST /api/v1/intents/{intentID}/validate` — validate execution against constraints
- `POST /api/v1/intents/{intentID}/settle` — settle execution and update reputation

**Leaderboard & Agents:**
- `GET /api/v1/leaderboard` — ranked agent list (score, win rate, slippage)
- `GET /api/v1/agents/{agentID}` — agent profile + history

### Broadcasting Real-Time Updates

All state changes broadcast via WebSocket to subscribed clients:

```go
// When a proposal arrives
h.hub.Broadcast(intentID, "proposal_received", proposal)

// Frontend receives via WebSocket:
// { "type": "proposal_received", "payload": {...proposal...} }
// When a winner is selected
h.hub.Broadcast(intentID, "winner_selected", winnerData)

// When execution settles
h.hub.Broadcast(intentID, "execution_settled", executionData)

// Frontend receives:
// { "type": "proposal_received", "payload": {...} }
```

---
Expand All @@ -302,7 +404,7 @@ Issue tracking for planned backend work:
|-------|-------------|--------|
| #4 | Server setup (HTTP + WebSocket) | ✅ Done |
| #2 | Database tables (models + migrations) | ✅ Done |
| #3 | API layer (all REST endpoints) | 🔄 In progress |
| #3 | API layer (all REST endpoints) | ✅ Done |
| #5 | Redis pub/sub integration | 🔄 Planned |
| #6 | Scoring engine | 🔄 Planned |
| #7 | Execution validation service | 🔄 Planned |
Expand Down
50 changes: 50 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
services:
backend:
build: .
container_name: intent-backend
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://intent:intent_prod_password@postgres:5432/intent?sslmode=disable
REDIS_URL: redis://redis:6379
PORT: 8080
ENV: production
CIRCLE_API_KEY: ${CIRCLE_API_KEY:-}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped

postgres:
image: postgres:16-alpine
container_name: intent-postgres-prod
environment:
POSTGRES_USER: intent
POSTGRES_PASSWORD: intent_prod_password
POSTGRES_DB: intent
volumes:
- postgres_prod_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U intent"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

redis:
image: redis:7-alpine
container_name: intent-redis-prod
volumes:
- redis_prod_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

volumes:
postgres_prod_data:
redis_prod_data:
Loading
Loading