Skip to content

Commit 1687b23

Browse files
authored
refactor: align implementations and improve configuration consistency (#62)
- Fix Rust credential defaults to match Vault bootstrap (dev_admin, dev_database) - Fix Rust password fallbacks from empty string to "changeme" - Align CORS origins across FastAPI code-first and API-first implementations - Fix Go RabbitMQ user default from "guest" to "dev_admin" - Fix TypeScript CORS to include all reference app ports - Add missing port/TLS environment variables to .env.example - Standardize version to 1.1.0 across all reference apps (Go, Node.js, Rust) - Fix Go .gitignore to not exclude cmd/api source directory - Add Go cmd/api/main.go entry point (was missing from git tracking)
1 parent e3eff88 commit 1687b23

11 files changed

Lines changed: 335 additions & 23 deletions

File tree

.env.example

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,38 @@ REFERENCE_API_HTTP_PORT=8000
277277
REFERENCE_API_HTTPS_PORT=8443
278278
REFERENCE_API_ENABLE_TLS=true
279279

280+
# ---------------------------------------------------------------------------
281+
# Additional Reference APIs (Go, Node.js, TypeScript, Rust)
282+
# ---------------------------------------------------------------------------
283+
# These reference applications demonstrate the same patterns in different languages.
284+
# Each can be started with: docker compose up -d <service-name>
285+
#
286+
# Services:
287+
# - reference-api-first (FastAPI API-first): Port 8001/8444
288+
# - reference-golang: Port 8002/8445
289+
# - reference-nodejs: Port 8003/8446
290+
# - reference-rust: Port 8004/8447
291+
# - reference-typescript: Port 8005/8448
292+
293+
# FastAPI API-First Implementation
294+
API_FIRST_HTTP_PORT=8001
295+
API_FIRST_HTTPS_PORT=8444
296+
API_FIRST_ENABLE_TLS=false
297+
298+
# Go Implementation
299+
GOLANG_HTTP_PORT=8002
300+
GOLANG_HTTPS_PORT=8445
301+
GOLANG_API_ENABLE_TLS=false
302+
303+
# Node.js Implementation
304+
NODEJS_HTTP_PORT=8003
305+
NODEJS_HTTPS_PORT=8446
306+
NODEJS_API_ENABLE_TLS=false
307+
308+
# Rust Implementation
309+
RUST_HTTP_PORT=8004
310+
RUST_HTTPS_PORT=8447
311+
280312
# ===========================================================================
281313
# Observability Stack (Prometheus, Grafana, Loki)
282314
# ===========================================================================

reference-apps/fastapi-api-first/app/main.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,16 @@
8484
)
8585

8686
# Configure CORS
87+
# In production, replace "*" with specific allowed origins
8788
CORS_ORIGINS = [
88-
"http://localhost:3000",
89-
"http://localhost:8000",
90-
"http://localhost:8001",
89+
"http://localhost:3000", # React/Next.js dev server
90+
"http://localhost:8000", # FastAPI code-first
91+
"http://localhost:8001", # FastAPI API-first
92+
"http://localhost:8080", # Common dev port
9193
"http://127.0.0.1:3000",
9294
"http://127.0.0.1:8000",
9395
"http://127.0.0.1:8001",
96+
"http://127.0.0.1:8080",
9497
]
9598

9699
if settings.DEBUG:

reference-apps/fastapi/app/main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,13 @@
9494
# CORS Configuration
9595
# In production, replace "*" with specific allowed origins
9696
CORS_ORIGINS = [
97-
"http://localhost:3000", # React/Next.js dev server
98-
"http://localhost:8000", # FastAPI (same origin)
99-
"http://localhost:8080", # Common dev port
97+
"http://localhost:3000", # React/Next.js dev server
98+
"http://localhost:8000", # FastAPI code-first
99+
"http://localhost:8001", # FastAPI API-first
100+
"http://localhost:8080", # Common dev port
100101
"http://127.0.0.1:3000",
101102
"http://127.0.0.1:8000",
103+
"http://127.0.0.1:8001",
102104
"http://127.0.0.1:8080",
103105
]
104106

