mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
feat: Replace GORM AutoMigrate with Goose CLI migrations in api-server database initialization
- Add os/exec import for running external commands - Replace AutoMigrate function with runMigrations function using goose postgres command - Call runMigrations in NewDatabase before initializing IMSI allocation - Remove PoolStats function and GORM-based schema migration code - Reorganize database methods with section comments (Subscriber CRUD, Payment methods, Transactions, Additional CRUD, IMSI allocation) -
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
@@ -31,36 +32,28 @@ func NewDatabase(cfg *config.DatabaseConfig) (*Database, error) {
|
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s",
|
||||
cfg.Host, cfg.Username, cfg.Password, cfg.Database, cfg.Port, cfg.SSLMode)
|
||||
|
||||
// Configure GORM logger
|
||||
gormLogger := logger.Default.LogMode(logger.Info)
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||
Logger: gormLogger,
|
||||
})
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: gormLogger})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Get underlying SQL DB to configure connection pool
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get underlying sql.DB: %w", err)
|
||||
}
|
||||
|
||||
// Configure connection pool for optimal performance
|
||||
sqlDB.SetMaxIdleConns(25) // Minimum idle connections
|
||||
sqlDB.SetMaxOpenConns(100) // Maximum open connections
|
||||
sqlDB.SetConnMaxLifetime(time.Hour) // Max connection lifetime
|
||||
sqlDB.SetConnMaxIdleTime(30 * time.Minute) // Max idle time before closing
|
||||
sqlDB.SetMaxIdleConns(25)
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||
sqlDB.SetConnMaxIdleTime(30 * time.Minute)
|
||||
|
||||
database := &Database{DB: db}
|
||||
|
||||
// Auto-migrate schemas
|
||||
if err := database.AutoMigrate(); err != nil {
|
||||
return nil, fmt.Errorf("failed to auto-migrate: %w", err)
|
||||
if err := runMigrations(dsn); err != nil {
|
||||
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
||||
}
|
||||
|
||||
// Initialize IMSI allocation
|
||||
if err := database.InitializeIMSIAllocation(); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize IMSI allocation: %w", err)
|
||||
}
|
||||
@@ -68,20 +61,14 @@ func NewDatabase(cfg *config.DatabaseConfig) (*Database, error) {
|
||||
return database, nil
|
||||
}
|
||||
|
||||
func (d *Database) AutoMigrate() error {
|
||||
return d.DB.AutoMigrate(
|
||||
&models.Subscriber{},
|
||||
&models.ServicePlan{},
|
||||
&models.Session{},
|
||||
&models.UsageRecord{},
|
||||
&IMSIAllocation{},
|
||||
&models.Plugin{},
|
||||
&models.Automation{},
|
||||
&models.AutomationRun{},
|
||||
&models.ConfigEntry{},
|
||||
&models.DeploymentRecord{},
|
||||
&models.ChaosExperimentRecord{},
|
||||
)
|
||||
func runMigrations(dsn string) error {
|
||||
cmd := exec.Command("goose", "postgres", dsn, "up", "migrations")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("goose migration failed: %w, output: %s", err, string(output))
|
||||
}
|
||||
log.Printf("Database migrations completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) InitializeIMSIAllocation() error {
|
||||
@@ -89,17 +76,10 @@ func (d *Database) InitializeIMSIAllocation() error {
|
||||
result := d.DB.First(&allocation)
|
||||
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
// Create initial allocation record
|
||||
allocation = IMSIAllocation{
|
||||
LastIMSI: 0,
|
||||
MinIMSI: 1,
|
||||
MaxIMSI: 999999999, // 9-digit subscriber numbers
|
||||
}
|
||||
|
||||
allocation = IMSIAllocation{LastIMSI: 0, MinIMSI: 1, MaxIMSI: 999999999}
|
||||
if err := d.DB.Create(&allocation).Error; err != nil {
|
||||
return fmt.Errorf("failed to create IMSI allocation: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Created IMSI allocation record")
|
||||
} else if result.Error != nil {
|
||||
return fmt.Errorf("failed to query IMSI allocation: %w", result.Error)
|
||||
@@ -116,27 +96,6 @@ func (d *Database) Close() error {
|
||||
return sqlDB.Close()
|
||||
}
|
||||
|
||||
// PoolStats returns current connection pool statistics
|
||||
func (d *Database) PoolStats() (map[string]any, error) {
|
||||
sqlDB, err := d.DB.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats := sqlDB.Stats()
|
||||
return map[string]any{
|
||||
"max_open_connections": stats.MaxOpenConnections,
|
||||
"open_connections": stats.OpenConnections,
|
||||
"in_use": stats.InUse,
|
||||
"idle": stats.Idle,
|
||||
"wait_count": stats.WaitCount,
|
||||
"wait_duration": stats.WaitDuration.String(),
|
||||
"max_idle_closed": stats.MaxIdleClosed,
|
||||
"max_idle_time_closed": stats.MaxIdleTimeClosed,
|
||||
"max_lifetime_closed": stats.MaxLifetimeClosed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Ping verifies database connectivity
|
||||
func (d *Database) Ping(ctx context.Context) error {
|
||||
sqlDB, err := d.DB.DB()
|
||||
if err != nil {
|
||||
@@ -145,6 +104,7 @@ func (d *Database) Ping(ctx context.Context) error {
|
||||
return sqlDB.PingContext(ctx)
|
||||
}
|
||||
|
||||
// Subscriber CRUD
|
||||
func (d *Database) CreateSubscriber(ctx context.Context, subscriber *models.Subscriber) error {
|
||||
return d.DB.WithContext(ctx).Create(subscriber).Error
|
||||
}
|
||||
@@ -176,8 +136,6 @@ func (d *Database) ListSubscribers(ctx context.Context, req *ListSubscribersRequ
|
||||
var total int64
|
||||
|
||||
query := d.DB.WithContext(ctx).Model(&models.Subscriber{})
|
||||
|
||||
// Apply filters
|
||||
if req.Status != "" {
|
||||
query = query.Where("status = ?", req.Status)
|
||||
}
|
||||
@@ -189,31 +147,15 @@ func (d *Database) ListSubscribers(ctx context.Context, req *ListSubscribersRequ
|
||||
"%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%")
|
||||
}
|
||||
|
||||
// Count total records
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Apply pagination and ordering
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
err := query.Preload("Plan").Offset(offset).Limit(req.PageSize).Order("created_at DESC").Find(&subscribers).Error
|
||||
|
||||
return subscribers, total, err
|
||||
}
|
||||
|
||||
func (d *Database) GetIMSIAllocation(ctx context.Context) (*IMSIAllocation, error) {
|
||||
var allocation IMSIAllocation
|
||||
err := d.DB.WithContext(ctx).First(&allocation).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &allocation, nil
|
||||
}
|
||||
|
||||
func (d *Database) UpdateIMSIAllocation(ctx context.Context, allocation *IMSIAllocation) error {
|
||||
return d.DB.WithContext(ctx).Save(allocation).Error
|
||||
}
|
||||
|
||||
func (d *Database) GetActiveSessionsByIMSI(ctx context.Context, imsi models.IMSI) ([]models.Session, error) {
|
||||
var sessions []models.Session
|
||||
err := d.DB.WithContext(ctx).Where("subscriber_id = ? AND status = ?", imsi, "active").Find(&sessions).Error
|
||||
@@ -224,15 +166,7 @@ func (d *Database) UpdateSession(ctx context.Context, session *models.Session) e
|
||||
return d.DB.WithContext(ctx).Save(session).Error
|
||||
}
|
||||
|
||||
func (d *Database) CreateTransaction(ctx context.Context, transaction *models.Transaction) error {
|
||||
return d.DB.WithContext(ctx).Create(transaction).Error
|
||||
}
|
||||
|
||||
func (d *Database) UpdateSubscriberBalance(ctx context.Context, subscriberID uint, amount float64) error {
|
||||
return d.DB.WithContext(ctx).Model(&models.Subscriber{}).Where("id = ?", subscriberID).
|
||||
UpdateColumn("balance", gorm.Expr("balance + ?", amount)).Error
|
||||
}
|
||||
|
||||
// Payment methods
|
||||
func (d *Database) CreatePaymentMethod(ctx context.Context, pm *models.PaymentMethod) error {
|
||||
return d.DB.WithContext(ctx).Create(pm).Error
|
||||
}
|
||||
@@ -246,16 +180,17 @@ func (d *Database) GetPaymentMethod(ctx context.Context, id string) (*models.Pay
|
||||
return &pm, nil
|
||||
}
|
||||
|
||||
func (d *Database) DeletePaymentMethod(ctx context.Context, id string) error {
|
||||
return d.DB.WithContext(ctx).Where("id = ?", id).Delete(&models.PaymentMethod{}).Error
|
||||
}
|
||||
|
||||
func (d *Database) ListPaymentMethods(ctx context.Context, subscriberID uint) ([]models.PaymentMethod, error) {
|
||||
var methods []models.PaymentMethod
|
||||
err := d.DB.WithContext(ctx).Where("subscriber_id = ?", subscriberID).Find(&methods).Error
|
||||
return methods, err
|
||||
}
|
||||
|
||||
// Transactions
|
||||
func (d *Database) CreateTransaction(ctx context.Context, transaction *models.Transaction) error {
|
||||
return d.DB.WithContext(ctx).Create(transaction).Error
|
||||
}
|
||||
|
||||
func (d *Database) GetTransaction(ctx context.Context, transactionID string) (*models.Transaction, error) {
|
||||
var tx models.Transaction
|
||||
err := d.DB.WithContext(ctx).Where("transaction_id = ?", transactionID).First(&tx).Error
|
||||
@@ -293,6 +228,16 @@ func (d *Database) ListTransactions(ctx context.Context, subscriberID uint) ([]m
|
||||
return transactions, err
|
||||
}
|
||||
|
||||
// Additional CRUD
|
||||
func (d *Database) UpdateSubscriberBalance(ctx context.Context, subscriberID uint, amount float64) error {
|
||||
return d.DB.WithContext(ctx).Model(&models.Subscriber{}).Where("id = ?", subscriberID).
|
||||
UpdateColumn("balance", gorm.Expr("balance + ?", amount)).Error
|
||||
}
|
||||
|
||||
func (d *Database) DeletePaymentMethod(ctx context.Context, id string) error {
|
||||
return d.DB.WithContext(ctx).Where("id = ?", id).Delete(&models.PaymentMethod{}).Error
|
||||
}
|
||||
|
||||
func (d *Database) CreateAlert(ctx context.Context, alert *models.Alert) error {
|
||||
return d.DB.WithContext(ctx).Create(alert).Error
|
||||
}
|
||||
@@ -301,11 +246,24 @@ func (d *Database) CreateNotification(ctx context.Context, notification *models.
|
||||
return d.DB.WithContext(ctx).Create(notification).Error
|
||||
}
|
||||
|
||||
// Request types for database operations
|
||||
// IMSI allocation
|
||||
func (d *Database) GetIMSIAllocation(ctx context.Context) (*IMSIAllocation, error) {
|
||||
var allocation IMSIAllocation
|
||||
err := d.DB.WithContext(ctx).First(&allocation).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &allocation, nil
|
||||
}
|
||||
|
||||
func (d *Database) UpdateIMSIAllocation(ctx context.Context, allocation *IMSIAllocation) error {
|
||||
return d.DB.WithContext(ctx).Save(allocation).Error
|
||||
}
|
||||
|
||||
type ListSubscribersRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Status models.SubscriberStatus `json:"status"`
|
||||
OrganizationID string `json:"organization_id"`
|
||||
Search string `json:"search"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Status string `json:"status"`
|
||||
OrganizationID string `json:"organization_id"`
|
||||
Search string `json:"search"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user