Files
ocdp-go/backend/internal/domain/service/user_management_service.go
Ivan087 29d0310f03 feat(frontend): add Helm chart browser, monitoring, chart-references and values templates pages
Add new frontend pages for the multi-tenant OCDP platform:

- Charts page (/charts): Browse Harbor OCI registries to list Helm chart repositories
  and versions, with deploy modal to launch charts on selected clusters
- Monitoring page (/monitoring): Display cluster metrics (CPU/Memory/GPU usage)
  and per-node details with resource utilization bars
- Chart References page (/chart-references): CRUD for chart metadata references
- Values Templates page (/templates): CRUD for Helm values templates with version
  history and rollback support
- Sidebar: Add Charts navigation, update Storage and Templates links
- api.ts: Add all API client functions (clusterApi, registryApi, instanceApi,
  monitoringApi, storageApi, chartReferenceApi, valuesTemplateApi,
  workspaceApi, userApi) with full TypeScript types

Note: deploy flow and values template rollback not yet end-to-end tested.
2026-04-15 16:59:31 +08:00

298 lines
7.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/google/uuid"
"github.com/ocdp/cluster-service/internal/domain/entity"
"github.com/ocdp/cluster-service/internal/domain/repository"
)
// UserManagementService 用户管理领域服务(仅 Admin 可用)
type UserManagementService struct {
userRepo repository.UserRepository
workspaceRepo repository.WorkspaceRepository
passwordHasher PasswordHasher
}
// NewUserManagementService 创建用户管理服务
func NewUserManagementService(
userRepo repository.UserRepository,
workspaceRepo repository.WorkspaceRepository,
passwordHasher PasswordHasher,
) *UserManagementService {
return &UserManagementService{
userRepo: userRepo,
workspaceRepo: workspaceRepo,
passwordHasher: passwordHasher,
}
}
// CreateUser 创建用户Admin 操作)
func (s *UserManagementService) CreateUser(ctx context.Context, username, password, email, role string, workspaceID string) (*entity.User, error) {
// 检查用户是否已存在
existing, _ := s.userRepo.GetByUsername(ctx, username)
if existing != nil {
return nil, entity.ErrUserExists
}
// 验证角色
if role != string(entity.RoleAdmin) && role != string(entity.RoleUser) {
return nil, fmt.Errorf("invalid role: %s", role)
}
// 如果指定了 workspace验证 workspace 存在
if workspaceID != "" {
_, err := s.workspaceRepo.GetByID(ctx, workspaceID)
if err != nil {
if err == entity.ErrWorkspaceNotFound {
return nil, entity.ErrWorkspaceNotFound
}
return nil, err
}
}
// Admin 不能分配到 workspace
if role == string(entity.RoleAdmin) && workspaceID != "" {
workspaceID = ""
}
// 哈希密码
passwordHash, err := s.passwordHasher.Hash(password)
if err != nil {
return nil, err
}
// 生成占位邮箱
if email == "" {
email = username + "@local.ocdp"
}
// 创建用户
user := entity.NewUser(username, passwordHash, email)
user.ID = uuid.New().String()
user.Role = entity.UserRole(role)
user.WorkspaceID = workspaceID
user.IsActive = true
user.MustChangePassword = true // 首次登录必须修改密码
if err := user.Validate(); err != nil {
return nil, err
}
if err := s.userRepo.Create(ctx, user); err != nil {
return nil, err
}
return user, nil
}
// GetUser 获取用户
func (s *UserManagementService) GetUser(ctx context.Context, id string) (*entity.User, error) {
return s.userRepo.GetByID(ctx, id)
}
// ListUsers 列出用户(可筛选 workspace
func (s *UserManagementService) ListUsers(ctx context.Context, workspaceID string) ([]*entity.User, error) {
if workspaceID != "" {
return s.userRepo.ListByWorkspace(ctx, workspaceID)
}
return s.userRepo.List(ctx)
}
// UpdateUser 更新用户信息
func (s *UserManagementService) UpdateUser(ctx context.Context, user *entity.User) error {
return s.userRepo.Update(ctx, user)
}
// SetUserActive 启用/禁用用户
func (s *UserManagementService) SetUserActive(ctx context.Context, userID string, isActive bool) error {
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return err
}
user.IsActive = isActive
return s.userRepo.Update(ctx, user)
}
// ChangeUserWorkspace 分配用户到 workspace
func (s *UserManagementService) ChangeUserWorkspace(ctx context.Context, userID, workspaceID string) error {
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return err
}
// Admin 不能分配到 workspace
if user.Role == entity.RoleAdmin {
return fmt.Errorf("admin user cannot be assigned to workspace")
}
// 验证 workspace 存在
if workspaceID != "" {
_, err := s.workspaceRepo.GetByID(ctx, workspaceID)
if err != nil {
if err == sql.ErrNoRows || err == entity.ErrWorkspaceNotFound {
return entity.ErrWorkspaceNotFound
}
return err
}
}
user.WorkspaceID = workspaceID
return s.userRepo.Update(ctx, user)
}
// ResetPassword 重置用户密码Admin 操作)
func (s *UserManagementService) ResetPassword(ctx context.Context, userID, newPassword string) error {
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return err
}
// 哈希新密码
passwordHash, err := s.passwordHasher.Hash(newPassword)
if err != nil {
return err
}
// 更新密码并设置必须修改密码标志
user.PasswordHash = passwordHash
user.MustChangePassword = true
user.RevokeAllTokens() // 强制登出所有会话
return s.userRepo.Update(ctx, user)
}
// DeleteUser 删除用户
func (s *UserManagementService) DeleteUser(ctx context.Context, id string) error {
return s.userRepo.Delete(ctx, id)
}
// GetUserWithWorkspace 获取用户及其 workspace 信息
type UserWithWorkspace struct {
User *entity.User
Workspace *entity.Workspace
}
// GetUserWithWorkspace 获取用户及其 workspace 信息
func (s *UserManagementService) GetUserWithWorkspace(ctx context.Context, userID string) (*UserWithWorkspace, error) {
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, err
}
result := &UserWithWorkspace{
User: user,
}
if user.WorkspaceID != "" {
workspace, _ := s.workspaceRepo.GetByID(ctx, user.WorkspaceID)
result.Workspace = workspace
}
return result, nil
}
// ListUsersWithWorkspace 列出用户及其 workspace 信息
func (s *UserManagementService) ListUsersWithWorkspace(ctx context.Context) ([]*UserWithWorkspace, error) {
users, err := s.userRepo.List(ctx)
if err != nil {
return nil, err
}
// 预加载所有 workspace
workspaces, err := s.workspaceRepo.List(ctx)
if err != nil {
return nil, err
}
workspaceMap := make(map[string]*entity.Workspace)
for _, w := range workspaces {
workspaceMap[w.ID] = w
}
result := make([]*UserWithWorkspace, len(users))
for i, user := range users {
result[i] = &UserWithWorkspace{
User: user,
}
if user.WorkspaceID != "" {
result[i].Workspace = workspaceMap[user.WorkspaceID]
}
}
return result, nil
}
// EnsureAdminExists 确保存在一个 Admin 用户
func (s *UserManagementService) EnsureAdminExists(ctx context.Context, defaultPassword string) error {
users, err := s.userRepo.List(ctx)
if err != nil {
return err
}
// 检查是否已有 admin
for _, u := range users {
if u.Role == entity.RoleAdmin {
return nil
}
}
// 创建默认 admin 用户
_, err = s.CreateUser(ctx, "admin", defaultPassword, "", string(entity.RoleAdmin), "")
return err
}
// CreateInitialUser 创建初始用户(首次启动时调用)
func (s *UserManagementService) CreateInitialUser(ctx context.Context, username, password, role string) (*entity.User, error) {
// 检查是否已有用户
users, err := s.userRepo.List(ctx)
if err != nil {
return nil, err
}
if len(users) > 0 {
return nil, fmt.Errorf("initial user already exists")
}
// 验证角色
if role != string(entity.RoleAdmin) && role != string(entity.RoleUser) {
return nil, fmt.Errorf("invalid role: %s", role)
}
// 哈希密码
passwordHash, err := s.passwordHasher.Hash(password)
if err != nil {
return nil, err
}
// 生成占位邮箱
email := username + "@local.ocdp"
// 创建用户
user := entity.NewUser(username, passwordHash, email)
user.ID = uuid.New().String()
user.Role = entity.UserRole(role)
// workspace_id 为 NULLadmin或空首个普通用户
user.IsActive = true
user.MustChangePassword = false // 初始用户不需要强制修改密码
if err := user.Validate(); err != nil {
return nil, err
}
// 设置创建时间和更新时间
now := time.Now()
user.CreatedAt = now
user.UpdatedAt = now
if err := s.userRepo.Create(ctx, user); err != nil {
return nil, err
}
return user, nil
}