2026-06-29 17:47:40 +08:00

551 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 的 RegionID 来自其 users 当前区域投影,不再相信 bd_leader_profiles 的历史快照。
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 自身,但必须是有效身份且同区域。
// 父级 RegionID 来自其 users 当前区域投影,避免角色表旧快照影响新 Agency 归属。
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
}
// SetAgencyStatus 显式切换 Agency 状态;关闭会同步关入会,恢复只恢复组织本体,避免误把申请入口重新放开。
func (r *Repository) SetAgencyStatus(ctx context.Context, command hostservice.SetAgencyStatusCommand) (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.CommandTypeSetAgencyStatus, 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 {
// deleted 已经解除当前 Agency 归属,是删除事实;重新激活会破坏 host_profile 当前归属和历史 membership 语义。
return hostdomain.Agency{}, xerr.New(xerr.Conflict, "deleted agency cannot be reopened")
}
agency.Status = command.Status
if command.Status == hostdomain.AgencyStatusClosed {
// 关闭组织必须同时停止 App 申请入口;重新 active 时保留当前 join_enabled让管理员显式决定是否开放入会。
agency.JoinEnabled = false
}
agency.UpdatedAtMs = command.NowMs
if err := updateAgencyAdminState(ctx, tx, agency); err != nil {
return hostdomain.Agency{}, mapHostDuplicateError(err)
}
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeSetAgencyStatus, ResultType: hostdomain.ResultTypeAgency, ResultID: agency.AgencyID, CreatedAtMs: command.NowMs}); err != nil {
return hostdomain.Agency{}, mapHostDuplicateError(err)
}
if err := insertHostOutbox(ctx, tx, command.EventID, "AgencyChanged", "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
}