migrate from github
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package membership_service
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/repository"
|
||||
"github.com/XingfenD/yoresee_doc/internal/repository/membership_repo"
|
||||
"github.com/XingfenD/yoresee_doc/internal/repository/user_repo"
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MembershipService struct {
|
||||
db *gorm.DB
|
||||
repo *membership_repo.MembershipRepository
|
||||
useRepo *user_repo.UserRepository
|
||||
}
|
||||
|
||||
func NewMembershipService(repos *repository.Repositories) *MembershipService {
|
||||
return &MembershipService{
|
||||
db: repos.DB,
|
||||
repo: repos.Membership,
|
||||
useRepo: repos.User,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MembershipService) GetMembershipIDByExternalID(req *dto.MembershipBaseRequest) (int64, error) {
|
||||
var membershipID int64
|
||||
if req.Type == dto.MembershipType_OrgNode {
|
||||
orgNodeID, err := s.repo.GetOrgNodeIDByExternalID(req.MembershipExternalID).Exec()
|
||||
if err != nil {
|
||||
return 0, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
membershipID = orgNodeID
|
||||
} else if req.Type == dto.MembershipType_UserGroup {
|
||||
userGroupID, err := s.repo.GetUserGroupIDByExternalID(req.MembershipExternalID).Exec()
|
||||
if err != nil {
|
||||
return 0, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
membershipID = userGroupID
|
||||
} else {
|
||||
return 0, status.StatusInvalidMembershipType
|
||||
}
|
||||
return membershipID, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) CreateMembershipRelation(membership *dto.CreateMembershipRelationRequest) error {
|
||||
userID, err := s.useRepo.GetIDByExternalID(membership.UserExternalID).Exec()
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] GetIDByExternalID failed, user_external_id=%s, err=%+v", membership.UserExternalID, err)
|
||||
return status.StatusUserNotFound
|
||||
}
|
||||
|
||||
membershipID, err := s.GetMembershipIDByExternalID(&membership.MembershipBaseRequest)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] GetMembershipIDByExternalID failed, membership_external_id=%s, type=%d, err=%+v", membership.MembershipExternalID, membership.Type, err)
|
||||
return status.GenErrWithCustomMsg(err, "membership target not found")
|
||||
}
|
||||
model := &model.MembershipRelation{
|
||||
UserID: userID,
|
||||
MembershipID: membershipID,
|
||||
Type: model.MembershipType(membership.Type),
|
||||
}
|
||||
|
||||
if err := s.repo.CreateMembership(model).Exec(); err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] CreateMembership failed, user_id=%d, membership_id=%d, type=%d, err=%+v", userID, membershipID, membership.Type, err)
|
||||
return status.GenErrWithCustomMsg(status.StatusWriteDBError, "create membership relation failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) GetUserGroupMeta(externalID string) (*model.UserGroupMeta, error) {
|
||||
meta, err := s.repo.GetUserGroupByExternalID(externalID).Exec()
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] GetUserGroupMeta failed, external_id=%s, err=%+v", externalID, err)
|
||||
return nil, status.GenErrWithCustomMsg(status.StatusMembershipMetaNotFound, "user group not found")
|
||||
}
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) GetOrgNodeMeta(externalID string) (*model.OrgNodeMeta, error) {
|
||||
meta, err := s.repo.GetOrgNodeByExternalID(externalID).Exec()
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] GetOrgNodeMeta failed, external_id=%s, err=%+v", externalID, err)
|
||||
return nil, status.GenErrWithCustomMsg(status.StatusMembershipMetaNotFound, "org node not found")
|
||||
}
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// func (s *MembershipService) GetMembershipRelation(req *dto.MembershipBaseRequest) (*dto.MembershipRelationResponse, error) {
|
||||
// var membershipMetaResponse *dto.MembershipMetaResponse
|
||||
// var membershipID int64
|
||||
// switch req.Type {
|
||||
// case model.MembershipType_UserGroup:
|
||||
// userGroupMeta, err := s.GetUserGroupMeta(req.MembershipExternalID)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// membershipMetaResponse = dto.NewMembershipMetaResponseFromUserGroupMetaModel(userGroupMeta)
|
||||
// membershipID = userGroupMeta.ID
|
||||
// case model.MembershipType_OrgNode:
|
||||
// orgNodeMeta, err := s.GetOrgNodeMeta(req.MembershipExternalID)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// membershipMetaResponse = dto.NewMembershipMetaResponseFromOrgNodeMetaModel(orgNodeMeta)
|
||||
// membershipID = orgNodeMeta.ID
|
||||
// default:
|
||||
// return nil, status.StatusInvalidMembershipType
|
||||
// }
|
||||
// model := &model.MembershipRelation{
|
||||
// Type: req.Type,
|
||||
// MembershipID: membershipID,
|
||||
// }
|
||||
// memberships, err := s.repo.ListMembership(model).Exec()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
|
||||
var MembershipSvc *MembershipService
|
||||
@@ -0,0 +1,473 @@
|
||||
package membership_service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/bytedance/gg/gslice"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (s *MembershipService) ListOrgNodes(req *dto.ListOrgNodesRequest) ([]*dto.OrgNodeResponse, int64, error) {
|
||||
if req == nil {
|
||||
return nil, 0, status.StatusParamError
|
||||
}
|
||||
req.Pagination = normalizePagination(req.Pagination)
|
||||
|
||||
var parentID int64 = 0
|
||||
if req.ParentExternalID != "" {
|
||||
parent, err := s.repo.GetOrgNodeByExternalID(req.ParentExternalID).Exec()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
parentID = parent.ID
|
||||
}
|
||||
|
||||
nodes, total, err := s.repo.QueryOrgNode().
|
||||
WithParentID(parentID).
|
||||
WithKeyword(req.Keyword).
|
||||
WithPagination(req.Pagination.Page, req.Pagination.PageSize).
|
||||
ExecWithTotal()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusReadDBError
|
||||
}
|
||||
|
||||
responses, err := s.buildOrgNodeResponses(nodes, req.IncludeChildren)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] buildOrgNodeResponses failed, err=%+v", err)
|
||||
return nil, 0, status.GenErrWithCustomMsg(err, "build org node response failed")
|
||||
}
|
||||
return responses, total, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) GetOrgNode(req *dto.GetOrgNodeRequest) (*dto.OrgNodeResponse, error) {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return nil, status.StatusParamError
|
||||
}
|
||||
node, err := s.repo.GetOrgNodeByExternalID(req.ExternalID).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
if req.IncludeChildren {
|
||||
return s.buildOrgNodeWithChildren(node)
|
||||
}
|
||||
|
||||
responses, err := s.buildOrgNodeResponses([]model.OrgNodeMeta{*node}, false)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] buildOrgNodeResponses failed, external_id=%s, err=%+v", req.ExternalID, err)
|
||||
return nil, status.GenErrWithCustomMsg(err, "build org node response failed")
|
||||
}
|
||||
if len(responses) == 0 {
|
||||
return nil, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
return responses[0], nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) CreateOrgNode(req *dto.CreateOrgNodeRequest) (string, error) {
|
||||
if req == nil || strings.TrimSpace(req.Name) == "" || strings.TrimSpace(req.CreatorUserExternalID) == "" {
|
||||
return "", status.StatusParamError
|
||||
}
|
||||
|
||||
var createdExternalID string
|
||||
err := utils.WithTransaction(s.db, func(tx *gorm.DB) error {
|
||||
creatorID, err := s.useRepo.GetIDByExternalID(req.CreatorUserExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusUserNotFound
|
||||
}
|
||||
|
||||
var parentID int64 = 0
|
||||
var parentPath string = ""
|
||||
if req.ParentExternalID != "" {
|
||||
parent, err := s.repo.GetOrgNodeByExternalID(req.ParentExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusMembershipMetaNotFound
|
||||
}
|
||||
parentID = parent.ID
|
||||
parentPath = parent.Path
|
||||
}
|
||||
|
||||
node := &model.OrgNodeMeta{
|
||||
ExternalID: utils.GenerateExternalID(utils.ExternalIDContextOrgNode),
|
||||
ParentID: parentID,
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
CreatorID: creatorID,
|
||||
}
|
||||
|
||||
if err := s.repo.CreateOrgNode(node).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
|
||||
node.Path = buildOrgNodePath(parentPath, node.ID)
|
||||
if err := s.repo.UpdateOrgNode(node).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
|
||||
userIDs, err := s.resolveUserExternalIDsToIDs(req.MemberUserExternalIDs, tx)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] resolveUserExternalIDsToIDs failed, err=%+v", err)
|
||||
return status.GenErrWithCustomMsg(err, "resolve user ids failed")
|
||||
}
|
||||
if err := s.syncOrgNodeMembers(tx, node.ID, userIDs); err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] syncOrgNodeMembers failed, node_id=%d, err=%+v", node.ID, err)
|
||||
return status.GenErrWithCustomMsg(err, "sync org node members failed")
|
||||
}
|
||||
|
||||
createdExternalID = node.ExternalID
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] CreateOrgNode transaction failed, err=%+v", err)
|
||||
return "", status.GenErrWithCustomMsg(err, "create org node failed")
|
||||
}
|
||||
return createdExternalID, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) UpdateOrgNode(req *dto.UpdateOrgNodeRequest) error {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
if req.Name == nil && req.Description == nil && !req.SyncMembers {
|
||||
return status.StatusParamError
|
||||
}
|
||||
|
||||
return utils.WithTransaction(s.db, func(tx *gorm.DB) error {
|
||||
node, err := s.repo.GetOrgNodeByExternalID(req.ExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
shouldSave := false
|
||||
if req.Name != nil {
|
||||
newName := strings.TrimSpace(*req.Name)
|
||||
if newName == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
node.Name = newName
|
||||
shouldSave = true
|
||||
}
|
||||
if req.Description != nil {
|
||||
node.Description = strings.TrimSpace(*req.Description)
|
||||
shouldSave = true
|
||||
}
|
||||
if shouldSave {
|
||||
if err := s.repo.UpdateOrgNode(node).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
}
|
||||
|
||||
if req.SyncMembers {
|
||||
userIDs, err := s.resolveUserExternalIDsToIDs(req.MemberUserExternalIDs, tx)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] resolveUserExternalIDsToIDs failed, external_id=%s, err=%+v", req.ExternalID, err)
|
||||
return status.GenErrWithCustomMsg(err, "resolve user ids failed")
|
||||
}
|
||||
if err := s.syncOrgNodeMembers(tx, node.ID, userIDs); err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] syncOrgNodeMembers failed, node_id=%d, err=%+v", node.ID, err)
|
||||
return status.GenErrWithCustomMsg(err, "sync org node members failed")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MembershipService) DeleteOrgNode(req *dto.DeleteOrgNodeRequest) error {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
|
||||
return utils.WithTransaction(s.db, func(tx *gorm.DB) error {
|
||||
node, err := s.repo.GetOrgNodeByExternalID(req.ExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
children, err := s.repo.QueryOrgNode().WithParentID(node.ID).Exec()
|
||||
if err != nil {
|
||||
return status.StatusReadDBError
|
||||
}
|
||||
if len(children) > 0 {
|
||||
return status.StatusOrgNodeHasChildren
|
||||
}
|
||||
|
||||
if err := tx.Where("type = ? AND membership_id = ?", model.MembershipType_OrgNode, node.ID).
|
||||
Delete(&model.MembershipRelation{}).Error; err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
if err := s.repo.DeleteOrgNode(node).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MembershipService) MoveOrgNode(req *dto.MoveOrgNodeRequest) error {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
|
||||
return utils.WithTransaction(s.db, func(tx *gorm.DB) error {
|
||||
node, err := s.repo.GetOrgNodeByExternalID(req.ExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
var newParentID int64 = 0
|
||||
var newParentPath string = ""
|
||||
if req.NewParentExternalID != "" {
|
||||
newParent, err := s.repo.GetOrgNodeByExternalID(req.NewParentExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusMembershipMetaNotFound
|
||||
}
|
||||
newParentID = newParent.ID
|
||||
newParentPath = newParent.Path
|
||||
|
||||
if newParentID == node.ID {
|
||||
return status.StatusParamError
|
||||
}
|
||||
|
||||
descendants, err := s.repo.QueryOrgNodeByPathPrefix(node.Path).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusReadDBError
|
||||
}
|
||||
for _, descendant := range descendants {
|
||||
if descendant.ID == newParentID {
|
||||
return status.StatusOrgNodeCannotMoveToDescendant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.ParentID = newParentID
|
||||
oldPath := node.Path
|
||||
node.Path = buildOrgNodePath(newParentPath, node.ID)
|
||||
|
||||
if err := s.repo.UpdateOrgNode(node).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
|
||||
descendants, err := s.repo.QueryOrgNodeByPathPrefix(oldPath).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusReadDBError
|
||||
}
|
||||
for _, descendant := range descendants {
|
||||
if descendant.ID == node.ID {
|
||||
continue
|
||||
}
|
||||
descendant.Path = strings.Replace(descendant.Path, oldPath, node.Path, 1)
|
||||
if err := s.repo.UpdateOrgNode(&descendant).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MembershipService) ListOrgNodeMembers(req *dto.ListOrgNodeMembersRequest) ([]*dto.UserResponse, int64, error) {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return nil, 0, status.StatusParamError
|
||||
}
|
||||
req.Pagination = normalizePagination(req.Pagination)
|
||||
|
||||
node, err := s.repo.GetOrgNodeByExternalID(req.ExternalID).Exec()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
relations, err := s.repo.ListMembership(&model.MembershipRelation{
|
||||
Type: model.MembershipType_OrgNode,
|
||||
MembershipID: node.ID,
|
||||
}).Exec()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusReadDBError
|
||||
}
|
||||
if len(relations) == 0 {
|
||||
return []*dto.UserResponse{}, 0, nil
|
||||
}
|
||||
|
||||
memberIDs := make([]int64, 0, len(relations))
|
||||
for _, relation := range relations {
|
||||
memberIDs = append(memberIDs, relation.UserID)
|
||||
}
|
||||
|
||||
query := s.useRepo.QueryUsers().
|
||||
WithPagination(req.Pagination.Page, req.Pagination.PageSize)
|
||||
if req.Keyword != nil && strings.TrimSpace(*req.Keyword) != "" {
|
||||
query = query.WithKeyword(req.Keyword)
|
||||
}
|
||||
query = query.WithUserIDs(memberIDs)
|
||||
|
||||
users, total, err := query.ExecWithTotal()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusReadDBError
|
||||
}
|
||||
|
||||
resp := make([]*dto.UserResponse, 0, len(users))
|
||||
for i := range users {
|
||||
user := users[i]
|
||||
resp = append(resp, dto.NewUserResponseFromModel(&user))
|
||||
}
|
||||
|
||||
return resp, total, nil
|
||||
}
|
||||
|
||||
func buildOrgNodePath(parentPath string, nodeID int64) string {
|
||||
label := "n" + utils.Int64ToString(nodeID)
|
||||
if strings.TrimSpace(parentPath) == "" {
|
||||
return label
|
||||
}
|
||||
return parentPath + "." + label
|
||||
}
|
||||
|
||||
func (s *MembershipService) buildOrgNodeResponses(nodes []model.OrgNodeMeta, includeChildren bool) ([]*dto.OrgNodeResponse, error) {
|
||||
if len(nodes) == 0 {
|
||||
return []*dto.OrgNodeResponse{}, nil
|
||||
}
|
||||
|
||||
nodeIDs := make([]int64, 0, len(nodes))
|
||||
parentIDs := make([]int64, 0, len(nodes))
|
||||
creatorIDs := make([]int64, 0, len(nodes))
|
||||
for _, n := range nodes {
|
||||
nodeIDs = append(nodeIDs, n.ID)
|
||||
parentIDs = append(parentIDs, n.ParentID)
|
||||
creatorIDs = append(creatorIDs, n.CreatorID)
|
||||
}
|
||||
parentIDs = gslice.Uniq(parentIDs)
|
||||
creatorIDs = gslice.Uniq(creatorIDs)
|
||||
|
||||
relations, err := s.repo.ListMembershipByTypeAndIDs(model.MembershipType_OrgNode, nodeIDs).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusReadDBError
|
||||
}
|
||||
|
||||
parentMap, err := s.repo.MGetOrgNodeByID(parentIDs).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusReadDBError
|
||||
}
|
||||
|
||||
creatorMap, err := s.useRepo.MGetUserByID(creatorIDs).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusReadDBError
|
||||
}
|
||||
|
||||
nodeMemberUserIDs := make(map[int64][]int64, len(nodeIDs))
|
||||
nodeMemberSeen := make(map[int64]map[int64]struct{}, len(nodeIDs))
|
||||
for _, relation := range relations {
|
||||
if _, ok := nodeMemberSeen[relation.MembershipID]; !ok {
|
||||
nodeMemberSeen[relation.MembershipID] = map[int64]struct{}{}
|
||||
}
|
||||
if _, exists := nodeMemberSeen[relation.MembershipID][relation.UserID]; exists {
|
||||
continue
|
||||
}
|
||||
nodeMemberSeen[relation.MembershipID][relation.UserID] = struct{}{}
|
||||
|
||||
nodeMemberUserIDs[relation.MembershipID] = append(nodeMemberUserIDs[relation.MembershipID], relation.UserID)
|
||||
}
|
||||
|
||||
responses := make([]*dto.OrgNodeResponse, 0, len(nodes))
|
||||
for _, n := range nodes {
|
||||
resp := &dto.OrgNodeResponse{
|
||||
ExternalID: n.ExternalID,
|
||||
Name: n.Name,
|
||||
Path: n.Path,
|
||||
Description: n.Description,
|
||||
MemberCount: len(nodeMemberUserIDs[n.ID]),
|
||||
}
|
||||
if parent := parentMap[n.ParentID]; parent != nil {
|
||||
resp.ParentExternalID = parent.ExternalID
|
||||
}
|
||||
if creator := creatorMap[n.CreatorID]; creator != nil {
|
||||
resp.CreatorUserExternalID = creator.ExternalID
|
||||
}
|
||||
responses = append(responses, resp)
|
||||
}
|
||||
|
||||
if includeChildren {
|
||||
for i, resp := range responses {
|
||||
node := nodes[i]
|
||||
children, err := s.repo.QueryOrgNode().WithParentID(node.ID).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusReadDBError
|
||||
}
|
||||
if len(children) > 0 {
|
||||
childResponses, err := s.buildOrgNodeResponses(children, true)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] buildOrgNodeResponses children failed, node_id=%d, err=%+v", node.ID, err)
|
||||
return nil, status.GenErrWithCustomMsg(err, "build org node children response failed")
|
||||
}
|
||||
resp.Children = childResponses
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) buildOrgNodeWithChildren(node *model.OrgNodeMeta) (*dto.OrgNodeResponse, error) {
|
||||
responses, err := s.buildOrgNodeResponses([]model.OrgNodeMeta{*node}, true)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] buildOrgNodeResponses tree failed, node_external_id=%s, err=%+v", node.ExternalID, err)
|
||||
return nil, status.GenErrWithCustomMsg(err, "build org node tree failed")
|
||||
}
|
||||
if len(responses) == 0 {
|
||||
return nil, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
return responses[0], nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) syncOrgNodeMembers(tx *gorm.DB, orgNodeID int64, targetUserIDs []int64) error {
|
||||
existingRelations, err := s.repo.ListMembership(&model.MembershipRelation{
|
||||
Type: model.MembershipType_OrgNode,
|
||||
MembershipID: orgNodeID,
|
||||
}).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusReadDBError
|
||||
}
|
||||
|
||||
existingUserIDs := map[int64]struct{}{}
|
||||
for _, relation := range existingRelations {
|
||||
existingUserIDs[relation.UserID] = struct{}{}
|
||||
}
|
||||
targetUserIDSet := map[int64]struct{}{}
|
||||
for _, userID := range targetUserIDs {
|
||||
targetUserIDSet[userID] = struct{}{}
|
||||
}
|
||||
|
||||
toCreate := make([]*model.MembershipRelation, 0)
|
||||
for userID := range targetUserIDSet {
|
||||
if _, ok := existingUserIDs[userID]; ok {
|
||||
continue
|
||||
}
|
||||
toCreate = append(toCreate, &model.MembershipRelation{
|
||||
Type: model.MembershipType_OrgNode,
|
||||
UserID: userID,
|
||||
MembershipID: orgNodeID,
|
||||
})
|
||||
}
|
||||
|
||||
toDeleteUserIDs := make([]int64, 0)
|
||||
for userID := range existingUserIDs {
|
||||
if _, ok := targetUserIDSet[userID]; ok {
|
||||
continue
|
||||
}
|
||||
toDeleteUserIDs = append(toDeleteUserIDs, userID)
|
||||
}
|
||||
|
||||
if len(toCreate) > 0 {
|
||||
if err := s.repo.BatchCreateMembership(toCreate).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
}
|
||||
if len(toDeleteUserIDs) > 0 {
|
||||
if err := tx.Where("type = ? AND membership_id = ? AND user_id IN ?", model.MembershipType_OrgNode, orgNodeID, toDeleteUserIDs).
|
||||
Delete(&model.MembershipRelation{}).Error; err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
package membership_service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/auth"
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/bytedance/gg/gslice"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPage = 1
|
||||
defaultPageSize = 20
|
||||
maxPageSize = 200
|
||||
)
|
||||
|
||||
func normalizePagination(p dto.Pagination) dto.Pagination {
|
||||
if p.Page <= 0 {
|
||||
p.Page = defaultPage
|
||||
}
|
||||
if p.PageSize <= 0 {
|
||||
p.PageSize = defaultPageSize
|
||||
}
|
||||
if p.PageSize > maxPageSize {
|
||||
p.PageSize = maxPageSize
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *MembershipService) ListUsers(req *dto.ListUsersRequest) ([]*dto.UserResponse, int64, error) {
|
||||
if req == nil {
|
||||
return nil, 0, status.StatusParamError
|
||||
}
|
||||
req.Pagination = normalizePagination(req.Pagination)
|
||||
|
||||
users, total, err := s.useRepo.QueryUsers().
|
||||
WithKeyword(req.Keyword).
|
||||
WithPagination(req.Pagination.Page, req.Pagination.PageSize).
|
||||
ExecWithTotal()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusReadDBError
|
||||
}
|
||||
|
||||
resp := make([]*dto.UserResponse, 0, len(users))
|
||||
for i := range users {
|
||||
user := users[i]
|
||||
resp = append(resp, dto.NewUserResponseFromModel(&user))
|
||||
}
|
||||
return resp, total, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) UpdateUser(req *dto.UpdateUserRequest) error {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
if req.Username == nil && req.Email == nil && req.Nickname == nil && req.Status == nil {
|
||||
return status.StatusParamError
|
||||
}
|
||||
|
||||
user, err := s.useRepo.GetByExternalID(req.ExternalID).Exec()
|
||||
if err != nil {
|
||||
return status.StatusUserNotFound
|
||||
}
|
||||
oldStatus := user.Status
|
||||
|
||||
if req.Username != nil {
|
||||
username := strings.TrimSpace(*req.Username)
|
||||
if username == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
user.Username = username
|
||||
}
|
||||
if req.Email != nil {
|
||||
email := strings.TrimSpace(*req.Email)
|
||||
if email == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
user.Email = email
|
||||
}
|
||||
if req.Nickname != nil {
|
||||
user.Nickname = strings.TrimSpace(*req.Nickname)
|
||||
}
|
||||
if req.Status != nil {
|
||||
user.Status = int(*req.Status)
|
||||
}
|
||||
|
||||
if err := s.useRepo.Update(user).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
if req.Status != nil && oldStatus > 0 && user.Status <= 0 {
|
||||
if err := auth.BlacklistUserJWTs(user.ExternalID); err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] blacklist user jwt tokens failed, user_external_id=%s, err=%+v", user.ExternalID, err)
|
||||
return status.GenErrWithCustomMsg(status.StatusServiceInternalError, "ban user failed: blacklist jwt tokens failed")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) ListUserGroupMembers(req *dto.ListUserGroupMembersRequest) ([]*dto.UserResponse, int64, error) {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return nil, 0, status.StatusParamError
|
||||
}
|
||||
req.Pagination = normalizePagination(req.Pagination)
|
||||
|
||||
group, err := s.repo.GetUserGroupByExternalID(req.ExternalID).Exec()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
relations, err := s.repo.ListMembership(&model.MembershipRelation{
|
||||
Type: model.MembershipType_UserGroup,
|
||||
MembershipID: group.ID,
|
||||
}).Exec()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusReadDBError
|
||||
}
|
||||
if len(relations) == 0 {
|
||||
return []*dto.UserResponse{}, 0, nil
|
||||
}
|
||||
|
||||
memberIDs := make([]int64, 0, len(relations))
|
||||
for _, relation := range relations {
|
||||
memberIDs = append(memberIDs, relation.UserID)
|
||||
}
|
||||
|
||||
query := s.useRepo.QueryUsers().
|
||||
WithPagination(req.Pagination.Page, req.Pagination.PageSize)
|
||||
if req.Keyword != nil && strings.TrimSpace(*req.Keyword) != "" {
|
||||
query = query.WithKeyword(req.Keyword)
|
||||
}
|
||||
query = query.WithUserIDs(memberIDs)
|
||||
|
||||
users, total, err := query.ExecWithTotal()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusReadDBError
|
||||
}
|
||||
|
||||
resp := make([]*dto.UserResponse, 0, len(users))
|
||||
for i := range users {
|
||||
user := users[i]
|
||||
resp = append(resp, dto.NewUserResponseFromModel(&user))
|
||||
}
|
||||
|
||||
return resp, total, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) ListUserGroups(req *dto.ListUserGroupsRequest) ([]*dto.UserGroupResponse, int64, error) {
|
||||
if req == nil {
|
||||
return nil, 0, status.StatusParamError
|
||||
}
|
||||
req.Pagination = normalizePagination(req.Pagination)
|
||||
|
||||
groups, total, err := s.repo.QueryUserGroup().
|
||||
WithKeyword(req.Keyword).
|
||||
WithPagination(req.Pagination.Page, req.Pagination.PageSize).
|
||||
ExecWithTotal()
|
||||
if err != nil {
|
||||
return nil, 0, status.StatusReadDBError
|
||||
}
|
||||
|
||||
responses, err := s.buildUserGroupResponses(groups, false)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] buildUserGroupResponses failed, err=%+v", err)
|
||||
return nil, 0, status.GenErrWithCustomMsg(err, "build user group response failed")
|
||||
}
|
||||
return responses, total, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) GetUserGroup(req *dto.GetUserGroupRequest) (*dto.UserGroupResponse, error) {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return nil, status.StatusParamError
|
||||
}
|
||||
group, err := s.repo.GetUserGroupByExternalID(req.ExternalID).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
responses, err := s.buildUserGroupResponses([]model.UserGroupMeta{*group}, false)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] buildUserGroupResponses failed, external_id=%s, err=%+v", req.ExternalID, err)
|
||||
return nil, status.GenErrWithCustomMsg(err, "build user group response failed")
|
||||
}
|
||||
if len(responses) == 0 {
|
||||
return nil, status.StatusMembershipMetaNotFound
|
||||
}
|
||||
return responses[0], nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) CreateUserGroup(req *dto.CreateUserGroupRequest) (string, error) {
|
||||
if req == nil || strings.TrimSpace(req.Name) == "" || strings.TrimSpace(req.CreatorUserExternalID) == "" {
|
||||
return "", status.StatusParamError
|
||||
}
|
||||
|
||||
var createdExternalID string
|
||||
err := utils.WithTransaction(s.db, func(tx *gorm.DB) error {
|
||||
creatorID, err := s.useRepo.GetIDByExternalID(req.CreatorUserExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusUserNotFound
|
||||
}
|
||||
|
||||
group := &model.UserGroupMeta{
|
||||
ExternalID: utils.GenerateExternalID(utils.ExternalIDContextUserGroup),
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
CreatorID: creatorID,
|
||||
}
|
||||
if err := s.repo.CreateUserGroup(group).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
|
||||
userIDs, err := s.resolveUserExternalIDsToIDs(req.MemberUserExternalIDs, tx)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] resolveUserExternalIDsToIDs failed, err=%+v", err)
|
||||
return status.GenErrWithCustomMsg(err, "resolve user ids failed")
|
||||
}
|
||||
if err := s.syncUserGroupMembers(tx, group.ID, userIDs); err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] syncUserGroupMembers failed, group_id=%d, err=%+v", group.ID, err)
|
||||
return status.GenErrWithCustomMsg(err, "sync user group members failed")
|
||||
}
|
||||
|
||||
createdExternalID = group.ExternalID
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] CreateUserGroup transaction failed, err=%+v", err)
|
||||
return "", status.GenErrWithCustomMsg(err, "create user group failed")
|
||||
}
|
||||
return createdExternalID, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) UpdateUserGroup(req *dto.UpdateUserGroupRequest) error {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
if req.Name == nil && req.Description == nil && !req.SyncMembers {
|
||||
return status.StatusParamError
|
||||
}
|
||||
|
||||
return utils.WithTransaction(s.db, func(tx *gorm.DB) error {
|
||||
group, err := s.repo.GetUserGroupByExternalID(req.ExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
shouldSave := false
|
||||
if req.Name != nil {
|
||||
newName := strings.TrimSpace(*req.Name)
|
||||
if newName == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
group.Name = newName
|
||||
shouldSave = true
|
||||
}
|
||||
if req.Description != nil {
|
||||
group.Description = strings.TrimSpace(*req.Description)
|
||||
shouldSave = true
|
||||
}
|
||||
if shouldSave {
|
||||
if err := s.repo.UpdateUserGroup(group).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
}
|
||||
|
||||
if req.SyncMembers {
|
||||
userIDs, err := s.resolveUserExternalIDsToIDs(req.MemberUserExternalIDs, tx)
|
||||
if err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] resolveUserExternalIDsToIDs failed, external_id=%s, err=%+v", req.ExternalID, err)
|
||||
return status.GenErrWithCustomMsg(err, "resolve user ids failed")
|
||||
}
|
||||
if err := s.syncUserGroupMembers(tx, group.ID, userIDs); err != nil {
|
||||
logrus.Errorf("[Service layer: MembershipService] syncUserGroupMembers failed, group_id=%d, err=%+v", group.ID, err)
|
||||
return status.GenErrWithCustomMsg(err, "sync user group members failed")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MembershipService) DeleteUserGroup(req *dto.DeleteUserGroupRequest) error {
|
||||
if req == nil || strings.TrimSpace(req.ExternalID) == "" {
|
||||
return status.StatusParamError
|
||||
}
|
||||
|
||||
return utils.WithTransaction(s.db, func(tx *gorm.DB) error {
|
||||
group, err := s.repo.GetUserGroupByExternalID(req.ExternalID).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusMembershipMetaNotFound
|
||||
}
|
||||
|
||||
if err := tx.Where("type = ? AND membership_id = ?", model.MembershipType_UserGroup, group.ID).
|
||||
Delete(&model.MembershipRelation{}).Error; err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
if err := s.repo.DeleteUserGroup(group).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MembershipService) buildUserGroupResponses(groups []model.UserGroupMeta, includeMembers bool) ([]*dto.UserGroupResponse, error) {
|
||||
if len(groups) == 0 {
|
||||
return []*dto.UserGroupResponse{}, nil
|
||||
}
|
||||
|
||||
groupIDs := make([]int64, 0, len(groups))
|
||||
creatorIDs := make([]int64, 0, len(groups))
|
||||
for _, g := range groups {
|
||||
groupIDs = append(groupIDs, g.ID)
|
||||
creatorIDs = append(creatorIDs, g.CreatorID)
|
||||
}
|
||||
creatorIDs = gslice.Uniq(creatorIDs)
|
||||
|
||||
relations, err := s.repo.ListMembershipByTypeAndIDs(model.MembershipType_UserGroup, groupIDs).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusReadDBError
|
||||
}
|
||||
|
||||
creatorMap, err := s.useRepo.MGetUserByID(creatorIDs).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusReadDBError
|
||||
}
|
||||
|
||||
groupMemberUserIDs := make(map[int64][]int64, len(groupIDs))
|
||||
groupMemberSeen := make(map[int64]map[int64]struct{}, len(groupIDs))
|
||||
for _, relation := range relations {
|
||||
if _, ok := groupMemberSeen[relation.MembershipID]; !ok {
|
||||
groupMemberSeen[relation.MembershipID] = map[int64]struct{}{}
|
||||
}
|
||||
if _, exists := groupMemberSeen[relation.MembershipID][relation.UserID]; exists {
|
||||
continue
|
||||
}
|
||||
groupMemberSeen[relation.MembershipID][relation.UserID] = struct{}{}
|
||||
|
||||
groupMemberUserIDs[relation.MembershipID] = append(groupMemberUserIDs[relation.MembershipID], relation.UserID)
|
||||
}
|
||||
|
||||
responses := make([]*dto.UserGroupResponse, 0, len(groups))
|
||||
for _, g := range groups {
|
||||
resp := &dto.UserGroupResponse{
|
||||
ExternalID: g.ExternalID,
|
||||
Name: g.Name,
|
||||
Description: g.Description,
|
||||
MemberCount: len(groupMemberUserIDs[g.ID]),
|
||||
}
|
||||
if creator := creatorMap[g.CreatorID]; creator != nil {
|
||||
resp.CreatorUserExternalID = creator.ExternalID
|
||||
}
|
||||
responses = append(responses, resp)
|
||||
}
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) resolveUserExternalIDsToIDs(externalIDs []string, tx *gorm.DB) ([]int64, error) {
|
||||
if len(externalIDs) == 0 {
|
||||
return []int64{}, nil
|
||||
}
|
||||
|
||||
deduped := make([]string, 0, len(externalIDs))
|
||||
seen := map[string]struct{}{}
|
||||
for _, ext := range externalIDs {
|
||||
ext = strings.TrimSpace(ext)
|
||||
if ext == "" {
|
||||
return nil, status.StatusParamError
|
||||
}
|
||||
if _, ok := seen[ext]; ok {
|
||||
continue
|
||||
}
|
||||
seen[ext] = struct{}{}
|
||||
deduped = append(deduped, ext)
|
||||
}
|
||||
|
||||
users, err := s.useRepo.ListByExternal(deduped).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return nil, status.StatusReadDBError
|
||||
}
|
||||
if len(users) != len(deduped) {
|
||||
return nil, status.StatusUserNotFound
|
||||
}
|
||||
|
||||
userIDs := make([]int64, 0, len(users))
|
||||
for _, user := range users {
|
||||
userIDs = append(userIDs, user.ID)
|
||||
}
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
func (s *MembershipService) syncUserGroupMembers(tx *gorm.DB, groupID int64, targetUserIDs []int64) error {
|
||||
existingRelations, err := s.repo.ListMembership(&model.MembershipRelation{
|
||||
Type: model.MembershipType_UserGroup,
|
||||
MembershipID: groupID,
|
||||
}).WithTx(tx).Exec()
|
||||
if err != nil {
|
||||
return status.StatusReadDBError
|
||||
}
|
||||
|
||||
existingUserIDs := map[int64]struct{}{}
|
||||
for _, relation := range existingRelations {
|
||||
existingUserIDs[relation.UserID] = struct{}{}
|
||||
}
|
||||
targetUserIDSet := map[int64]struct{}{}
|
||||
for _, userID := range targetUserIDs {
|
||||
targetUserIDSet[userID] = struct{}{}
|
||||
}
|
||||
|
||||
toCreate := make([]*model.MembershipRelation, 0)
|
||||
for userID := range targetUserIDSet {
|
||||
if _, ok := existingUserIDs[userID]; ok {
|
||||
continue
|
||||
}
|
||||
toCreate = append(toCreate, &model.MembershipRelation{
|
||||
Type: model.MembershipType_UserGroup,
|
||||
UserID: userID,
|
||||
MembershipID: groupID,
|
||||
})
|
||||
}
|
||||
|
||||
toDeleteUserIDs := make([]int64, 0)
|
||||
for userID := range existingUserIDs {
|
||||
if _, ok := targetUserIDSet[userID]; ok {
|
||||
continue
|
||||
}
|
||||
toDeleteUserIDs = append(toDeleteUserIDs, userID)
|
||||
}
|
||||
|
||||
if len(toCreate) > 0 {
|
||||
if err := s.repo.BatchCreateMembership(toCreate).WithTx(tx).Exec(); err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
}
|
||||
if len(toDeleteUserIDs) > 0 {
|
||||
if err := tx.Where("type = ? AND membership_id = ? AND user_id IN ?", model.MembershipType_UserGroup, groupID, toDeleteUserIDs).
|
||||
Delete(&model.MembershipRelation{}).Error; err != nil {
|
||||
return status.StatusWriteDBError
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user