package useCases import ( "RenewCMS/internal/domain/contenttype" "RenewCMS/internal/domain/role" "RenewCMS/internal/domain/user" "errors" "slices" "time" ) type RoleEditor struct { ID uint32 `json:"id"` Name string `json:"name"` } type RoleView struct { role.Role Members int `json:"members"` UpdatedAt *time.Time `json:"updated_at"` UpdatedBy *RoleEditor `json:"updated_by"` } type roleViewer struct { roles role.Repository users user.Repository contentTypes contenttype.Repository } func (v roleViewer) liveGrants(grants []role.Grant) ([]role.Grant, error) { typeKeys, err := allowedTypeKeys(v.contentTypes) if err != nil { return nil, err } live := make([]role.Grant, 0, len(grants)) for _, grant := range grants { if _, typeKey, parseErr := role.ParseTypedKey(grant.Key); parseErr == nil && !slices.Contains(typeKeys, typeKey) { continue } live = append(live, grant) } return live, nil } func (v roleViewer) views(found []role.Role) ([]RoleView, error) { counts, err := memberCounts(v.roles, v.users.GetAll()) if err != nil { return nil, err } editors, err := v.editors(found) if err != nil { return nil, err } views := make([]RoleView, 0, len(found)) for _, item := range found { view := toRoleView(item, counts[item.ID], editors) if view.Grants, err = v.liveGrants(view.Grants); err != nil { return nil, err } views = append(views, view) } return views, nil } func (v roleViewer) one(found role.Role, members int) (RoleView, error) { editors, err := v.editors([]role.Role{found}) if err != nil { return RoleView{}, err } view := toRoleView(found, members, editors) if view.Grants, err = v.liveGrants(view.Grants); err != nil { return RoleView{}, err } return view, nil } func (v roleViewer) members(roleID uint32) (int, error) { counts, err := memberCounts(v.roles, v.users.GetAll()) if err != nil { return 0, err } return counts[roleID], nil } func (v roleViewer) editors(found []role.Role) (map[uint32]string, error) { ids := []uint32{} for _, item := range found { if item.UpdatedBy != nil { ids = append(ids, *item.UpdatedBy) } } names := map[uint32]string{} if len(ids) == 0 { return names, nil } editors, err := v.users.FindByIDs(ids) if err != nil { return nil, err } for _, editor := range editors { names[editor.ID] = editor.NameOrUsername() } return names, nil } func toRoleView(found role.Role, members int, editors map[uint32]string) RoleView { view := RoleView{Role: found, Members: members} view.Grants = found.EffectiveGrants() if !found.UpdatedAt.IsZero() { stamp := found.UpdatedAt.UTC() view.UpdatedAt = &stamp } if found.UpdatedBy != nil { if name, present := editors[*found.UpdatedBy]; present { view.UpdatedBy = &RoleEditor{ID: *found.UpdatedBy, Name: name} } } return view } func memberCounts(roles role.Repository, accounts []user.User) (map[uint32]int, error) { counts := map[uint32]int{} legacyIDs := map[user.Role]uint32{} for _, account := range accounts { id := account.RoleID if id == 0 { resolved, cached := legacyIDs[account.Role] if !cached { found, err := resolveRole(roles, account) if err != nil && !errors.Is(err, role.ErrRoleNotFound) { return nil, err } resolved = found.ID legacyIDs[account.Role] = resolved } id = resolved } if id != 0 { counts[id]++ } } return counts, nil }