package tenant import ( "strings" "quyun/v2/app/errorx" "quyun/v2/app/http/tenant/dto" "quyun/v2/app/requests" "quyun/v2/app/services" "quyun/v2/database/models" "quyun/v2/pkg/consts" "github.com/gofiber/fiber/v3" log "github.com/sirupsen/logrus" ) // tenantUserAdmin provides tenant-admin member management endpoints. // // @provider type tenantUserAdmin struct{} // adminRemoveUser // // @Summary 移除租户成员(租户管理) // @Tags Tenant // @Accept json // @Produce json // @Param tenantCode path string true "Tenant Code" // @Param userID path int64 true "UserID" // @Success 200 {object} requests.Pager // // @Router /t/:tenantCode/v1/management/users/:userID [delete] // @Bind tenant local key(tenant) // @Bind tenantUser local key(tenant_user) // @Bind userID path func (*tenantUserAdmin) adminRemoveUser( ctx fiber.Ctx, tenant *models.Tenant, tenantUser *models.TenantUser, userID int64, ) error { if err := requireTenantAdmin(tenantUser); err != nil { return err } if userID <= 0 { return errorx.ErrInvalidParameter.WithMsg("user_id must be > 0") } log.WithFields(log.Fields{ "tenant_id": tenant.ID, "operator_user_id": tenantUser.UserID, "target_user_id": userID, "operator_is_admin": true, }).Info("tenant.admin.users.remove") // 关键语义:删除成员接口幂等化;目标用户不属于租户时也返回成功,便于后台重试与批量操作。 return services.Tenant.RemoveUser(ctx.Context(), tenant.ID, userID) } // adminJoinUser // // @Summary 添加租户成员(租户管理) // @Tags Tenant // @Accept json // @Produce json // @Param tenantCode path string true "Tenant Code" // @Param userID path int64 true "UserID" // @Success 200 {object} dto.AdminTenantUserJoinResponse // // @Router /t/:tenantCode/v1/management/users/:userID/join [post] // @Bind tenant local key(tenant) // @Bind tenantUser local key(tenant_user) // @Bind userID path func (*tenantUserAdmin) adminJoinUser( ctx fiber.Ctx, tenant *models.Tenant, tenantUser *models.TenantUser, userID int64, ) (*dto.AdminTenantUserJoinResponse, error) { if err := requireTenantAdmin(tenantUser); err != nil { return nil, err } if userID <= 0 { return nil, errorx.ErrInvalidParameter.WithMsg("user_id must be > 0") } log.WithFields(log.Fields{ "tenant_id": tenant.ID, "operator_user_id": tenantUser.UserID, "target_user_id": userID, "operator_is_admin": true, }).Info("tenant.admin.users.join") // 关键逻辑:以 TenantUser 为准创建成员关系;服务层保证幂等(已存在则不重复创建)。 if err := services.Tenant.AddUser(ctx.Context(), tenant.ID, userID); err != nil { return nil, err } m, err := services.Tenant.FindTenantUser(ctx.Context(), tenant.ID, userID) if err != nil { return nil, err } return &dto.AdminTenantUserJoinResponse{TenantUser: m}, nil } // adminSetUserRole // // @Summary 设置成员角色(租户管理) // @Tags Tenant // @Accept json // @Produce json // @Param tenantCode path string true "Tenant Code" // @Param userID path int64 true "UserID" // @Param form body dto.AdminTenantUserRoleUpdateForm true "Form" // @Success 200 {object} dto.AdminTenantUserJoinResponse // // @Router /t/:tenantCode/v1/management/users/:userID/role [patch] // @Bind tenant local key(tenant) // @Bind tenantUser local key(tenant_user) // @Bind userID path // @Bind form body func (*tenantUserAdmin) adminSetUserRole( ctx fiber.Ctx, tenant *models.Tenant, tenantUser *models.TenantUser, userID int64, form *dto.AdminTenantUserRoleUpdateForm, ) (*dto.AdminTenantUserJoinResponse, error) { if err := requireTenantAdmin(tenantUser); err != nil { return nil, err } if userID <= 0 || form == nil { return nil, errorx.ErrInvalidParameter } roleStr := strings.TrimSpace(form.Role) if roleStr == "" { return nil, errorx.ErrInvalidParameter.WithMsg("role is required") } var role consts.TenantUserRole switch roleStr { case string(consts.TenantUserRoleMember): role = consts.TenantUserRoleMember case string(consts.TenantUserRoleTenantAdmin): role = consts.TenantUserRoleTenantAdmin default: return nil, errorx.ErrInvalidParameter.WithMsg("invalid role") } log.WithFields(log.Fields{ "tenant_id": tenant.ID, "operator_user_id": tenantUser.UserID, "target_user_id": userID, "role": role, }).Info("tenant.admin.users.set_role") if err := services.Tenant.SetUserRole(ctx.Context(), tenant.ID, userID, role); err != nil { return nil, err } m, err := services.Tenant.FindTenantUser(ctx.Context(), tenant.ID, userID) if err != nil { return nil, err } return &dto.AdminTenantUserJoinResponse{TenantUser: m}, nil } // adminTenantUsers // // @Summary 成员列表(租户管理) // @Tags Tenant // @Accept json // @Produce json // @Param tenantCode path string true "Tenant Code" // @Param filter query dto.AdminTenantUserListFilter true "Filter" // @Success 200 {object} requests.Pager{items=dto.AdminTenantUserItem} // // @Router /t/:tenantCode/v1/management/users [get] // @Bind tenant local key(tenant) // @Bind tenantUser local key(tenant_user) // @Bind filter query func (*tenantUserAdmin) adminTenantUsers( ctx fiber.Ctx, tenant *models.Tenant, tenantUser *models.TenantUser, filter *dto.AdminTenantUserListFilter, ) (*requests.Pager, error) { if err := requireTenantAdmin(tenantUser); err != nil { return nil, err } log.WithFields(log.Fields{ "tenant_id": tenant.ID, "user_id": tenantUser.UserID, "query_uid": filter.UserID, "role": filter.Role, "status": filter.Status, "username": filter.UsernameTrimmed(), }).Info("tenant.admin.users.list") // 按 llm.txt 约束:HTTP 层不允许直接查询数据库,全部交由 services 层处理。 return services.Tenant.AdminTenantUsersPage(ctx.Context(), tenant.ID, filter) }