thread-master/internal/model/permission/usecase/usecase.go

259 lines
8.8 KiB
Go
Raw Normal View History

2026-06-26 08:37:04 +00:00
package usecase
import (
"context"
"sort"
"strings"
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 == "" {
continue
}
if existing, ok := result.Permissions[node.HTTPPath]; ok {
result.Permissions[node.HTTPPath] = mergeHTTPMethods(existing, node.HTTPMethods)
continue
}
result.Permissions[node.HTTPPath] = node.HTTPMethods
}
if includeTree {
result.Tree = buildTree(nodes)
}
return result, nil
}
func mergeHTTPMethods(existing, next string) string {
seen := map[string]struct{}{}
out := make([]string, 0, 8)
for _, block := range []string{existing, next} {
for _, item := range strings.Split(block, "|") {
method := strings.ToUpper(strings.TrimSpace(item))
if method == "" {
continue
}
if _, ok := seen[method]; ok {
continue
}
seen[method] = struct{}{}
out = append(out, method)
}
}
return strings.Join(out, "|")
}
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{
"auth.logout",
"member.me.read",
"member.me.update",
"member.placement_settings.read",
"member.placement_settings.update",
"permission.me.read",
"setting.manage",
"ai.use",
"brand.manage",
"persona.manage",
"placement_topic.manage",
"threads_account.manage",
"job.manage",
"job.schedule.manage",
}
}
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 {
allMethods := "GET|POST|PUT|PATCH|DELETE"
return []*entity.Permission{
{Name: "auth.logout", HTTPMethods: "POST", HTTPPath: "/api/v1/auth/logout", Status: entity.StatusOpen, Type: entity.TypeFrontendUser},
{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: "member.placement_settings.read", HTTPMethods: "GET", HTTPPath: "/api/v1/members/me/placement-settings", Status: entity.StatusOpen, Type: entity.TypeFrontendUser},
{Name: "member.placement_settings.update", HTTPMethods: "PATCH", HTTPPath: "/api/v1/members/me/placement-settings", 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: "brand.manage", HTTPMethods: allMethods, HTTPPath: "/api/v1/brands/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser},
{Name: "persona.manage", HTTPMethods: allMethods, HTTPPath: "/api/v1/personas/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser},
{Name: "placement_topic.manage", HTTPMethods: allMethods, HTTPPath: "/api/v1/placement/topics/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser},
{Name: "threads_account.manage", HTTPMethods: "GET|POST|PUT|PATCH", HTTPPath: "/api/v1/threads-accounts/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser},
{Name: "job.manage", HTTPMethods: "GET|POST", HTTPPath: "/api/v1/jobs/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser},
{Name: "job.schedule.manage", HTTPMethods: "GET|POST|PUT|DELETE", HTTPPath: "/api/v1/job/schedules/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser},
{Name: "job.template.manage", HTTPMethods: "GET|PUT", HTTPPath: "/api/v1/job/templates/*", Status: entity.StatusOpen, Type: entity.TypeBackendUser},
}
}