package repository import ( "fmt" "netidhub-saas-be/app/database" "netidhub-saas-be/app/database/entity" "netidhub-saas-be/app/module/clients/request" "netidhub-saas-be/utils/client" "netidhub-saas-be/utils/paginator" "strings" "github.com/google/uuid" "github.com/rs/zerolog" "gorm.io/gorm" ) type clientsRepository struct { DB *database.Database Log zerolog.Logger } // ClientsRepository define interface of IClientsRepository type ClientsRepository interface { GetAll(req request.ClientsQueryRequest) (clientss []*entity.Clients, paging paginator.Pagination, err error) FindOne(id uuid.UUID) (clients *entity.Clients, err error) Create(clients *entity.Clients) (clientsReturn *entity.Clients, err error) Update(id uuid.UUID, clients *entity.Clients) (err error) Delete(id uuid.UUID) (err error) // New hierarchy methods GetWithHierarchy(clientId uuid.UUID) (*entity.Clients, error) GetSubClients(parentId uuid.UUID, includeInactive bool) ([]entity.Clients, error) GetAllSubClients(parentId uuid.UUID) ([]entity.Clients, error) GetRootClients() ([]entity.Clients, error) GetClientStats(clientId uuid.UUID) (map[string]interface{}, error) MoveClient(clientId, newParentId uuid.UUID) error IsParentClient(clientId uuid.UUID) (bool, error) } func NewClientsRepository(db *database.Database, logger zerolog.Logger) ClientsRepository { return &clientsRepository{ DB: db, Log: logger, } } // implement interface of IClientsRepository func (_i *clientsRepository) GetAll(req request.ClientsQueryRequest) (clientss []*entity.Clients, paging paginator.Pagination, err error) { var count int64 query := _i.DB.DB.Model(&entity.Clients{}) // Name filter if req.Name != nil && *req.Name != "" { name := strings.ToLower(*req.Name) query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") } // Type filter if req.ClientType != nil && *req.ClientType != "" && *req.ClientType != "all" { query = query.Where("client_type = ?", *req.ClientType) } // Parent filter if req.ParentClientId != nil { query = query.Where("parent_client_id = ?", *req.ParentClientId) } // Only parent clients if req.OnlyParentClients != nil && *req.OnlyParentClients { query = query.Where("id IN (SELECT DISTINCT parent_client_id FROM clients WHERE parent_client_id IS NOT NULL)") } // Only standalone if req.OnlyStandalone != nil && *req.OnlyStandalone { query = query.Where("client_type = ?", "standalone") } // Active filter if req.IsActive != nil { query = query.Where("is_active = ?", *req.IsActive) } else { query = query.Where("is_active = ?", true) // Default: only active } // Preload relationships query = query.Preload("ParentClient").Preload("SubClients", "is_active = ?", true) // Count total records query.Count(&count) // Apply sorting if req.Pagination.SortBy != "" { direction := "ASC" if req.Pagination.Sort == "desc" { direction = "DESC" } query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) } else { direction := "DESC" sortBy := "created_at" query.Order(fmt.Sprintf("%s %s", sortBy, direction)) } // Apply pagination (manual calculation for better performance) page := req.Pagination.Page limit := req.Pagination.Limit if page <= 0 { page = 1 } if limit <= 0 { limit = 10 } offset := (page - 1) * limit err = query.Offset(offset).Limit(limit).Find(&clientss).Error if err != nil { return } // Create pagination response paging = paginator.Pagination{ Page: page, Limit: limit, Count: count, TotalPage: int((count + int64(limit) - 1) / int64(limit)), } return } func (_i *clientsRepository) FindOne(id uuid.UUID) (clients *entity.Clients, err error) { if err := _i.DB.DB.First(&clients, id).Error; err != nil { return nil, err } return clients, nil } func (_i *clientsRepository) Create(clients *entity.Clients) (clientsReturn *entity.Clients, err error) { result := _i.DB.DB.Create(clients) return clients, result.Error } func (_i *clientsRepository) Update(id uuid.UUID, clients *entity.Clients) (err error) { return _i.DB.DB.Model(&entity.Clients{}). Where(&entity.Clients{ID: id}). Updates(clients).Error } func (_i *clientsRepository) Delete(id uuid.UUID) error { return _i.DB.DB.Delete(&entity.Clients{}, id).Error } // ===================================================================== // NEW HIERARCHY METHODS // ===================================================================== // GetWithHierarchy gets client with parent and sub-clients loaded func (_i *clientsRepository) GetWithHierarchy(clientId uuid.UUID) (*entity.Clients, error) { var client entity.Clients err := _i.DB.DB.Preload("ParentClient"). Preload("SubClients", "is_active = ?", true). Where("id = ?", clientId). First(&client).Error return &client, err } // GetSubClients gets direct children of a client func (_i *clientsRepository) GetSubClients(parentId uuid.UUID, includeInactive bool) ([]entity.Clients, error) { var clients []entity.Clients query := _i.DB.DB.Where("parent_client_id = ?", parentId) if !includeInactive { query = query.Where("is_active = ?", true) } err := query.Find(&clients).Error return clients, err } // GetAllSubClients gets all descendants recursively func (_i *clientsRepository) GetAllSubClients(parentId uuid.UUID) ([]entity.Clients, error) { subClientIDs, err := client.GetSubClientIDs(_i.DB.DB, parentId) if err != nil || len(subClientIDs) == 0 { return []entity.Clients{}, err } var clients []entity.Clients err = _i.DB.DB.Where("id IN ?", subClientIDs).Find(&clients).Error return clients, err } // GetRootClients gets all clients without parent func (_i *clientsRepository) GetRootClients() ([]entity.Clients, error) { var clients []entity.Clients err := _i.DB.DB.Where("parent_client_id IS NULL AND is_active = ?", true). Find(&clients).Error return clients, err } // GetClientStats gets statistics for a client func (_i *clientsRepository) GetClientStats(clientId uuid.UUID) (map[string]interface{}, error) { stats := make(map[string]interface{}) // Count users var userCount int64 _i.DB.DB.Model(&entity.Users{}).Where("client_id = ? AND is_active = ?", clientId, true).Count(&userCount) stats["total_users"] = userCount // Count articles var articleCount int64 _i.DB.DB.Model(&entity.Articles{}).Where("client_id = ? AND is_active = ?", clientId, true).Count(&articleCount) stats["total_articles"] = articleCount // Count sub-clients var subClientCount int64 _i.DB.DB.Model(&entity.Clients{}).Where("parent_client_id = ? AND is_active = ?", clientId, true).Count(&subClientCount) stats["sub_client_count"] = subClientCount return stats, nil } // MoveClient moves a client to a different parent func (_i *clientsRepository) MoveClient(clientId, newParentId uuid.UUID) error { // Validate no circular reference if newParentId != uuid.Nil { // Check if newParent is a descendant of client (would create circle) descendants, _ := client.GetSubClientIDs(_i.DB.DB, clientId) for _, descId := range descendants { if descId == newParentId { return gorm.ErrInvalidData // Circular reference } } } return _i.DB.DB.Model(&entity.Clients{}). Where("id = ?", clientId). Update("parent_client_id", newParentId).Error } // IsParentClient checks if client has children func (_i *clientsRepository) IsParentClient(clientId uuid.UUID) (bool, error) { var count int64 err := _i.DB.DB.Model(&entity.Clients{}). Where("parent_client_id = ? AND is_active = ?", clientId, true). Count(&count).Error return count > 0, err }