kontenhumas-be/app/module/clients/service/clients.service.go

514 lines
17 KiB
Go

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"
"time"
"github.com/gofiber/fiber/v2"
"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
ClientLogoUploadSvc *ClientLogoUploadService
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) (*response.ClientWithUserResponse, error)
// Logo upload methods
UploadLogo(clientId uuid.UUID, c *fiber.Ctx) (string, error)
DeleteLogo(clientId uuid.UUID, imagePath string) error
GetLogoURL(imagePath string) (string, error)
}
// NewClientsService init ClientsService
func NewClientsService(repo repository.ClientsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, usersSvc usersService.UsersService, clientLogoUploadSvc *ClientLogoUploadService) ClientsService {
return &clientsService{
Repo: repo,
Log: log,
UsersRepo: usersRepo,
UsersSvc: usersSvc,
ClientLogoUploadSvc: clientLogoUploadSvc,
}
}
// 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,
LogoUrl: req.LogoUrl,
LogoImagePath: req.LogoImagePath,
Address: req.Address,
PhoneNumber: req.PhoneNumber,
Website: req.Website,
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,
LogoUrl: client.LogoUrl,
LogoImagePath: client.LogoImagePath,
Address: client.Address,
PhoneNumber: client.PhoneNumber,
Website: client.Website,
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) (*response.ClientWithUserResponse, error) {
_i.Log.Info().Interface("data", req).Msg("Creating client with admin user (Public endpoint)")
// 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()
// For public endpoint, no created by user
newClient.CreatedById = nil
// 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,
ClientId: &createdClient.ID,
// Set default admin level and role (you may need to adjust these based on your system)
UserLevelId: 1, // Assuming level 1 is generic level
UserRoleId: 2, // Assuming role 1 is admin client role
}
// Create user with the new client ID
createdUser, err := _i.UsersSvc.Save("", 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")
_i.Log.Info().Interface("createdClient", createdClient).Msg("Created Client")
// 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
}
// =====================================================================
// LOGO UPLOAD METHODS
// =====================================================================
// UploadLogo uploads client logo to MinIO
func (_i *clientsService) UploadLogo(clientId uuid.UUID, c *fiber.Ctx) (string, error) {
_i.Log.Info().Str("clientId", clientId.String()).Msg("Uploading client logo")
// Upload logo using the upload service
imagePath, err := _i.ClientLogoUploadSvc.UploadLogo(c, clientId.String())
if err != nil {
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Msg("Failed to upload client logo")
return "", err
}
// Update client with new logo image path
updateReq := request.ClientsUpdateRequest{
LogoImagePath: &imagePath,
}
err = _i.Update(clientId, updateReq)
if err != nil {
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Str("imagePath", imagePath).Msg("Failed to update client with logo path")
// Try to clean up uploaded file
cleanupErr := _i.ClientLogoUploadSvc.DeleteLogo(clientId.String(), imagePath)
if cleanupErr != nil {
_i.Log.Error().Err(cleanupErr).Str("imagePath", imagePath).Msg("Failed to cleanup uploaded logo after update failure")
}
return "", fmt.Errorf("failed to update client with logo path: %w", err)
}
_i.Log.Info().Str("clientId", clientId.String()).Str("imagePath", imagePath).Msg("Client logo uploaded and updated successfully")
return imagePath, nil
}
// DeleteLogo deletes client logo from MinIO
func (_i *clientsService) DeleteLogo(clientId uuid.UUID, imagePath string) error {
_i.Log.Info().Str("clientId", clientId.String()).Str("imagePath", imagePath).Msg("Deleting client logo")
err := _i.ClientLogoUploadSvc.DeleteLogo(clientId.String(), imagePath)
if err != nil {
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Str("imagePath", imagePath).Msg("Failed to delete client logo")
return err
}
// Clear logo image path from client
emptyPath := ""
updateReq := request.ClientsUpdateRequest{
LogoImagePath: &emptyPath,
}
err = _i.Update(clientId, updateReq)
if err != nil {
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Msg("Failed to clear logo path from client")
return fmt.Errorf("failed to clear logo path from client: %w", err)
}
_i.Log.Info().Str("clientId", clientId.String()).Msg("Client logo deleted successfully")
return nil
}
// GetLogoURL generates a presigned URL for the logo
func (_i *clientsService) GetLogoURL(imagePath string) (string, error) {
if imagePath == "" {
return "", nil
}
_i.Log.Info().Str("imagePath", imagePath).Msg("Generating logo URL")
// Generate presigned URL valid for 24 hours
url, err := _i.ClientLogoUploadSvc.GetLogoURL(imagePath, 24*time.Hour)
if err != nil {
_i.Log.Error().Err(err).Str("imagePath", imagePath).Msg("Failed to generate logo URL")
return "", err
}
_i.Log.Info().Str("imagePath", imagePath).Str("url", url).Msg("Logo URL generated successfully")
return url, nil
}