| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- // MembershipRepo 成员关系仓储(t_membership,人 ↔ 机构/校区 + 角色,多租户核心,ADR-007)。
- //
- // 平台级访问(同包注释):登录时取"这个用户有哪些机构/校区/角色"是全局查询,不做 scoping。
- // 多租户防线在后续业务域 Repository 才启动。
- //
- // 实体跨域引用说明:t_membership 的 GORM 实体定义在 **org 域 model 包**(internal/org/model.Membership,
- // 与 Institution/Campus/Role 同域)——登录域只"读"它取 role/作用域,不持有实体所有权。
- // 本文件故用别名 orgmodel 显式标注这一跨域引用,与本包内 identity/model(User/Identity)区分。
- //
- // 登录两步协议的支撑:
- // - 第一步 POST /auth/login 验账密后,Service 调 ListByCanonicalUserID 取该用户全部 membership,
- // 返回给前端展示"机构/校区选择页";
- // - 第二步 POST /auth/issue 选定 (institutionId, campusId) 后,Service 调
- // GetByUserInstitutionCampus 取对应 membership 的 role,签发 token。
- package repository
- import (
- "context"
- "gorm.io/gorm"
- orgmodel "yuansiwei/unified-backend/internal/org/model"
- )
- // MembershipRepo 成员关系仓储。
- type MembershipRepo struct {
- db *gorm.DB
- }
- // NewMembershipRepo 构造成员关系仓储。
- //
- // 入参:db *gorm.DB:数据库句柄(必填)。
- // 出参:*MembershipRepo:就绪仓储。
- func NewMembershipRepo(db *gorm.DB) *MembershipRepo {
- return &MembershipRepo{db: db}
- }
- // ListByCanonicalUserID 取某用户全部成员关系(登录第一步返回机构/校区选择列表用)。
- //
- // 入参:
- // - ctx context.Context:请求上下文。
- // - uid string:canonical_user_id(ULID)。
- //
- // 出参:
- // - []orgmodel.Membership:该用户全部 membership(含 institution_id/campus_id/role/status;
- // 空切片表示该用户尚无任何机构归属——登录成功后无机构可选,前端提示"请联系管理员分配机构")。
- // 注意:返回非 nil 切片(空时为 []orgmodel.Membership{}),JSON 序列化为 [] 而非 null。
- // - error:errors.ErrDBError(DB 异常);用户无 membership 不算错误(返回空切片,不 ErrNotFound)。
- //
- // 实现说明:Find 而非 First(一条用户可挂多机构/多角色,多对多);按 created_at 升序
- // (登录页展示按最早加入排前,用户习惯先选常用机构)。
- func (r *MembershipRepo) ListByCanonicalUserID(ctx context.Context, uid string) ([]orgmodel.Membership, error) {
- var list []orgmodel.Membership
- if err := r.db.WithContext(ctx).
- Where("canonical_user_id = ?", uid).
- Order("created_at ASC").
- Find(&list).Error; err != nil {
- return nil, mapGormErr(err)
- }
- if list == nil {
- list = []orgmodel.Membership{} // 非 nil:JSON 序列化 [] 而非 null
- }
- return list, nil
- }
- // GetByUserInstitutionCampus 按 (canonical_user_id, institution_id, campus_id) 取唯一 membership
- // (登录第二步 /auth/issue 选定作用域后,取该条的 role 签发 token)。
- //
- // 入参:
- // - ctx context.Context:请求上下文。
- // - uid string:canonical_user_id(ULID)。
- // - institutionID int64:机构 ID(scope 根;0 表示平台级超管,无机构过滤)。
- // - campusID int64:校区 ID(0 表示机构级/不限校区,如超管视角)。
- //
- // 出参:
- // - *orgmodel.Membership:对应 membership(含 Role,供签发 token 的 claims.Roles)。
- // - error:
- // - errors.ErrNotFound:该用户在此 (机构, 校区) 下无 membership(越权/数据不一致,登录第二步应报 2003 业务规则冲突);
- // - errors.ErrDBError:DB 异常。
- //
- // 作用域语义(与 org 域 model 注释对齐):
- // - institution_id=0:平台级(超管跨机构);campus_id=0:机构级/不限校区。
- // - UNIQUE(canonical_user_id, campus_id, role) 保证同一 (用户, 校区, 角色) 唯一;
- // 但同一 (用户, 机构, 校区) 可有多个 role(如某人既是教务又是财务),此时取**最新一条**
- // (created_at 最大)——多角色场景下登录默认进"最近使用的角色"。
- func (r *MembershipRepo) GetByUserInstitutionCampus(ctx context.Context, uid string, institutionID, campusID int64) (*orgmodel.Membership, error) {
- var m orgmodel.Membership
- q := r.db.WithContext(ctx).
- Where("canonical_user_id = ?", uid).
- Where("institution_id = ?", institutionID).
- Where("campus_id = ?", campusID)
- // 同一 (用户, 机构, 校区) 可能多角色,取 created_at 最新的一条(最近使用的角色)
- if err := q.Order("created_at DESC").First(&m).Error; err != nil {
- return nil, mapGormErr(err)
- }
- return &m, nil
- }
|