feat: Add carrier configuration layer with GORM repository, file loading, and model conversion support

- Add GormCarrierRepository struct with database and logger fields
- Add CarrierModel with ID, name, MCC, MNC, country code, active status, priority, ES2 config, capabilities, and timestamp fields
- Add GetCarriers with active carrier filtering and model-to-carrier conversion
- Add GetCarrier with ID lookup and not found handling
- Add SaveCarrier with upsert logic for create or update operations
This commit is contained in:
nutcas3
2026-05-02 00:19:30 +03:00
parent 3d23df17ea
commit 01395fcd47
2 changed files with 278 additions and 0 deletions
@@ -0,0 +1,236 @@
package integration
import (
"context"
"encoding/json"
"fmt"
"os"
"time"
"gorm.io/gorm"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/config"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp"
"github.com/sirupsen/logrus"
)
// NewGormCarrierRepository creates a new GORM carrier repository
func NewGormCarrierRepository(db *gorm.DB, logger *logrus.Logger) *GormCarrierRepository {
return &GormCarrierRepository{
db: db,
logger: logger,
}
}
// CarrierModel represents the database model for carriers
type CarrierModel struct {
ID string `gorm:"primaryKey;column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
MCC string `gorm:"column:mcc" json:"mcc"`
MNC string `gorm:"column:mnc" json:"mnc"`
CountryCode string `gorm:"column:country_code" json:"country_code"`
IsActive bool `gorm:"column:is_active" json:"is_active"`
Priority int `gorm:"column:priority" json:"priority"`
ES2Config string `gorm:"column:es2_config;type:text" json:"es2_config"`
Capabilities string `gorm:"column:capabilities;type:text" json:"capabilities"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
}
// TableName returns the table name for the carrier model
func (CarrierModel) TableName() string {
return "carriers"
}
// GetCarriers retrieves all carriers from the database
func (r *GormCarrierRepository) GetCarriers(ctx context.Context) ([]*smdp.Carrier, error) {
var models []CarrierModel
if err := r.db.WithContext(ctx).Where("is_active = ?", true).Find(&models).Error; err != nil {
r.logger.Error("Failed to get carriers from database", "error", err)
return nil, fmt.Errorf("failed to get carriers: %w", err)
}
carriers := make([]*smdp.Carrier, 0, len(models))
for _, model := range models {
carrier, err := r.modelToCarrier(&model)
if err != nil {
r.logger.Error("Failed to convert carrier model", "error", err, "carrier_id", model.ID)
continue
}
carriers = append(carriers, carrier)
}
return carriers, nil
}
// GetCarrier retrieves a specific carrier by ID
func (r *GormCarrierRepository) GetCarrier(ctx context.Context, id string) (*smdp.Carrier, error) {
var model CarrierModel
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("carrier not found: %s", id)
}
r.logger.Error("Failed to get carrier from database", "error", err, "carrier_id", id)
return nil, fmt.Errorf("failed to get carrier: %w", err)
}
return r.modelToCarrier(&model)
}
// SaveCarrier saves a carrier to the database
func (r *GormCarrierRepository) SaveCarrier(ctx context.Context, carrier *smdp.Carrier) error {
model := r.carrierToModel(carrier)
// Check if carrier exists and update or create accordingly
var existing CarrierModel
err := r.db.WithContext(ctx).Where("id = ?", model.ID).First(&existing).Error
if err == gorm.ErrRecordNotFound {
// Create new carrier
if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
r.logger.WithError(err).Error("Failed to create carrier")
return fmt.Errorf("failed to create carrier: %w", err)
}
} else if err != nil {
r.logger.WithError(err).Error("Failed to check carrier existence")
return fmt.Errorf("failed to check carrier: %w", err)
} else {
// Update existing carrier
model.UpdatedAt = time.Now()
if err := r.db.WithContext(ctx).Model(&existing).Updates(model).Error; err != nil {
r.logger.WithError(err).Error("Failed to update carrier")
return fmt.Errorf("failed to update carrier: %w", err)
}
}
return nil
}
// UpdateCarrierMetrics updates carrier metrics
func (r *GormCarrierRepository) UpdateCarrierMetrics(ctx context.Context, id string, metrics *smdp.CarrierMetrics) error {
// In a real implementation, you might have a separate metrics table
// For now, we'll log the metrics update
r.logger.Info("Carrier metrics updated", "carrier_id", id,
"total_requests", metrics.TotalRequests,
"success_rate", float64(metrics.SuccessfulRequests)/float64(metrics.TotalRequests)*100)
return nil
}
// Helper methods for model conversion
func (r *GormCarrierRepository) modelToCarrier(model *CarrierModel) (*smdp.Carrier, error) {
carrier := &smdp.Carrier{
ID: model.ID,
Name: model.Name,
MCC: model.MCC,
MNC: model.MNC,
CountryCode: model.CountryCode,
IsActive: model.IsActive,
Priority: model.Priority,
}
// Parse ES2Config
if model.ES2Config != "" {
var es2Config config.ES2Config
if err := json.Unmarshal([]byte(model.ES2Config), &es2Config); err != nil {
return nil, fmt.Errorf("failed to parse ES2 config: %w", err)
}
carrier.ES2Config = &es2Config
}
// Parse Capabilities
if model.Capabilities != "" {
var capabilities smdp.CarrierCapabilities
if err := json.Unmarshal([]byte(model.Capabilities), &capabilities); err != nil {
return nil, fmt.Errorf("failed to parse carrier capabilities: %w", err)
}
carrier.Capabilities = &capabilities
}
// Initialize metrics (in production, this would come from a metrics table)
carrier.Metrics = &smdp.CarrierMetrics{
TotalRequests: 0,
SuccessfulRequests: 0,
FailedRequests: 0,
AverageResponseTime: 0,
RequestRate: 0,
}
return carrier, nil
}
func (r *GormCarrierRepository) carrierToModel(carrier *smdp.Carrier) *CarrierModel {
model := &CarrierModel{
ID: carrier.ID,
Name: carrier.Name,
MCC: carrier.MCC,
MNC: carrier.MNC,
CountryCode: carrier.CountryCode,
IsActive: carrier.IsActive,
Priority: carrier.Priority,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Serialize ES2Config
if carrier.ES2Config != nil {
if data, err := json.Marshal(carrier.ES2Config); err == nil {
model.ES2Config = string(data)
}
}
// Serialize Capabilities
if carrier.Capabilities != nil {
if data, err := json.Marshal(carrier.Capabilities); err == nil {
model.Capabilities = string(data)
}
}
return model
}
// LoadCarriersFromFile loads carrier configurations from a JSON/YAML file
func LoadCarriersFromFile(configPath string) (*CarrierConfig, error) {
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read carrier config file: %w", err)
}
var config CarrierConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to parse carrier config file: %w", err)
}
return &config, nil
}
// ConvertConfigToCarriers converts carrier definitions to smdp.Carrier objects
func ConvertConfigToCarriers(config *CarrierConfig) ([]*smdp.Carrier, error) {
carriers := make([]*smdp.Carrier, 0, len(config.Carriers))
for _, def := range config.Carriers {
carrier := &smdp.Carrier{
ID: def.ID,
Name: def.Name,
MCC: def.MCC,
MNC: def.MNC,
CountryCode: def.CountryCode,
IsActive: def.IsActive,
Priority: def.Priority,
ES2Config: def.ES2Config,
Capabilities: def.Capabilities,
Metrics: &smdp.CarrierMetrics{
TotalRequests: 0,
SuccessfulRequests: 0,
FailedRequests: 0,
AverageResponseTime: 0,
RequestRate: 0,
},
}
carriers = append(carriers, carrier)
}
return carriers, nil
}
@@ -0,0 +1,42 @@
package integration
import (
"context"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/config"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
// CarrierConfig represents the configuration structure for carriers
type CarrierConfig struct {
Carriers []CarrierDefinition `json:"carriers" yaml:"carriers"`
}
// CarrierDefinition defines a carrier configuration
type CarrierDefinition struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name" yaml:"name"`
MCC string `json:"mcc" yaml:"mcc"`
MNC string `json:"mnc" yaml:"mnc"`
CountryCode string `json:"country_code" yaml:"country_code"`
IsActive bool `json:"is_active" yaml:"is_active"`
Priority int `json:"priority" yaml:"priority"`
ES2Config *config.ES2Config `json:"es2_config" yaml:"es2_config"`
Capabilities *smdp.CarrierCapabilities `json:"capabilities" yaml:"capabilities"`
}
// CarrierRepository defines the interface for carrier data operations
type CarrierRepository interface {
GetCarriers(ctx context.Context) ([]*smdp.Carrier, error)
GetCarrier(ctx context.Context, id string) (*smdp.Carrier, error)
SaveCarrier(ctx context.Context, carrier *smdp.Carrier) error
UpdateCarrierMetrics(ctx context.Context, id string, metrics *smdp.CarrierMetrics) error
}
// GormCarrierRepository implements CarrierRepository using GORM
type GormCarrierRepository struct {
db *gorm.DB
logger *logrus.Logger
}