502 lines
22 KiB
Go
502 lines
22 KiB
Go
package host
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/xerr"
|
||
hostdomain "hyapp/services/user-service/internal/domain/host"
|
||
hostservice "hyapp/services/user-service/internal/service/host"
|
||
)
|
||
|
||
// CreateBDLeader 创建后台直属 BD 负责人;admin 鉴权在 admin-server,这里只保证事实一致性。
|
||
func (r *Repository) CreateBDLeader(ctx context.Context, command hostservice.CreateBDLeaderCommand) (hostdomain.BDProfile, error) {
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
} else if ok {
|
||
if err := requireCommandResult(existing, hostdomain.CommandTypeCreateBDLeader, hostdomain.ResultTypeBDProfile); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
return queryBDLeaderProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID)
|
||
}
|
||
|
||
regionID, err := r.userRegion(ctx, tx, command.TargetUserID, "FOR UPDATE")
|
||
if err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
if err := requireActiveRegion(ctx, tx, regionID); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
if _, ok, err := queryBDLeaderProfileByUserMaybe(ctx, tx, command.TargetUserID, true); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
} else if ok {
|
||
return hostdomain.BDProfile{}, xerr.New(xerr.Conflict, "target already has bd leader profile")
|
||
}
|
||
|
||
profile := hostdomain.BDProfile{
|
||
UserID: command.TargetUserID,
|
||
Role: hostdomain.BDRoleLeader,
|
||
RegionID: regionID,
|
||
Status: hostdomain.BDStatusActive,
|
||
CreatedByUserID: command.AdminUserID,
|
||
CreatedAtMs: command.NowMs,
|
||
UpdatedAtMs: command.NowMs,
|
||
}
|
||
if err := insertBDLeaderProfile(ctx, tx, profile); err != nil {
|
||
return hostdomain.BDProfile{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeCreateBDLeader, ResultType: hostdomain.ResultTypeBDProfile, ResultID: profile.UserID, CreatedAtMs: command.NowMs}); err != nil {
|
||
return hostdomain.BDProfile{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, command.EventID, "BDCreated", "bd_profile", profile.UserID, command.NowMs); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
return profile, nil
|
||
}
|
||
|
||
// CreateBD 创建普通 BD;父级 Leader 可选,填入时必须是同区域有效 Leader。
|
||
func (r *Repository) CreateBD(ctx context.Context, command hostservice.CreateBDCommand) (hostdomain.BDProfile, error) {
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
} else if ok {
|
||
if err := requireCommandResult(existing, hostdomain.CommandTypeCreateBD, hostdomain.ResultTypeBDProfile); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
return queryBDProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID)
|
||
}
|
||
|
||
regionID, err := r.userRegion(ctx, tx, command.TargetUserID, "FOR UPDATE")
|
||
if err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
if command.ParentLeaderUserID > 0 {
|
||
// 独立 BD 不需要父级行锁;只有传入 Leader 时才锁定独立 Leader 表并校验启用状态和区域归属。
|
||
leader, err := queryBDLeaderProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.ParentLeaderUserID)
|
||
if err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
if leader.Status != hostdomain.BDStatusActive {
|
||
return hostdomain.BDProfile{}, xerr.New(xerr.PermissionDenied, "parent leader is not active")
|
||
}
|
||
if regionID != leader.RegionID {
|
||
return hostdomain.BDProfile{}, xerr.New(xerr.PermissionDenied, "target region does not match leader region")
|
||
}
|
||
}
|
||
if _, ok, err := queryBDProfileByUserMaybe(ctx, tx, command.TargetUserID, true); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
} else if ok {
|
||
return hostdomain.BDProfile{}, xerr.New(xerr.Conflict, "target already has bd profile")
|
||
}
|
||
|
||
profile := hostdomain.BDProfile{
|
||
UserID: command.TargetUserID,
|
||
Role: hostdomain.BDRoleBD,
|
||
RegionID: regionID,
|
||
ParentLeaderUserID: command.ParentLeaderUserID,
|
||
Status: hostdomain.BDStatusActive,
|
||
CreatedByUserID: command.AdminUserID,
|
||
CreatedAtMs: command.NowMs,
|
||
UpdatedAtMs: command.NowMs,
|
||
}
|
||
if err := insertBDProfile(ctx, tx, profile); err != nil {
|
||
return hostdomain.BDProfile{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeCreateBD, ResultType: hostdomain.ResultTypeBDProfile, ResultID: profile.UserID, CreatedAtMs: command.NowMs}); err != nil {
|
||
return hostdomain.BDProfile{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, command.EventID, "BDCreated", "bd_profile", profile.UserID, command.NowMs); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
return profile, nil
|
||
}
|
||
|
||
// SetBDStatus 按调用方明确传入的角色只改对应身份状态,不迁移角色、区域和上级负责人。
|
||
func (r *Repository) SetBDStatus(ctx context.Context, command hostservice.SetBDStatusCommand) (hostdomain.BDProfile, error) {
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
} else if ok {
|
||
if err := requireCommandResult(existing, hostdomain.CommandTypeSetBDStatus, hostdomain.ResultTypeBDProfile); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
if command.Role == hostdomain.BDRoleLeader {
|
||
return queryBDLeaderProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID)
|
||
}
|
||
return queryBDProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID)
|
||
}
|
||
|
||
var before hostdomain.BDProfile
|
||
switch command.Role {
|
||
case hostdomain.BDRoleBD:
|
||
// 普通 BD 和 Leader 已经拆表;状态路由传 bd 时只锁普通 BD 行。
|
||
before, err = queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.TargetUserID)
|
||
case hostdomain.BDRoleLeader:
|
||
// 状态路由传 bd_leader 时只锁 Leader 行,避免同用户的普通 BD 钱包权限被误停。
|
||
before, err = queryBDLeaderProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.TargetUserID)
|
||
default:
|
||
err = xerr.New(xerr.InvalidArgument, "bd role is invalid")
|
||
}
|
||
if err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
after := before
|
||
after.Status = command.Status
|
||
after.UpdatedAtMs = command.NowMs
|
||
if command.Role == hostdomain.BDRoleLeader {
|
||
if err := updateBDLeaderProfileStatus(ctx, tx, after); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
} else {
|
||
if err := updateBDProfileStatus(ctx, tx, after); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
}
|
||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeSetBDStatus, ResultType: hostdomain.ResultTypeBDProfile, ResultID: after.UserID, CreatedAtMs: command.NowMs}); err != nil {
|
||
return hostdomain.BDProfile{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, command.EventID, "BDStatusChanged", "bd_profile", after.UserID, command.NowMs); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return hostdomain.BDProfile{}, err
|
||
}
|
||
return after, nil
|
||
}
|
||
|
||
// CreateCoinSeller 创建币商身份;币商可以叠加在 Agency/BD/BD Leader 用户上。
|
||
func (r *Repository) CreateCoinSeller(ctx context.Context, command hostservice.CreateCoinSellerCommand) (hostdomain.CoinSellerProfile, error) {
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
} else if ok {
|
||
if err := requireCommandResult(existing, hostdomain.CommandTypeCreateCoinSeller, hostdomain.ResultTypeCoinSellerProfile); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
return queryCoinSellerProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID)
|
||
}
|
||
|
||
if _, err := r.userRegion(ctx, tx, command.TargetUserID, "FOR UPDATE"); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
if _, ok, err := queryCoinSellerProfileByUserMaybe(ctx, tx, command.TargetUserID, true); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
} else if ok {
|
||
return hostdomain.CoinSellerProfile{}, xerr.New(xerr.Conflict, "target already has coin_seller profile")
|
||
}
|
||
|
||
profile := hostdomain.CoinSellerProfile{
|
||
UserID: command.TargetUserID,
|
||
Status: hostdomain.CoinSellerStatusActive,
|
||
MerchantAssetType: hostdomain.CoinSellerMerchantAssetType,
|
||
CreatedByUserID: command.AdminUserID,
|
||
CreatedAtMs: command.NowMs,
|
||
UpdatedAtMs: command.NowMs,
|
||
}
|
||
if err := insertCoinSellerProfile(ctx, tx, profile); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeCreateCoinSeller, ResultType: hostdomain.ResultTypeCoinSellerProfile, ResultID: profile.UserID, CreatedAtMs: command.NowMs}); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, command.EventID, "CoinSellerCreated", "coin_seller_profile", profile.UserID, command.NowMs); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
return profile, nil
|
||
}
|
||
|
||
// SetCoinSellerStatus 启用或停用币商身份,不改币商钱包余额。
|
||
func (r *Repository) SetCoinSellerStatus(ctx context.Context, command hostservice.SetCoinSellerStatusCommand) (hostdomain.CoinSellerProfile, error) {
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
} else if ok {
|
||
if err := requireCommandResult(existing, hostdomain.CommandTypeSetCoinSellerStatus, hostdomain.ResultTypeCoinSellerProfile); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
return queryCoinSellerProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID)
|
||
}
|
||
|
||
before, err := queryCoinSellerProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.TargetUserID)
|
||
if err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
after := before
|
||
after.Status = command.Status
|
||
after.UpdatedAtMs = command.NowMs
|
||
if err := updateCoinSellerProfileStatus(ctx, tx, after); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeSetCoinSellerStatus, ResultType: hostdomain.ResultTypeCoinSellerProfile, ResultID: after.UserID, CreatedAtMs: command.NowMs}); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, command.EventID, "CoinSellerStatusChanged", "coin_seller_profile", after.UserID, command.NowMs); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return hostdomain.CoinSellerProfile{}, err
|
||
}
|
||
return after, nil
|
||
}
|
||
|
||
// CreateAgency 后台直建 Agency;父级 BD 可选,填入时必须是同区域有效 BD。
|
||
func (r *Repository) CreateAgency(ctx context.Context, command hostservice.CreateAgencyCommand) (hostdomain.CreateAgencyResult, error) {
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
} else if ok {
|
||
if err := requireCommandResult(existing, hostdomain.CommandTypeCreateAgency, hostdomain.ResultTypeAgency); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
return createAgencyResultByAgencyID(ctx, tx, existing.ResultID)
|
||
}
|
||
|
||
regionID, err := r.userRegion(ctx, tx, command.OwnerUserID, "FOR UPDATE")
|
||
if err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
if command.ParentBDUserID > 0 {
|
||
// 独立 Agency 不需要父级行锁;传入父级时可挂普通 BD 或 Leader 自身,但必须是有效身份且同区域。
|
||
parentBD, err := queryActiveAgencyInviterProfile(ctx, tx, command.ParentBDUserID)
|
||
if err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
if regionID != parentBD.RegionID {
|
||
return hostdomain.CreateAgencyResult{}, xerr.New(xerr.PermissionDenied, "owner region does not match parent bd region")
|
||
}
|
||
}
|
||
if _, ok, err := queryActiveAgencyByOwner(ctx, tx, command.OwnerUserID, true); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
} else if ok {
|
||
return hostdomain.CreateAgencyResult{}, xerr.New(xerr.Conflict, "owner already has active agency")
|
||
}
|
||
if _, ok, err := queryActiveMembershipByHost(ctx, tx, command.OwnerUserID, true); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
} else if ok {
|
||
return hostdomain.CreateAgencyResult{}, xerr.New(xerr.Conflict, "owner already has active agency membership")
|
||
}
|
||
|
||
// max_hosts 是历史兼容字段,当前业务不再限制主播数量,新建 Agency 固化为 0。
|
||
agency := hostdomain.Agency{AgencyID: command.AgencyID, OwnerUserID: command.OwnerUserID, RegionID: regionID, ParentBDUserID: command.ParentBDUserID, Name: command.Name, Status: hostdomain.AgencyStatusActive, JoinEnabled: command.JoinEnabled, MaxHosts: 0, CreatedByUserID: command.AdminUserID, CreatedAtMs: command.NowMs, UpdatedAtMs: command.NowMs}
|
||
if err := insertAgency(ctx, tx, agency); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, mapHostDuplicateError(err)
|
||
}
|
||
hostProfile, created, err := ensureHostProfileForMembership(ctx, tx, command.OwnerUserID, regionID, command.MembershipID, agency.AgencyID, hostdomain.HostSourceAdminCreateAgency, command.NowMs)
|
||
if err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
membership := hostdomain.AgencyMembership{MembershipID: command.MembershipID, AgencyID: agency.AgencyID, HostUserID: command.OwnerUserID, RegionID: regionID, MembershipType: hostdomain.MembershipTypeOwner, Status: hostdomain.MembershipStatusActive, JoinedAtMs: command.NowMs, CreatedAtMs: command.NowMs, UpdatedAtMs: command.NowMs}
|
||
if err := insertAgencyMembership(ctx, tx, membership); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeCreateAgency, ResultType: hostdomain.ResultTypeAgency, ResultID: agency.AgencyID, CreatedAtMs: command.NowMs}); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, mapHostDuplicateError(err)
|
||
}
|
||
if created {
|
||
if err := insertHostOutbox(ctx, tx, command.EventID, "HostCreated", "host_profile", hostProfile.UserID, command.NowMs); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, command.EventID+1, "AgencyCreated", "agency", agency.AgencyID, command.NowMs); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, command.EventID+2, "AgencyMembershipChanged", "agency_membership", membership.MembershipID, command.NowMs); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
return hostdomain.CreateAgencyResult{Agency: agency, HostProfile: hostProfile, Membership: membership}, nil
|
||
}
|
||
|
||
// CloseAgency 关闭 Agency 本体和入会开关,不回写历史 membership。
|
||
func (r *Repository) CloseAgency(ctx context.Context, command hostservice.CloseAgencyCommand) (hostdomain.Agency, error) {
|
||
return r.updateAgencyAdmin(ctx, command.CommandID, hostdomain.CommandTypeCloseAgency, command.AgencyID, command.EventID, command.NowMs, func(agency hostdomain.Agency) hostdomain.Agency {
|
||
agency.Status = hostdomain.AgencyStatusClosed
|
||
agency.JoinEnabled = false
|
||
return agency
|
||
})
|
||
}
|
||
|
||
// DeleteAgency 删除 Agency 身份;历史 Agency、membership 和 host_profile 都保留,只解除当前有效归属。
|
||
func (r *Repository) DeleteAgency(ctx context.Context, command hostservice.DeleteAgencyCommand) (hostdomain.Agency, error) {
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
if existing, ok, err := commandResult(ctx, tx, command.CommandID); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
} else if ok {
|
||
if err := requireCommandResult(existing, hostdomain.CommandTypeDeleteAgency, hostdomain.ResultTypeAgency); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
return queryAgency(ctx, tx, "WHERE agency_id = ?", existing.ResultID)
|
||
}
|
||
|
||
agency, err := queryAgency(ctx, tx, "WHERE agency_id = ? FOR UPDATE", command.AgencyID)
|
||
if err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
if agency.Status == hostdomain.AgencyStatusDeleted {
|
||
return hostdomain.Agency{}, xerr.New(xerr.Conflict, "agency already deleted")
|
||
}
|
||
|
||
activeMemberships, err := queryAgencyMemberships(ctx, tx, fmt.Sprintf(`
|
||
SELECT %s
|
||
FROM agency_memberships
|
||
WHERE app_code = ? AND agency_id = ? AND status = ?
|
||
FOR UPDATE
|
||
`, agencyMembershipColumns), appcode.FromContext(ctx), agency.AgencyID, hostdomain.MembershipStatusActive)
|
||
if err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
|
||
endReason := command.Reason
|
||
if endReason == "" {
|
||
endReason = "delete agency"
|
||
}
|
||
for _, membership := range activeMemberships {
|
||
hostProfile, err := queryHostProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", membership.HostUserID)
|
||
if err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
membership.Status = hostdomain.MembershipStatusEnded
|
||
membership.EndedAtMs = command.NowMs
|
||
membership.EndedByUserID = command.AdminUserID
|
||
membership.EndedReason = endReason
|
||
membership.UpdatedAtMs = command.NowMs
|
||
if err := endAgencyMembership(ctx, tx, membership); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
if hostProfile.CurrentMembershipID == membership.MembershipID {
|
||
// 删除 Agency 只释放当前归属指针,主播身份、来源和首次成为主播时间都保留。
|
||
hostProfile.CurrentAgencyID = 0
|
||
hostProfile.CurrentMembershipID = 0
|
||
hostProfile.UpdatedAtMs = command.NowMs
|
||
if err := updateHostCurrentMembership(ctx, tx, hostProfile); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
}
|
||
}
|
||
|
||
agency.Status = hostdomain.AgencyStatusDeleted
|
||
agency.JoinEnabled = false
|
||
agency.UpdatedAtMs = command.NowMs
|
||
if err := updateAgencyAdminState(ctx, tx, agency); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeDeleteAgency, ResultType: hostdomain.ResultTypeAgency, ResultID: agency.AgencyID, CreatedAtMs: command.NowMs}); err != nil {
|
||
return hostdomain.Agency{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, command.EventID, "AgencyDeleted", "agency", agency.AgencyID, command.NowMs); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
return agency, nil
|
||
}
|
||
|
||
// SetAgencyJoinEnabled 调整 Agency 入会开关。
|
||
func (r *Repository) SetAgencyJoinEnabled(ctx context.Context, command hostservice.SetAgencyJoinEnabledCommand) (hostdomain.Agency, error) {
|
||
return r.updateAgencyAdmin(ctx, command.CommandID, hostdomain.CommandTypeSetAgencyJoinEnabled, command.AgencyID, command.EventID, command.NowMs, func(agency hostdomain.Agency) hostdomain.Agency {
|
||
agency.JoinEnabled = command.JoinEnabled
|
||
return agency
|
||
})
|
||
}
|
||
|
||
func (r *Repository) updateAgencyAdmin(ctx context.Context, commandID string, commandType string, agencyID int64, eventID int64, nowMs int64, mutate func(hostdomain.Agency) hostdomain.Agency) (hostdomain.Agency, error) {
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
if existing, ok, err := commandResult(ctx, tx, commandID); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
} else if ok {
|
||
if err := requireCommandResult(existing, commandType, hostdomain.ResultTypeAgency); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
return queryAgency(ctx, tx, "WHERE agency_id = ?", existing.ResultID)
|
||
}
|
||
before, err := queryAgency(ctx, tx, "WHERE agency_id = ? FOR UPDATE", agencyID)
|
||
if err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
after := mutate(before)
|
||
after.UpdatedAtMs = nowMs
|
||
if err := updateAgencyAdminState(ctx, tx, after); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: commandID, CommandType: commandType, ResultType: hostdomain.ResultTypeAgency, ResultID: after.AgencyID, CreatedAtMs: nowMs}); err != nil {
|
||
return hostdomain.Agency{}, mapHostDuplicateError(err)
|
||
}
|
||
if err := insertHostOutbox(ctx, tx, eventID, "AgencyChanged", "agency", after.AgencyID, nowMs); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return hostdomain.Agency{}, err
|
||
}
|
||
return after, nil
|
||
}
|
||
|
||
func createAgencyResultByAgencyID(ctx context.Context, q sqlQueryer, agencyID int64) (hostdomain.CreateAgencyResult, error) {
|
||
agency, err := queryAgency(ctx, q, "WHERE agency_id = ?", agencyID)
|
||
if err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
hostProfile, err := queryHostProfile(ctx, q, "WHERE user_id = ?", agency.OwnerUserID)
|
||
if err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
membership, err := queryAgencyMembership(ctx, q, "WHERE agency_id = ? AND host_user_id = ? AND membership_type = ? AND status = ?", agency.AgencyID, agency.OwnerUserID, hostdomain.MembershipTypeOwner, hostdomain.MembershipStatusActive)
|
||
if err != nil {
|
||
return hostdomain.CreateAgencyResult{}, err
|
||
}
|
||
return hostdomain.CreateAgencyResult{Agency: agency, HostProfile: hostProfile, Membership: membership}, nil
|
||
}
|