reference-apps/golang/.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
# Build artifacts
2-
api
1+
# Build artifacts (binary only, not source directories)
2+
/api
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
// Package main provides the entry point for the DevStack Core Go reference API.
2+
//
3+
// This application demonstrates integration patterns with the DevStack Core
4+
// infrastructure stack including Vault secrets management, database connections,
5+
// Redis clustering, and RabbitMQ messaging.
6+
//
7+
// Architecture:
8+
// - Gin web framework for HTTP routing
9+
// - HashiCorp Vault for secrets management
10+
// - PostgreSQL, MySQL, MongoDB for database operations
11+
// - Redis cluster for distributed caching
12+
// - RabbitMQ for message queuing
13+
// - Prometheus for metrics collection
14+
// - Structured logging with Logrus
15+
//
16+
// This is a reference implementation for learning purposes only.
17+
// Not intended for production use.
18+
package main
19+
20+
import (
21+
"context"
22+
"fmt"
23+
"net/http"
24+
"os"
25+
"os/signal"
26+
"syscall"
27+
"time"
28+
29+
"github.com/gin-gonic/gin"
30+
"github.com/prometheus/client_golang/prometheus"
31+
"github.com/prometheus/client_golang/prometheus/promhttp"
32+
"github.com/sirupsen/logrus"
33+
34+
"github.com/normbrandinger/devstack-core/reference-apps/golang/internal/config"
35+
"github.com/normbrandinger/devstack-core/reference-apps/golang/internal/handlers"
36+
"github.com/normbrandinger/devstack-core/reference-apps/golang/internal/middleware"
37+
"github.com/normbrandinger/devstack-core/reference-apps/golang/internal/services"
38+
)
39+
40+
var (
41+
httpRequestsTotal = prometheus.NewCounterVec(
42+
prometheus.CounterOpts{
43+
Name: "http_requests_total",
44+
Help: "Total number of HTTP requests",
45+
},
46+
[]string{"method", "endpoint", "status"},
47+
)
48+
49+
httpRequestDuration = prometheus.NewHistogramVec(
50+
prometheus.HistogramOpts{
51+
Name: "http_request_duration_seconds",
52+
Help: "HTTP request latency",
53+
Buckets: prometheus.DefBuckets,
54+
},
55+
[]string{"method", "endpoint"},
56+
)
57+
)
58+
59+
// init registers Prometheus metrics collectors at package initialization time.
60+
// These metrics are used to track HTTP request counts and latency.
61+
func init() {
62+
prometheus.MustRegister(httpRequestsTotal)
63+
prometheus.MustRegister(httpRequestDuration)
64+
}
65+
66+
// main is the application entry point.
67+
//
68+
// It performs the following initialization steps:
69+
// 1. Loads configuration from environment variables
70+
// 2. Configures structured logging with JSON format
71+
// 3. Initializes Vault client and verifies connectivity
72+
// 4. Sets up Gin router with middleware (logging, CORS, recovery)
73+
// 5. Registers all API route handlers
74+
// 6. Starts HTTP server in a goroutine
75+
// 7. Waits for interrupt signal (SIGINT/SIGTERM)
76+
// 8. Performs graceful shutdown with 5-second timeout
77+
//
78+
// The application exits cleanly on receiving termination signals.
79+
func main() {
80+
// Load configuration
81+
cfg := config.Load()
82+
83+
// Setup logger
84+
logger := logrus.New()
85+
logger.SetFormatter(&logrus.JSONFormatter{})
86+
if cfg.Debug {
87+
logger.SetLevel(logrus.DebugLevel)
88+
} else {
89+
logger.SetLevel(logrus.InfoLevel)
90+
}
91+
92+
logger.Info("Starting Golang reference API")
93+
logger.Infof("Environment: %s", cfg.Environment)
94+
logger.Infof("Vault address: %s", cfg.VaultAddr)
95+
96+
// Initialize Vault client
97+
vaultClient, err := services.NewVaultClient(cfg.VaultAddr, cfg.VaultToken, cfg.VaultAppRoleDir)
98+
if err != nil {
99+
logger.Fatalf("Failed to initialize Vault client: %v", err)
100+
}
101+
102+
// Test Vault connection
103+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
104+
_, err = vaultClient.HealthCheck(ctx)
105+
cancel()
106+
if err != nil {
107+
logger.Warnf("Vault health check failed: %v", err)
108+
} else {
109+
logger.Info("Vault connection successful")
110+
}
111+
112+
// Setup Gin
113+
if !cfg.Debug {
114+
gin.SetMode(gin.ReleaseMode)
115+
}
116+
117+
router := gin.New()
118+
router.Use(gin.Recovery())
119+
router.Use(middleware.LoggingMiddleware(logger))
120+
router.Use(middleware.CORSMiddleware())
121+
122+
// Initialize handlers
123+
healthHandler := handlers.NewHealthHandler(cfg, vaultClient)
124+
vaultHandler := handlers.NewVaultHandler(vaultClient)
125+
databaseHandler := handlers.NewDatabaseHandler(cfg, vaultClient)
126+
cacheHandler := handlers.NewCacheHandler(cfg, vaultClient)
127+
redisClusterHandler := handlers.NewRedisClusterHandler(cfg, vaultClient)
128+
messagingHandler := handlers.NewMessagingHandler(cfg, vaultClient)
129+
130+
// Root endpoint
131+
router.GET("/", func(c *gin.Context) {
132+
c.JSON(http.StatusOK, gin.H{
133+
"name": "DevStack Core Reference API",
134+
"version": "1.1.0",
135+
"language": "Go",
136+
"description": "Reference implementation for infrastructure integration",
137+
"docs": "/docs",
138+
"health": "/health/all",
139+
"metrics": "/metrics",
140+
"security": gin.H{
141+
"cors": gin.H{
142+
"enabled": true,
143+
"allowed_origins": "localhost:3000, localhost:8000, localhost:8002",
144+
"allowed_methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
145+
"credentials": true,
146+
"max_age": "600s",
147+
},
148+
"request_validation": gin.H{
149+
"max_request_size": "10MB",
150+
"allowed_content_types": []string{
151+
"application/json",
152+
"application/x-www-form-urlencoded",
153+
"multipart/form-data",
154+
"text/plain",
155+
},
156+
},
157+
},
158+
"redis_cluster": gin.H{
159+
"nodes": "/redis/cluster/nodes",
160+
"slots": "/redis/cluster/slots",
161+
"info": "/redis/cluster/info",
162+
"node_info": "/redis/nodes/{node_name}/info",
163+
},
164+
"examples": gin.H{
165+
"vault": "/examples/vault",
166+
"databases": "/examples/database",
167+
"cache": "/examples/cache",
168+
"messaging": "/examples/messaging",
169+
},
170+
"note": "This is a reference implementation, not production code",
171+
})
172+
})
173+
174+
// Health check routes
175+
health := router.Group("/health")
176+
{
177+
health.GET("/", healthHandler.SimpleHealth)
178+
health.GET("/vault", healthHandler.VaultHealth)
179+
health.GET("/postgres", healthHandler.PostgresHealth)
180+
health.GET("/mysql", healthHandler.MySQLHealth)
181+
health.GET("/mongodb", healthHandler.MongoDBHealth)
182+
health.GET("/redis", healthHandler.RedisHealth)
183+
health.GET("/rabbitmq", healthHandler.RabbitMQHealth)
184+
health.GET("/all", healthHandler.AllHealth)
185+
}
186+
187+
// Vault example routes
188+
vault := router.Group("/examples/vault")
189+
{
190+
vault.GET("/secret/:service_name", vaultHandler.GetSecret)
191+
vault.GET("/secret/:service_name/:key", vaultHandler.GetSecretKey)
192+
}
193+
194+
// Database example routes
195+
database := router.Group("/examples/database")
196+
{
197+
database.GET("/postgres/query", databaseHandler.PostgresQuery)
198+
database.GET("/mysql/query", databaseHandler.MySQLQuery)
199+
database.GET("/mongodb/query", databaseHandler.MongoDBQuery)
200+
}
201+
202+
// Cache example routes
203+
cache := router.Group("/examples/cache")
204+
{
205+
cache.GET("/:key", cacheHandler.GetCache)
206+
cache.POST("/:key", cacheHandler.SetCache)
207+
cache.DELETE("/:key", cacheHandler.DeleteCache)
208+
}
209+
210+
// Redis cluster routes
211+
redisGroup := router.Group("/redis")
212+
{
213+
redisGroup.GET("/cluster/nodes", redisClusterHandler.ClusterNodes)
214+
redisGroup.GET("/cluster/slots", redisClusterHandler.ClusterSlots)
215+
redisGroup.GET("/cluster/info", redisClusterHandler.ClusterInfo)
216+
redisGroup.GET("/nodes/:node_name/info", redisClusterHandler.NodeInfo)
217+
}
218+
219+
// Messaging example routes
220+
messaging := router.Group("/examples/messaging")
221+
{
222+
messaging.POST("/publish/:queue", messagingHandler.PublishMessage)
223+
messaging.GET("/queue/:queue_name/info", messagingHandler.QueueInfo)
224+
}
225+
226+
// Metrics endpoint
227+
router.GET("/metrics", gin.WrapH(promhttp.Handler()))
228+
229+
// Create HTTP server
230+
addr := fmt.Sprintf(":%s", cfg.HTTPPort)
231+
srv := &http.Server{
232+
Addr: addr,
233+
Handler: router,
234+
}
235+
236+
// Start server in goroutine
237+
go func() {
238+
logger.Infof("Starting HTTP server on port %s", cfg.HTTPPort)
239+
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
240+
logger.Fatalf("Failed to start server: %v", err)
241+
}
242+
}()
243+
244+
// Wait for interrupt signal to gracefully shutdown the server
245+
quit := make(chan os.Signal, 1)
246+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
247+
<-quit
248+
249+
logger.Info("Shutting down server...")
250+
251+
// Graceful shutdown with 5 second timeout
252+
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
253+
defer cancel()
254+
255+
if err := srv.Shutdown(ctx); err != nil {
256+
logger.Fatalf("Server forced to shutdown: %v", err)
257+
}
258+
259+
logger.Info("Server exited")
260+
}

