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
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Exclude node_modules from Docker builds
ffm/node_modules
ffm/dist
ffm/.git
ffm/.DS_Store
ffm/*.log

# Exclude other build artifacts
api/tmp
*.log
.DS_Store
.git
.gitignore
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ standalone-down: ## Stop standalone production container

standalone-build: ## Build standalone production container
@echo "$(GREEN)Building standalone production container...$(NC)"
docker compose -p bfm-standalone -f $(COMPOSE_STANDALONE_FILE) build
docker compose -p bfm-standalone -f $(COMPOSE_STANDALONE_FILE) build --no-cache

standalone-logs: ## Show logs from standalone container
docker compose -p bfm-standalone -f $(COMPOSE_STANDALONE_FILE) logs -f
Expand Down
47 changes: 45 additions & 2 deletions api/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
Expand All @@ -21,6 +22,7 @@ import (
"github.com/toolsascode/bfm/api/internal/logger"
"github.com/toolsascode/bfm/api/internal/queuefactory"
"github.com/toolsascode/bfm/api/internal/registry"
"github.com/toolsascode/bfm/api/internal/state"
statepg "github.com/toolsascode/bfm/api/internal/state/postgresql"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -114,7 +116,7 @@ func main() {
if migrationCount == 0 {
logger.Warnf("No migrations loaded from %s - ensure migration files exist in the expected directory structure", sfmPath)
} else {
logger.Infof("Successfully loaded %d migration(s) from %s", migrationCount, sfmPath)
logger.Infof("Successfully loaded %d migration(s) from %s", migrationCount, sfmPath)

// Log migration breakdown by backend/connection for better visibility
allMigrations := registry.GlobalRegistry.GetAll()
Expand All @@ -137,15 +139,56 @@ func main() {
loader.StartWatching()
defer loader.StopWatching()

// Start background reindexer
reindexInterval := 5 * time.Minute
if intervalStr := os.Getenv("BFM_REINDEX_INTERVAL_MINUTES"); intervalStr != "" {
if intervalMinutes, err := time.ParseDuration(intervalStr + "m"); err == nil {
reindexInterval = intervalMinutes
}
}
reindexer := state.NewReindexer(stateTracker, registry.GlobalRegistry, reindexInterval)
reindexer.Start()
defer reindexer.Stop()
logger.Infof("Background reindexer started with interval: %v", reindexInterval)

// Set Gin mode - use BFM_APP_MODE env var if set, otherwise default to release mode
if ginMode := os.Getenv("BFM_APP_MODE"); ginMode != "" {
gin.SetMode(ginMode)
} else {
gin.SetMode(gin.ReleaseMode)
}

// Initialize HTTP server
router := gin.New()

// Custom logger middleware that skips health check endpoints
// Determine log format from environment variable (default to JSON)
logFormat := strings.ToLower(os.Getenv("BFM_LOG_FORMAT"))
useJSON := logFormat != "plaintext" && logFormat != "plain" && logFormat != "text"

// Custom logger middleware that skips health check endpoints and supports JSON/plaintext
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
// Skip logging for health check endpoints
if param.Path == "/health" || param.Path == "/api/v1/health" {
return ""
}

if useJSON {
// JSON format
logEntry := map[string]interface{}{
"timestamp": param.TimeStamp.Format("2006-01-02T15:04:05.000Z07:00"),
"status": param.StatusCode,
"latency": param.Latency.String(),
"client_ip": param.ClientIP,
"method": param.Method,
"path": param.Path,
"error": param.ErrorMessage,
}
if jsonBytes, err := json.Marshal(logEntry); err == nil {
return string(jsonBytes) + "\n"
}
}

// Plaintext format (fallback or when explicitly set)
return fmt.Sprintf("[GIN] %s | %3d | %13v | %15s | %-7s %s\n",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
param.StatusCode,
Expand Down
5 changes: 5 additions & 0 deletions api/deploy/docker-compose.servers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ services:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
- POSTGRES_DB=migration_state
command: >
postgres
-c max_connections=200
-c shared_buffers=256MB
-c effective_cache_size=1GB
ports:
- "5433:5432"
volumes:
Expand Down
2 changes: 1 addition & 1 deletion api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/gin-gonic/gin v1.10.1
github.com/lib/pq v1.10.9
github.com/segmentio/kafka-go v0.4.49
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.10.2
go.etcd.io/etcd/client/v3 v3.5.10
google.golang.org/grpc v1.77.0
Expand Down Expand Up @@ -65,7 +66,6 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
Expand Down
20 changes: 20 additions & 0 deletions api/internal/api/http/dto/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ type MigrationDetailResponse struct {
StructuredDependencies []DependencyResponse `json:"structured_dependencies,omitempty"` // Structured dependencies with validation requirements
}

// RollbackRequest represents a request to rollback a migration
type RollbackRequest struct {
Schemas []string `json:"schemas,omitempty"` // Array for dynamic schemas
}

// RollbackResponse represents a rollback operation result
type RollbackResponse struct {
Success bool `json:"success"`
Expand All @@ -82,6 +87,21 @@ type MigrateUpRequest struct {
DryRun bool `json:"dry_run"`
}

// MigrationExecutionResponse represents an execution record from migrations_executions
type MigrationExecutionResponse struct {
ID int `json:"id"`
MigrationID string `json:"migration_id"`
Schema string `json:"schema"`
Version string `json:"version"`
Connection string `json:"connection"`
Backend string `json:"backend"`
Status string `json:"status"`
Applied bool `json:"applied"`
AppliedAt string `json:"applied_at,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

// MigrateDownRequest represents a request to execute down migrations
type MigrateDownRequest struct {
MigrationID string `json:"migration_id" binding:"required"`
Expand Down
Loading