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

- Add MVNORepository implementation with CreateMVNO, GetMVNO, UpdateMVNO, ListMVNOs, DeleteMVNO, and UpdateMVNOStatus methods
- 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 building, filter application, and descending created_at ordering
- Add GetMVNOStats with total count, status distribution, and plan distribution aggregation
This commit is contained in:
nutcas3
2026-05-03 09:50:26 +03:00
parent c7ddd3c5d7
commit 5cb2904c36
@@ -0,0 +1,172 @@
package repository
import (
"context"
"fmt"
"time"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/mvno"
"gorm.io/gorm"
)
// MVNOFilter defines filtering options for MVNO queries
type MVNOFilter struct {
Status mvno.MVNOStatus `json:"status,omitempty"`
Plan mvno.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"`
}
// CreateMVNO creates a new MVNO record
func (r *GormRepository) CreateMVNO(ctx context.Context, mvno *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.MVNO, error) {
var mvno 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.MVNO, error) {
var mvno 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.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 *mvno.MVNOFilter) ([]*mvno.MVNO, error) {
query := r.db.WithContext(ctx).Model(&mvno.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.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.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.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 mvno.MVNOStatus `gorm:"column:status"`
Count int64 `gorm:"column:count"`
}
if err := r.db.WithContext(ctx).Model(&mvno.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 mvno.MVNOPlan `gorm:"column:plan"`
Count int64 `gorm:"column:count"`
}
if err := r.db.WithContext(ctx).Model(&mvno.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 mvno.MVNOStatus) error {
if err := r.db.WithContext(ctx).Model(&mvno.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
}