- Updated .env.example to include new configuration options for the UltiAI branding and API endpoints. - Enhanced Nginx configuration to support new API routes for the MCP and WebSocket connections. - Introduced sub-filters for branding adjustments in Nginx responses. - Added new JavaScript patch for API endpoint adjustments. - Implemented tests for new API functionalities and improved error handling in the AI gateway.
132 lines
3.5 KiB
Go
132 lines
3.5 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
platformusers "github.com/ultisuite/ulti-backend/internal/users"
|
|
)
|
|
|
|
type bulkUsersRequest struct {
|
|
UserIDs []string `json:"user_ids"`
|
|
Action string `json:"action"`
|
|
Role string `json:"role"`
|
|
GroupID string `json:"group_id"`
|
|
}
|
|
|
|
type bulkUsersResult struct {
|
|
SuccessCount int `json:"success_count"`
|
|
Failed []bulkUserFailure `json:"failed,omitempty"`
|
|
}
|
|
|
|
type bulkUserFailure struct {
|
|
UserID string `json:"user_id"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func (s *Service) BulkUsersAction(ctx context.Context, actorSub string, req bulkUsersRequest) (bulkUsersResult, error) {
|
|
action := strings.ToLower(strings.TrimSpace(req.Action))
|
|
userIDs := dedupeNonEmpty(req.UserIDs)
|
|
if len(userIDs) == 0 {
|
|
return bulkUsersResult{}, fmt.Errorf("no user ids")
|
|
}
|
|
|
|
result := bulkUsersResult{}
|
|
switch action {
|
|
case "disable":
|
|
for _, userID := range userIDs {
|
|
if err := s.DisableUser(ctx, actorSub, userID); err != nil {
|
|
result.Failed = append(result.Failed, bulkUserFailure{UserID: userID, Message: bulkErrorMessage(err)})
|
|
continue
|
|
}
|
|
result.SuccessCount++
|
|
}
|
|
case "reactivate":
|
|
for _, userID := range userIDs {
|
|
if err := s.ReactivateUser(ctx, actorSub, userID); err != nil {
|
|
result.Failed = append(result.Failed, bulkUserFailure{UserID: userID, Message: bulkErrorMessage(err)})
|
|
continue
|
|
}
|
|
result.SuccessCount++
|
|
}
|
|
case "delete":
|
|
for _, userID := range userIDs {
|
|
if err := s.DeleteUser(ctx, actorSub, userID); err != nil {
|
|
result.Failed = append(result.Failed, bulkUserFailure{UserID: userID, Message: bulkErrorMessage(err)})
|
|
continue
|
|
}
|
|
result.SuccessCount++
|
|
}
|
|
case "set_role":
|
|
role := strings.TrimSpace(req.Role)
|
|
for _, userID := range userIDs {
|
|
if _, err := s.SetUserRole(ctx, actorSub, userID, role); err != nil {
|
|
result.Failed = append(result.Failed, bulkUserFailure{UserID: userID, Message: bulkErrorMessage(err)})
|
|
continue
|
|
}
|
|
result.SuccessCount++
|
|
}
|
|
case "add_to_group":
|
|
groupID := strings.TrimSpace(req.GroupID)
|
|
if groupID == "" {
|
|
return bulkUsersResult{}, fmt.Errorf("group_id required")
|
|
}
|
|
if err := s.AddUsersToGroup(ctx, actorSub, groupID, userIDs); err != nil {
|
|
return bulkUsersResult{}, err
|
|
}
|
|
result.SuccessCount = len(userIDs)
|
|
case "remove_from_group":
|
|
groupID := strings.TrimSpace(req.GroupID)
|
|
if groupID == "" {
|
|
return bulkUsersResult{}, fmt.Errorf("group_id required")
|
|
}
|
|
if err := s.RemoveUsersFromGroup(ctx, actorSub, groupID, userIDs); err != nil {
|
|
return bulkUsersResult{}, err
|
|
}
|
|
result.SuccessCount = len(userIDs)
|
|
default:
|
|
return bulkUsersResult{}, fmt.Errorf("invalid action")
|
|
}
|
|
|
|
s.logAudit(ctx, actorSub, "bulk_users_action", map[string]any{
|
|
"action": action,
|
|
"success_count": result.SuccessCount,
|
|
"failed_count": len(result.Failed),
|
|
"group_id": strings.TrimSpace(req.GroupID),
|
|
"role": strings.TrimSpace(req.Role),
|
|
})
|
|
return result, nil
|
|
}
|
|
|
|
func bulkErrorMessage(err error) string {
|
|
if errors.Is(err, ErrNotFound) {
|
|
return "not found"
|
|
}
|
|
if errors.Is(err, platformusers.ErrLastPlatformAdmin) {
|
|
return "cannot remove the last platform admin"
|
|
}
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
return err.Error()
|
|
}
|
|
|
|
func dedupeNonEmpty(ids []string) []string {
|
|
seen := make(map[string]struct{}, len(ids))
|
|
out := make([]string, 0, len(ids))
|
|
for _, id := range ids {
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
out = append(out, id)
|
|
}
|
|
return out
|
|
}
|