210 lines
6.7 KiB
Go
210 lines
6.7 KiB
Go
|
|
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},
|
||
|
|
}
|
||
|
|
}
|