reference-apps/golang/internal/config/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ func Load() *Config {
9393
RedisPort: getEnv("REDIS_PORT", "6379"),
9494
RedisPassword: getEnv("REDIS_PASSWORD", ""),
9595

96-
// RabbitMQ
96+
// RabbitMQ - defaults match Vault bootstrap credentials
9797
RabbitMQHost: getEnv("RABBITMQ_HOST", "rabbitmq"),
9898
RabbitMQPort: getEnv("RABBITMQ_PORT", "5672"),
99-
RabbitMQUser: getEnv("RABBITMQ_USER", "guest"),
99+
RabbitMQUser: getEnv("RABBITMQ_USER", "dev_admin"),
100100
RabbitMQPassword: getEnv("RABBITMQ_PASSWORD", ""),
101101
}
102102
}

reference-apps/nodejs/src/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const config = {
6464
// Application Metadata
6565
app: {
6666
name: 'DevStack Core Node.js Reference API',
67-
version: '1.0.0',
67+
version: '1.1.0',
6868
language: 'Node.js',
6969
framework: 'Express'
7070
}

reference-apps/rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "devstack-core-rust-api"
3-
version = "1.0.0"
3+
version = "1.1.0"
44
edition = "2021"
55

66
[dependencies]

0 commit comments

Comments
 (0)