package service import ( "errors" "fmt" "netidhub-saas-be/app/database/entity" "netidhub-saas-be/app/module/clients/mapper" "netidhub-saas-be/app/module/clients/repository" "netidhub-saas-be/app/module/clients/request" "netidhub-saas-be/app/module/clients/response" usersRepository "netidhub-saas-be/app/module/users/repository" usersRequest "netidhub-saas-be/app/module/users/request" usersService "netidhub-saas-be/app/module/users/service" "netidhub-saas-be/utils/paginator" "github.com/google/uuid" "github.com/rs/zerolog" utilSvc "netidhub-saas-be/utils/service" ) // ClientsService type clientsService struct { Repo repository.ClientsRepository UsersRepo usersRepository.UsersRepository UsersSvc usersService.UsersService Log zerolog.Logger } // ClientsService define interface of IClientsService type ClientsService interface { All(authToken string, req request.ClientsQueryRequest) (clients []*response.ClientsResponse, paging paginator.Pagination, err error) Show(id uuid.UUID) (clients *response.ClientsResponse, err error) Save(req request.ClientsCreateRequest, authToken string) (clients *entity.Clients, err error) Update(id uuid.UUID, req request.ClientsUpdateRequest) (err error) Delete(id uuid.UUID) error // New hierarchy methods CreateSubClient(parentId uuid.UUID, req request.ClientsCreateRequest) (*entity.Clients, error) MoveClient(clientId uuid.UUID, req request.MoveClientRequest) error GetHierarchy(clientId uuid.UUID) (*response.ClientHierarchyResponse, error) GetClientStats(clientId uuid.UUID) (*response.ClientStatsResponse, error) BulkCreateSubClients(req request.BulkCreateSubClientsRequest) (*response.BulkOperationResponse, error) // Client with user creation CreateClientWithUser(req request.ClientWithUserCreateRequest, authToken string) (*response.ClientWithUserResponse, error) } // NewClientsService init ClientsService func NewClientsService(repo repository.ClientsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, usersSvc usersService.UsersService) ClientsService { return &clientsService{ Repo: repo, Log: log, UsersRepo: usersRepo, UsersSvc: usersSvc, } } // All implement interface of ClientsService func (_i *clientsService) All(authToken string, req request.ClientsQueryRequest) (clientss []*response.ClientsResponse, paging paginator.Pagination, err error) { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } results, paging, err := _i.Repo.GetAll(req) if err != nil { return } for _, result := range results { clientss = append(clientss, mapper.ClientsResponseMapper(result)) } return } func (_i *clientsService) Show(id uuid.UUID) (clients *response.ClientsResponse, err error) { result, err := _i.Repo.FindOne(id) if err != nil { return nil, err } return mapper.ClientsResponseMapper(result), nil } func (_i *clientsService) Save(req request.ClientsCreateRequest, authToken string) (clients *entity.Clients, err error) { _i.Log.Info().Interface("data", req).Msg("") // Convert request to entity newReq := &entity.Clients{ Name: req.Name, Description: req.Description, ClientType: req.ClientType, ParentClientId: req.ParentClientId, MaxUsers: req.MaxUsers, MaxStorage: req.MaxStorage, Settings: req.Settings, } _i.Log.Info().Interface("token", authToken).Msg("") createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) _i.Log.Info().Interface("token", authToken).Msg("") newReq.CreatedById = &createdBy.ID newReq.ID = uuid.New() _i.Log.Info().Interface("new data", newReq).Msg("") return _i.Repo.Create(newReq) } func (_i *clientsService) Update(id uuid.UUID, req request.ClientsUpdateRequest) (err error) { _i.Log.Info().Interface("data", req).Msg("") // Convert request to entity updateReq := &entity.Clients{ Name: *req.Name, Description: req.Description, ClientType: *req.ClientType, ParentClientId: req.ParentClientId, MaxUsers: req.MaxUsers, MaxStorage: req.MaxStorage, Settings: req.Settings, IsActive: req.IsActive, } return _i.Repo.Update(id, updateReq) } func (_i *clientsService) Delete(id uuid.UUID) error { result, err := _i.Repo.FindOne(id) if err != nil { return err } isActive := false result.IsActive = &isActive return _i.Repo.Update(id, result) } // ===================================================================== // NEW HIERARCHY METHODS // ===================================================================== // CreateSubClient creates a client under a parent func (_i *clientsService) CreateSubClient(parentId uuid.UUID, req request.ClientsCreateRequest) (*entity.Clients, error) { // Validate parent exists _, err := _i.Repo.FindOne(parentId) if err != nil { return nil, errors.New("parent client not found") } // Set client type and parent req.ClientType = "sub_client" req.ParentClientId = &parentId // Convert to entity newReq := &entity.Clients{ Name: req.Name, Description: req.Description, ClientType: req.ClientType, ParentClientId: req.ParentClientId, MaxUsers: req.MaxUsers, MaxStorage: req.MaxStorage, Settings: req.Settings, } newReq.ID = uuid.New() return _i.Repo.Create(newReq) } // MoveClient moves a client to different parent func (_i *clientsService) MoveClient(clientId uuid.UUID, req request.MoveClientRequest) error { client, err := _i.Repo.FindOne(clientId) if err != nil { return errors.New("client not found") } // If moving to root (standalone) if req.TargetParentId == nil { client.ClientType = "standalone" client.ParentClientId = nil return _i.Repo.Update(clientId, client) } // Validate target parent exists _, err = _i.Repo.FindOne(*req.TargetParentId) if err != nil { return errors.New("target parent not found") } // Move return _i.Repo.MoveClient(clientId, *req.TargetParentId) } // GetHierarchy gets full client tree func (_i *clientsService) GetHierarchy(clientId uuid.UUID) (*response.ClientHierarchyResponse, error) { client, err := _i.Repo.GetWithHierarchy(clientId) if err != nil { return nil, err } return _i.buildHierarchyResponse(client, 0, []string{}), nil } // buildHierarchyResponse recursively builds hierarchy func (_i *clientsService) buildHierarchyResponse(client *entity.Clients, level int, path []string) *response.ClientHierarchyResponse { currentPath := append(path, client.Name) resp := &response.ClientHierarchyResponse{ ID: client.ID, Name: client.Name, Description: client.Description, ClientType: client.ClientType, Level: level, Path: currentPath, ParentClientId: client.ParentClientId, IsActive: client.IsActive, } // Count users (simplified - would need proper DB access) resp.CurrentUsers = 0 // TODO: implement user count // Build sub-clients recursively if client.SubClients != nil { for _, subClient := range client.SubClients { resp.SubClients = append(resp.SubClients, *_i.buildHierarchyResponse(&subClient, level+1, currentPath)) } } return resp } // GetClientStats gets comprehensive statistics func (_i *clientsService) GetClientStats(clientId uuid.UUID) (*response.ClientStatsResponse, error) { client, err := _i.Repo.FindOne(clientId) if err != nil { return nil, err } stats, err := _i.Repo.GetClientStats(clientId) if err != nil { return nil, err } isParent, _ := _i.Repo.IsParentClient(clientId) return &response.ClientStatsResponse{ ClientId: client.ID, ClientName: client.Name, TotalUsers: stats["total_users"].(int), TotalArticles: stats["total_articles"].(int), SubClientCount: stats["sub_client_count"].(int), IsParent: isParent, }, nil } // BulkCreateSubClients creates multiple sub-clients func (_i *clientsService) BulkCreateSubClients(req request.BulkCreateSubClientsRequest) (*response.BulkOperationResponse, error) { results := []response.BulkOperationResult{} successful := 0 failed := 0 for i, subClientReq := range req.SubClients { createReq := request.ClientsCreateRequest{ Name: subClientReq.Name, Description: subClientReq.Description, ClientType: "sub_client", ParentClientId: &req.ParentClientId, MaxUsers: subClientReq.MaxUsers, MaxStorage: subClientReq.MaxStorage, } client, err := _i.CreateSubClient(req.ParentClientId, createReq) if err != nil { failed++ errMsg := err.Error() results = append(results, response.BulkOperationResult{ Index: i, Name: subClientReq.Name, Success: false, Error: &errMsg, }) } else { successful++ results = append(results, response.BulkOperationResult{ Index: i, ClientId: &client.ID, Name: client.Name, Success: true, }) } } return &response.BulkOperationResponse{ TotalRequested: len(req.SubClients), Successful: successful, Failed: failed, Results: results, }, nil } // CreateClientWithUser creates a client and admin user in one transaction func (_i *clientsService) CreateClientWithUser(req request.ClientWithUserCreateRequest, authToken string) (*response.ClientWithUserResponse, error) { _i.Log.Info().Interface("data", req).Msg("Creating client with admin user") // Step 1: Create the client clientReq := req.Client newClient := &entity.Clients{ Name: clientReq.Name, Description: clientReq.Description, ClientType: clientReq.ClientType, ParentClientId: clientReq.ParentClientId, MaxUsers: clientReq.MaxUsers, MaxStorage: clientReq.MaxStorage, Settings: clientReq.Settings, } // Generate new UUID for client newClient.ID = uuid.New() // Set created by if auth token provided if authToken != "" { createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if createdBy != nil { newClient.CreatedById = &createdBy.ID } } // Create client createdClient, err := _i.Repo.Create(newClient) if err != nil { _i.Log.Error().Err(err).Msg("Failed to create client") return nil, fmt.Errorf("failed to create client: %w", err) } _i.Log.Info().Interface("clientId", createdClient.ID).Msg("Client created successfully") // Step 2: Create admin user for the client adminUserReq := req.AdminUser // Convert to UsersCreateRequest format userCreateReq := usersRequest.UsersCreateRequest{ Username: adminUserReq.Username, Email: adminUserReq.Email, Fullname: adminUserReq.Fullname, Password: adminUserReq.Password, PhoneNumber: adminUserReq.PhoneNumber, Address: adminUserReq.Address, WorkType: adminUserReq.WorkType, GenderType: adminUserReq.GenderType, IdentityType: adminUserReq.IdentityType, IdentityGroup: adminUserReq.IdentityGroup, IdentityGroupNumber: adminUserReq.IdentityGroupNumber, IdentityNumber: adminUserReq.IdentityNumber, DateOfBirth: adminUserReq.DateOfBirth, LastEducation: adminUserReq.LastEducation, // Set default admin level and role (you may need to adjust these based on your system) UserLevelId: 1, // Assuming level 1 is admin level UserRoleId: 1, // Assuming role 1 is admin role } // Create user with the new client ID createdUser, err := _i.UsersSvc.Save(authToken, userCreateReq) if err != nil { _i.Log.Error().Err(err).Msg("Failed to create admin user") // Rollback: delete the created client _i.Repo.Delete(createdClient.ID) return nil, fmt.Errorf("failed to create admin user: %w", err) } _i.Log.Info().Interface("userId", createdUser.ID).Msg("Admin user created successfully") // Step 3: Prepare response clientResponse := mapper.ClientsResponseMapper(createdClient) adminUserResponse := response.AdminUserResponse{ ID: createdUser.ID, Username: createdUser.Username, Email: createdUser.Email, Fullname: createdUser.Fullname, UserLevelId: createdUser.UserLevelId, UserRoleId: createdUser.UserRoleId, PhoneNumber: createdUser.PhoneNumber, Address: createdUser.Address, WorkType: createdUser.WorkType, GenderType: createdUser.GenderType, IdentityType: createdUser.IdentityType, IdentityGroup: createdUser.IdentityGroup, IdentityGroupNumber: createdUser.IdentityGroupNumber, IdentityNumber: createdUser.IdentityNumber, DateOfBirth: createdUser.DateOfBirth, LastEducation: createdUser.LastEducation, KeycloakId: createdUser.KeycloakId, ClientId: *createdUser.ClientId, IsActive: *createdUser.IsActive, CreatedAt: createdUser.CreatedAt, UpdatedAt: createdUser.UpdatedAt, } return &response.ClientWithUserResponse{ Client: *clientResponse, AdminUser: adminUserResponse, Message: fmt.Sprintf("Client '%s' and admin user '%s' created successfully", createdClient.Name, createdUser.Username), }, nil }