diff --git a/.woodpecker.yml b/.woodpecker.yml index f0d4946..128312e 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,6 +9,33 @@ clone: steps: # COMPONENT_STEPS_BELOW + + # Woodpecker CI step for api service + # Add this step to your .woodpecker.yml + + build-api: + image: woodpeckerci/plugin-kaniko + settings: + registry: registry.threesix.ai + repo: composed5/api + tags: + - latest + - ${CI_COMMIT_SHA:0:8} + context: . + dockerfile: services/api/Dockerfile + cache: true + skip-tls-verify: true + when: + branch: main + event: push + + deploy-api: + image: bitnami/kubectl:latest + commands: + - kubectl set image deployment/composed5-api api=registry.threesix.ai/composed5/api:${CI_COMMIT_SHA:0:8} -n projects || echo "Deployment not found, skipping" + when: + branch: main + event: push # Do not remove the marker above - component steps are inserted here verify: diff --git a/CLAUDE.md b/CLAUDE.md index 4dc5aa6..fe2d847 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,4 +46,7 @@ composed5/ ## Components - +| Component | Type | Path | +|-----------|------|------| +| **api** | API service | `services/api/` | + diff --git a/Procfile b/Procfile index 8e897c6..60fb4f9 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ # Local development processes # Components will be added below as they're created +api: cd services/api && make run diff --git a/go.work b/go.work index 9ffbefe..9b16450 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,5 @@ go 1.23 use ./pkg +use ./services/api // Component modules will be added below diff --git a/services/api/.env.example b/services/api/.env.example new file mode 100644 index 0000000..f14b99c --- /dev/null +++ b/services/api/.env.example @@ -0,0 +1,17 @@ +# api Service Configuration + +# Server +SERVER_PORT=8001 +SERVER_HOST=0.0.0.0 + +# App +APP_NAME=api +APP_ENVIRONMENT=development +APP_DEBUG=true + +# Logging +LOG_LEVEL=debug +LOG_FORMAT=text + +# Database (if needed) +DATABASE_URL=postgres://dev:dev@localhost:5432/composed5?sslmode=disable diff --git a/services/api/Dockerfile b/services/api/Dockerfile new file mode 100644 index 0000000..7ab6d79 --- /dev/null +++ b/services/api/Dockerfile @@ -0,0 +1,35 @@ +# Build stage +FROM golang:1.23-alpine AS builder + +# Install git for go mod download +RUN apk add --no-cache git + +WORKDIR /app + +# Copy go workspace files +COPY go.work go.work.sum* ./ +COPY pkg/go.mod pkg/go.sum* ./pkg/ +COPY services/api/go.mod services/api/go.sum* ./services/api/ + +# Download dependencies +RUN cd services/api && go mod download + +# Copy source +COPY pkg/ ./pkg/ +COPY services/api/ ./services/api/ + +# Build +RUN cd services/api && CGO_ENABLED=0 go build -o /api ./cmd/server + +# Production stage +FROM alpine:3.19 + +RUN apk add --no-cache ca-certificates tzdata + +WORKDIR / + +COPY --from=builder /api /api + +EXPOSE 8001 + +ENTRYPOINT ["/api"] diff --git a/services/api/Makefile b/services/api/Makefile new file mode 100644 index 0000000..f3ed819 --- /dev/null +++ b/services/api/Makefile @@ -0,0 +1,34 @@ +.PHONY: build run test lint fmt docker-build clean + +SERVICE := api +BINARY := bin/$(SERVICE) +GO_MODULE := github.com/jordan/composed5 + +# Build the service binary +build: + go build -o $(BINARY) ./cmd/server + +# Run the service locally +run: + go run ./cmd/server + +# Run tests +test: + go test -v ./... + +# Run linter +lint: + golangci-lint run ./... + +# Format code +fmt: + gofmt -w . + goimports -w -local $(GO_MODULE) . + +# Build Docker image (run from monorepo root) +docker-build: + docker build -t $(SERVICE):latest -f Dockerfile ../.. + +# Clean build artifacts +clean: + rm -rf bin/ diff --git a/services/api/cmd/server/main.go b/services/api/cmd/server/main.go new file mode 100644 index 0000000..9fdf0aa --- /dev/null +++ b/services/api/cmd/server/main.go @@ -0,0 +1,18 @@ +// Package main is the entry point for the api service. +package main + +import ( + "github.com/jordan/composed5/pkg/app" + "github.com/jordan/composed5/services/api/internal/api" +) + +func main() { + // Create application + application := app.New("api", app.WithDefaultPort(8001)) + + // Register routes + api.RegisterRoutes(application) + + // Start server + application.Run() +} diff --git a/services/api/component.yaml b/services/api/component.yaml new file mode 100644 index 0000000..95172bd --- /dev/null +++ b/services/api/component.yaml @@ -0,0 +1,9 @@ +name: api +type: service +port: 8001 +path: services/api +dependencies: [] +# Add dependencies as needed: +# - postgres +# - redis +# - other-service diff --git a/services/api/go.mod b/services/api/go.mod new file mode 100644 index 0000000..6655221 --- /dev/null +++ b/services/api/go.mod @@ -0,0 +1,5 @@ +module github.com/jordan/composed5/services/api + +go 1.23 + +require github.com/jordan/composed5/pkg v0.0.0 diff --git a/services/api/internal/api/handlers/health.go b/services/api/internal/api/handlers/health.go new file mode 100644 index 0000000..185ba9a --- /dev/null +++ b/services/api/internal/api/handlers/health.go @@ -0,0 +1,26 @@ +package handlers + +import ( + "net/http" + + "github.com/jordan/composed5/pkg/httpresponse" + "github.com/jordan/composed5/pkg/logging" +) + +// Health handles health check endpoints. +type Health struct { + logger *logging.Logger +} + +// NewHealth creates a new Health handler. +func NewHealth(logger *logging.Logger) *Health { + return &Health{logger: logger} +} + +// Check returns the service health status. +func (h *Health) Check(w http.ResponseWriter, r *http.Request) { + httpresponse.OK(w, r, map[string]string{ + "service": "api", + "status": "healthy", + }) +} diff --git a/services/api/internal/api/routes.go b/services/api/internal/api/routes.go new file mode 100644 index 0000000..ea0779b --- /dev/null +++ b/services/api/internal/api/routes.go @@ -0,0 +1,21 @@ +// Package api provides HTTP routing and handlers for the api service. +package api + +import ( + "github.com/jordan/composed5/pkg/app" + "github.com/jordan/composed5/services/api/internal/api/handlers" +) + +// RegisterRoutes registers all HTTP routes for the service. +func RegisterRoutes(application *app.App) { + logger := application.Logger() + + // Initialize handlers + healthHandler := handlers.NewHealth(logger) + + // Register API routes + application.Route("/api/v1", func(r app.Router) { + r.Get("/health", healthHandler.Check) + // Add more routes here + }) +} diff --git a/services/api/internal/config/config.go b/services/api/internal/config/config.go new file mode 100644 index 0000000..e797013 --- /dev/null +++ b/services/api/internal/config/config.go @@ -0,0 +1,32 @@ +// Package config provides service-specific configuration. +package config + +import ( + "github.com/jordan/composed5/pkg/config" +) + +// Config extends the base config with api-specific settings. +type Config struct { + config.AppConfig + Server config.ServerConfig + Database config.DatabaseConfig + Logging config.LoggingConfig + // Add service-specific config fields here +} + +// Load reads configuration from environment variables. +func Load() (*Config, error) { + if err := config.Init(config.Options{ + AppName: "api", + DefaultPort: 8001, + }); err != nil { + return nil, err + } + + return &Config{ + AppConfig: config.ReadAppConfig(), + Server: config.ReadServerConfig(), + Database: config.ReadDatabaseConfig(), + Logging: config.ReadLoggingConfig(), + }, nil +} diff --git a/services/api/migrations/.gitkeep b/services/api/migrations/.gitkeep new file mode 100644 index 0000000..e69de29