package usecase import ( "context" "sort" memberentity "haixun-backend/internal/model/member/domain/entity" "haixun-backend/internal/model/permission/domain/entity" domrepo "haixun-backend/internal/model/permission/domain/repository" domusecase "haixun-backend/internal/model/permission/domain/usecase" ) type permissionUseCase struct { permissions domrepo.PermissionRepository rolePermissions domrepo.RolePermissionRepository } func NewUseCase(permissions domrepo.PermissionRepository, rolePermissions domrepo.RolePermissionRepository) domusecase.UseCase { return &permissionUseCase{permissions: permissions, rolePermissions: rolePermissions} } func (u *permissionUseCase) EnsureDefaultPermissions(ctx context.Context) error { for _, permission := range defaultPermissions() { if err := u.permissions.UpsertByName(ctx, permission); err != nil { return err } } return nil } func (u *permissionUseCase) EnsureDefaultRolePermissions(ctx context.Context, tenantID string) error { if tenantID == "" { return nil } perms, err := u.permissions.List(ctx, domrepo.PermissionFilter{Status: entity.StatusOpen}) if err != nil { return err } allIDs := make([]string, 0, len(perms)) byName := make(map[string]string, len(perms)) for _, item := range perms { id := item.ID.Hex() allIDs = append(allIDs, id) byName[item.Name] = id } if err := u.rolePermissions.SetForRole(ctx, tenantID, "admin", allIDs); err != nil { return err } userIDs := make([]string, 0, len(defaultUserPermissionNames())) for _, name := range defaultUserPermissionNames() { if id, ok := byName[name]; ok { userIDs = append(userIDs, id) } } return u.rolePermissions.SetForRole(ctx, tenantID, "user", userIDs) } func (u *permissionUseCase) Catalog(ctx context.Context, req domusecase.CatalogRequest) ([]domusecase.PermissionNode, []domusecase.PermissionNode, error) { filter := domrepo.PermissionFilter{} if req.Status != "" { filter.Status = entity.Status(req.Status) } if req.Type != "" { filter.Type = entity.Type(req.Type) } items, err := u.permissions.List(ctx, filter) if err != nil { return nil, nil, err } list := permissionNodes(items) if req.Tree { return buildTree(list), nil, nil } return nil, list, nil } func (u *permissionUseCase) Me(ctx context.Context, member *memberentity.Member, includeTree bool) (*domusecase.MePermissions, error) { if member == nil { return nil, nil } roles := member.Roles if len(roles) == 0 { roles = []string{"user"} } rolePermissions, err := u.rolePermissions.ListByRoles(ctx, member.TenantID, roles) if err != nil { return nil, err } ids := make([]string, 0, len(rolePermissions)) for _, item := range rolePermissions { ids = append(ids, item.PermissionID) } perms, err := u.permissions.ListByIDs(ctx, ids) if err != nil { return nil, err } if len(perms) == 0 { perms, err = u.permissions.List(ctx, domrepo.PermissionFilter{Status: entity.StatusOpen}) if err != nil { return nil, err } if !hasRole(roles, "admin") { perms = filterPermissionsByName(perms, defaultUserPermissionNames()) } } nodes := permissionNodes(perms) result := &domusecase.MePermissions{ TenantID: member.TenantID, UID: member.UID, Roles: roles, Permissions: map[string]string{}, } for _, node := range nodes { if node.HTTPPath != "" && node.HTTPMethods != "" { result.Permissions[node.HTTPPath] = node.HTTPMethods } } if includeTree { result.Tree = buildTree(nodes) } return result, nil } func hasRole(roles []string, role string) bool { for _, item := range roles { if item == role { return true } } return false } func filterPermissionsByName(items []*entity.Permission, names []string) []*entity.Permission { allowed := map[string]struct{}{} for _, name := range names { allowed[name] = struct{}{} } out := make([]*entity.Permission, 0, len(items)) for _, item := range items { if _, ok := allowed[item.Name]; ok { out = append(out, item) } } return out } func defaultUserPermissionNames() []string { return []string{ "member.me.read", "member.me.update", "permission.me.read", "ai.use", } } func (u *permissionUseCase) ReplaceRolePermissions(ctx context.Context, tenantID, roleKey string, permissionIDs []string) error { return u.rolePermissions.SetForRole(ctx, tenantID, roleKey, permissionIDs) } func permissionNodes(items []*entity.Permission) []domusecase.PermissionNode { out := make([]domusecase.PermissionNode, 0, len(items)) for _, item := range items { out = append(out, domusecase.PermissionNode{ ID: item.ID.Hex(), Parent: item.Parent, Name: item.Name, HTTPMethods: item.HTTPMethods, HTTPPath: item.HTTPPath, Status: string(item.Status), Type: string(item.Type), }) } sort.SliceStable(out, func(i, j int) bool { return out[i].Name < out[j].Name }) return out } func buildTree(items []domusecase.PermissionNode) []domusecase.PermissionNode { byParent := map[string][]domusecase.PermissionNode{} index := map[string]*domusecase.PermissionNode{} nodes := make([]domusecase.PermissionNode, len(items)) copy(nodes, items) for i := range nodes { index[nodes[i].ID] = &nodes[i] byParent[nodes[i].Parent] = append(byParent[nodes[i].Parent], nodes[i]) } for parent, children := range byParent { sort.SliceStable(children, func(i, j int) bool { return children[i].Name < children[j].Name }) byParent[parent] = children } for parent, children := range byParent { if node, ok := index[parent]; ok { node.Children = children } } return byParent[""] } func defaultPermissions() []*entity.Permission { return []*entity.Permission{ {Name: "member.me.read", HTTPMethods: "GET", HTTPPath: "/api/v1/members/me", Status: entity.StatusOpen, Type: entity.TypeFrontendUser}, {Name: "member.me.update", HTTPMethods: "PATCH", HTTPPath: "/api/v1/members/me", Status: entity.StatusOpen, Type: entity.TypeFrontendUser}, {Name: "permission.catalog.read", HTTPMethods: "GET", HTTPPath: "/api/v1/permissions/catalog", Status: entity.StatusOpen, Type: entity.TypeBackendUser}, {Name: "permission.me.read", HTTPMethods: "GET", HTTPPath: "/api/v1/permissions/me", Status: entity.StatusOpen, Type: entity.TypeFrontendUser}, {Name: "setting.manage", HTTPMethods: "GET|PUT|DELETE", HTTPPath: "/api/v1/settings/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser}, {Name: "ai.use", HTTPMethods: "GET|POST", HTTPPath: "/api/v1/ai/*", Status: entity.StatusOpen, Type: entity.TypeFrontendUser}, {Name: "job.manage", HTTPMethods: "GET|POST|PUT", HTTPPath: "/api/v1/jobs/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser}, {Name: "job.template.manage", HTTPMethods: "GET|PUT", HTTPPath: "/api/v1/job/templates/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser}, } }