feat: Add MVNO repository with CRUD operations, filtering, and statistics aggregation

- Add Repository interface with CreateMVNO, GetMVNO, UpdateMVNO, ListMVNOs, DeleteMVNO, and UpdateMVNOStatus methods
- Add GormRepository struct with db and logger dependencies for GORM-based persistence
- Add MVNOFilter struct with status, plan, business_id, limit, offset, and date range filtering
- Add GetMVNO and GetMVNOByBusinessID methods with record not found error handling
- Add ListMVNOs with dynamic query
This commit is contained in:
nutcas3
2026-05-02 19:04:14 +03:00
parent fd827d4ad0
commit 3b10a90851
@@ -0,0 +1,198 @@
package mvno
import (
"context"
"fmt"
"time"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
// Repository defines the interface for MVNO data operations
type Repository interface {
CreateMVNO(ctx context.Context, mvno *MVNO) error
GetMVNO(ctx context.Context, id string) (*MVNO, error)
GetMVNOByBusinessID(ctx context.Context, businessID string) (*MVNO, error)
UpdateMVNO(ctx context.Context, mvno *MVNO) error
ListMVNOs(ctx context.Context, filter *MVNOFilter) ([]*MVNO, error)
DeleteMVNO(ctx context.Context, id string) error
UpdateMVNOStatus(ctx context.Context, id string, status MVNOStatus) error
GetMVNOStats(ctx context.Context) (map[string]interface{}, error)
}
// MVNOFilter defines filtering options for MVNO queries
type MVNOFilter struct {
Status MVNOStatus `json:"status,omitempty"`
Plan MVNOPlan `json:"plan,omitempty"`
BusinessID string `json:"business_id,omitempty"`
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
CreatedAfter *time.Time `json:"created_after,omitempty"`
CreatedBefore *time.Time `json:"created_before,omitempty"`
}
// GormRepository implements Repository using GORM
type GormRepository struct {
db *gorm.DB
logger *logrus.Logger
}
// NewGormRepository creates a new GORM repository
func NewGormRepository(db *gorm.DB, logger *logrus.Logger) *GormRepository {
return &GormRepository{
db: db,
logger: logger,
}
}
// CreateMVNO creates a new MVNO record
func (r *GormRepository) CreateMVNO(ctx context.Context, mvno *MVNO) error {
if err := r.db.WithContext(ctx).Create(mvno).Error; err != nil {
return fmt.Errorf("failed to create MVNO: %w", err)
}
r.logger.Info("MVNO created", "id", mvno.ID, "business_id", mvno.BusinessID)
return nil
}
// GetMVNO retrieves an MVNO by ID
func (r *GormRepository) GetMVNO(ctx context.Context, id string) (*MVNO, error) {
var mvno MVNO
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&mvno).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("MVNO not found: %s", id)
}
return nil, fmt.Errorf("failed to get MVNO: %w", err)
}
return &mvno, nil
}
// GetMVNOByBusinessID retrieves an MVNO by business ID
func (r *GormRepository) GetMVNOByBusinessID(ctx context.Context, businessID string) (*MVNO, error) {
var mvno MVNO
if err := r.db.WithContext(ctx).Where("business_id = ?", businessID).First(&mvno).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("MVNO not found for business ID: %s", businessID)
}
return nil, fmt.Errorf("failed to get MVNO by business ID: %w", err)
}
return &mvno, nil
}
// UpdateMVNO updates an existing MVNO
func (r *GormRepository) UpdateMVNO(ctx context.Context, mvno *MVNO) error {
if err := r.db.WithContext(ctx).Save(mvno).Error; err != nil {
return fmt.Errorf("failed to update MVNO: %w", err)
}
r.logger.Info("MVNO updated", "id", mvno.ID, "status", mvno.Status)
return nil
}
// ListMVNOs lists MVNOs with optional filtering
func (r *GormRepository) ListMVNOs(ctx context.Context, filter *MVNOFilter) ([]*MVNO, error) {
query := r.db.WithContext(ctx).Model(&MVNO{})
// Apply filters
if filter != nil {
if filter.Status != "" {
query = query.Where("status = ?", filter.Status)
}
if filter.Plan != "" {
query = query.Where("plan = ?", filter.Plan)
}
if filter.BusinessID != "" {
query = query.Where("business_id = ?", filter.BusinessID)
}
if filter.CreatedAfter != nil {
query = query.Where("created_at >= ?", *filter.CreatedAfter)
}
if filter.CreatedBefore != nil {
query = query.Where("created_at <= ?", *filter.CreatedBefore)
}
if filter.Limit > 0 {
query = query.Limit(filter.Limit)
}
if filter.Offset > 0 {
query = query.Offset(filter.Offset)
}
}
var mvnos []*MVNO
if err := query.Order("created_at DESC").Find(&mvnos).Error; err != nil {
return nil, fmt.Errorf("failed to list MVNOs: %w", err)
}
return mvnos, nil
}
// DeleteMVNO soft deletes an MVNO
func (r *GormRepository) DeleteMVNO(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&MVNO{}).Error; err != nil {
return fmt.Errorf("failed to delete MVNO: %w", err)
}
r.logger.Info("MVNO deleted", "id", id)
return nil
}
// GetMVNOStats returns statistics about MVNOs
func (r *GormRepository) GetMVNOStats(ctx context.Context) (map[string]any, error) {
stats := make(map[string]any)
// Total count
var totalCount int64
if err := r.db.WithContext(ctx).Model(&MVNO{}).Count(&totalCount).Error; err != nil {
return nil, fmt.Errorf("failed to count total MVNOs: %w", err)
}
stats["total"] = totalCount
// Count by status
var statusCounts []struct {
Status MVNOStatus `gorm:"column:status"`
Count int64 `gorm:"column:count"`
}
if err := r.db.WithContext(ctx).Model(&MVNO{}).
Select("status, COUNT(*) as count").
Group("status").
Scan(&statusCounts).Error; err != nil {
return nil, fmt.Errorf("failed to count by status: %w", err)
}
statusMap := make(map[string]int64)
for _, sc := range statusCounts {
statusMap[string(sc.Status)] = sc.Count
}
stats["by_status"] = statusMap
// Count by plan
var planCounts []struct {
Plan MVNOPlan `gorm:"column:plan"`
Count int64 `gorm:"column:count"`
}
if err := r.db.WithContext(ctx).Model(&MVNO{}).
Select("plan, COUNT(*) as count").
Group("plan").
Scan(&planCounts).Error; err != nil {
return nil, fmt.Errorf("failed to count by plan: %w", err)
}
planMap := make(map[string]int64)
for _, pc := range planCounts {
planMap[string(pc.Plan)] = pc.Count
}
stats["by_plan"] = planMap
return stats, nil
}
// UpdateMVNOStatus updates only the status of an MVNO
func (r *GormRepository) UpdateMVNOStatus(ctx context.Context, id string, status MVNOStatus) error {
if err := r.db.WithContext(ctx).Model(&MVNO{}).Where("id = ?", id).Update("status", status).Error; err != nil {
return fmt.Errorf("failed to update MVNO status: %w", err)
}
r.logger.Info("MVNO status updated", "id", id, "new_status", status)
return nil
}