637 lines
17 KiB
Go
637 lines
17 KiB
Go
package tenant
|
|
|
|
import (
|
|
"context"
|
|
"github.com/qianfree/team-api/internal/dao"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
|
|
"github.com/qianfree/team-api/internal/consts"
|
|
lcommon "github.com/qianfree/team-api/internal/logic/common"
|
|
"github.com/qianfree/team-api/internal/logic/relay"
|
|
"github.com/qianfree/team-api/internal/middleware"
|
|
do "github.com/qianfree/team-api/internal/model/do"
|
|
|
|
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
|
"github.com/qianfree/team-api/internal/utility/export"
|
|
)
|
|
|
|
// ApiKeyList 列出 API Keys,支持按类型过滤
|
|
func (s *sTenant) ApiKeyList(ctx context.Context, req *v1.TenantApiKeyListReq) (*v1.TenantApiKeyListRes, error) {
|
|
tenantID := middleware.GetTenantID(ctx)
|
|
userID := middleware.GetUserID(ctx)
|
|
role := middleware.GetUserRole(ctx)
|
|
page, pageSize := lcommon.NormalizePagination(req.Page, req.PageSize)
|
|
|
|
// member 只能查看个人密钥
|
|
if role == "member" && req.KeyType == "project" {
|
|
return &v1.TenantApiKeyListRes{
|
|
List: []map[string]any{},
|
|
Total: 0,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}, nil
|
|
}
|
|
|
|
query := dao.ApiKeys.Ctx(ctx).Where("tenant_id", tenantID)
|
|
|
|
if req.KeyType == "project" {
|
|
// 项目密钥:owner/admin 可看租户内所有项目密钥
|
|
query = query.Where("key_type", "project")
|
|
if req.ProjectID > 0 {
|
|
query = query.Where("project_id", req.ProjectID)
|
|
}
|
|
} else {
|
|
// 个人密钥(默认):只看自己的
|
|
query = query.Where("user_id", userID).Where("key_type", "personal")
|
|
}
|
|
|
|
type keyRow struct {
|
|
Id int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
KeyPrefix string `json:"key_prefix"`
|
|
Scope string `json:"scope"`
|
|
Status string `json:"status"`
|
|
KeyType string `json:"key_type"`
|
|
ProjectId *int64 `json:"project_id"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
RateLimitQps *int `json:"rate_limit_qps"`
|
|
RateLimitConcurrency *int `json:"rate_limit_concurrency"`
|
|
TotalQuota *float64 `json:"total_quota"`
|
|
UsedQuota *float64 `json:"used_quota"`
|
|
CreatedAt *time.Time `json:"created_at"`
|
|
UpdatedAt *time.Time `json:"updated_at"`
|
|
}
|
|
|
|
var keys []keyRow
|
|
var err error
|
|
var total int
|
|
err = query.Fields("id, name, key_prefix, scope, status, key_type, project_id, expires_at, rate_limit_qps, rate_limit_concurrency, total_quota, used_quota, created_at, updated_at").
|
|
OrderDesc("created_at").
|
|
Page(page, pageSize).
|
|
ScanAndCount(&keys, &total, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if keys == nil {
|
|
keys = []keyRow{}
|
|
}
|
|
|
|
list := make([]map[string]any, 0, len(keys))
|
|
for _, k := range keys {
|
|
modelCount, _ := dao.ApiKeyModelScopes.Ctx(ctx).Where("api_key_id", k.Id).Count()
|
|
list = append(list, map[string]any{
|
|
"id": k.Id,
|
|
"name": k.Name,
|
|
"key_prefix": k.KeyPrefix,
|
|
"scope": k.Scope,
|
|
"model_count": modelCount,
|
|
"status": k.Status,
|
|
"key_type": k.KeyType,
|
|
"project_id": k.ProjectId,
|
|
"expires_at": k.ExpiresAt,
|
|
"rate_limit_qps": k.RateLimitQps,
|
|
"rate_limit_concurrency": k.RateLimitConcurrency,
|
|
"total_quota": k.TotalQuota,
|
|
"used_quota": k.UsedQuota,
|
|
"created_at": k.CreatedAt,
|
|
"updated_at": k.UpdatedAt,
|
|
})
|
|
}
|
|
|
|
return &v1.TenantApiKeyListRes{
|
|
List: list,
|
|
Total: total,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}, nil
|
|
}
|
|
|
|
// ApiKeyCreate 创建新的 API Key
|
|
func (s *sTenant) ApiKeyCreate(ctx context.Context, req *v1.TenantApiKeyCreateReq) (*v1.TenantApiKeyCreateRes, error) {
|
|
tenantID := middleware.GetTenantID(ctx)
|
|
userID := middleware.GetUserID(ctx)
|
|
role := middleware.GetUserRole(ctx)
|
|
|
|
keyType := req.KeyType
|
|
if keyType == "" {
|
|
keyType = "personal"
|
|
}
|
|
if keyType != "personal" && keyType != "project" {
|
|
return nil, lcommon.NewBusinessError(consts.CodeBadRequest, "无效的密钥类型")
|
|
}
|
|
|
|
// 项目密钥权限检查
|
|
if keyType == "project" {
|
|
if role != "owner" && role != "admin" {
|
|
return nil, lcommon.NewBusinessError(consts.CodeProjectKeyForbidden, consts.MsgProjectKeyForbidden)
|
|
}
|
|
if req.ProjectID <= 0 {
|
|
return nil, lcommon.NewBusinessError(consts.CodeBadRequest, "项目密钥必须指定关联项目")
|
|
}
|
|
// 验证项目存在且活跃
|
|
var project struct {
|
|
Id int64 `json:"id"`
|
|
Status string `json:"status"`
|
|
}
|
|
err := dao.TntProjects.Ctx(ctx).
|
|
Where("id", req.ProjectID).
|
|
Where("tenant_id", tenantID).
|
|
Scan(&project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if project.Id == 0 {
|
|
return nil, lcommon.NewBusinessError(consts.CodeProjectNotFound, consts.MsgProjectNotFound)
|
|
}
|
|
if project.Status != "active" {
|
|
return nil, lcommon.NewBusinessError(consts.CodeProjectNotActive, consts.MsgProjectNotActive)
|
|
}
|
|
}
|
|
|
|
rawKey, prefix, encryptedKey, err := relay.GenerateApiKey(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data := do.ApiKeys{
|
|
TenantId: tenantID,
|
|
UserId: userID,
|
|
Name: req.Name,
|
|
EncryptedKey: encryptedKey,
|
|
KeyPrefix: prefix,
|
|
Scope: req.Scope,
|
|
Status: "active",
|
|
}
|
|
|
|
if keyType == "project" {
|
|
data.ProjectId = req.ProjectID
|
|
}
|
|
|
|
if req.ExpiresAt != nil {
|
|
data.ExpiresAt = req.ExpiresAt
|
|
}
|
|
|
|
result, err := dao.ApiKeys.Ctx(ctx).Insert(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyID, err := result.LastInsertId()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, modelName := range req.ModelNames {
|
|
if modelName != "" {
|
|
dao.ApiKeyModelScopes.Ctx(ctx).Insert(do.ApiKeyModelScopes{
|
|
ApiKeyId: keyID,
|
|
ModelName: modelName,
|
|
})
|
|
}
|
|
}
|
|
|
|
return &v1.TenantApiKeyCreateRes{
|
|
Id: keyID,
|
|
Name: req.Name,
|
|
Key: rawKey,
|
|
KeyPrefix: prefix,
|
|
Scope: req.Scope,
|
|
KeyType: keyType,
|
|
}, nil
|
|
}
|
|
|
|
// ApiKeyDelete 禁用 API Key
|
|
func (s *sTenant) ApiKeyDelete(ctx context.Context, req *v1.TenantApiKeyDeleteReq) (*v1.TenantApiKeyDeleteRes, error) {
|
|
tenantID := middleware.GetTenantID(ctx)
|
|
userID := middleware.GetUserID(ctx)
|
|
role := middleware.GetUserRole(ctx)
|
|
keyID := req.Id
|
|
|
|
// 先查询密钥信息以判断类型
|
|
type keyInfo struct {
|
|
KeyType string `json:"key_type"`
|
|
KeyPrefix string `json:"key_prefix"`
|
|
}
|
|
var info *keyInfo
|
|
err := dao.ApiKeys.Ctx(ctx).
|
|
Where("id", keyID).
|
|
Where("tenant_id", tenantID).
|
|
Fields("key_type, key_prefix").
|
|
Scan(&info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if info == nil {
|
|
return nil, lcommon.NewNotFoundError("API key")
|
|
}
|
|
|
|
query := dao.ApiKeys.Ctx(ctx).
|
|
Where("id", keyID).
|
|
Where("tenant_id", tenantID)
|
|
|
|
if info.KeyType == "project" {
|
|
// 项目密钥:需要 owner/admin 权限
|
|
if role != "owner" && role != "admin" {
|
|
return nil, lcommon.NewBusinessError(consts.CodeProjectKeyForbidden, consts.MsgProjectKeyForbidden)
|
|
}
|
|
// 不限 user_id,按租户级操作
|
|
} else {
|
|
// 个人密钥:只能禁用自己的
|
|
query = query.Where("user_id", userID)
|
|
}
|
|
|
|
result, err := query.Data(do.ApiKeys{
|
|
Status: "disabled",
|
|
}).Update()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rowsAffected, _ := result.RowsAffected()
|
|
if rowsAffected == 0 {
|
|
return nil, lcommon.NewNotFoundError("API key")
|
|
}
|
|
|
|
if info.KeyPrefix != "" {
|
|
g.Log().Infof(ctx, "API key %d disabled, cache invalidation needed for prefix %s", keyID, info.KeyPrefix)
|
|
}
|
|
|
|
return &v1.TenantApiKeyDeleteRes{}, nil
|
|
}
|
|
|
|
// ApiKeyUpdate 更新 API Key 的可编辑字段
|
|
func (s *sTenant) ApiKeyUpdate(ctx context.Context, req *v1.TenantApiKeyUpdateReq) (*v1.TenantApiKeyUpdateRes, error) {
|
|
tenantID := middleware.GetTenantID(ctx)
|
|
userID := middleware.GetUserID(ctx)
|
|
role := middleware.GetUserRole(ctx)
|
|
keyID := req.Id
|
|
|
|
// 查询密钥信息
|
|
type keyInfo struct {
|
|
KeyType string `json:"key_type"`
|
|
KeyPrefix string `json:"key_prefix"`
|
|
Status string `json:"status"`
|
|
UsedQuota float64 `json:"used_quota"`
|
|
}
|
|
var info *keyInfo
|
|
err := dao.ApiKeys.Ctx(ctx).
|
|
Where("id", keyID).
|
|
Where("tenant_id", tenantID).
|
|
Fields("key_type, key_prefix, status, used_quota").
|
|
Scan(&info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if info == nil {
|
|
return nil, lcommon.NewNotFoundError("API key")
|
|
}
|
|
|
|
// 权限检查
|
|
if info.KeyType == "project" {
|
|
if role != "owner" && role != "admin" {
|
|
return nil, lcommon.NewBusinessError(consts.CodeProjectKeyForbidden, consts.MsgProjectKeyForbidden)
|
|
}
|
|
} else {
|
|
// 个人密钥:只能更新自己的
|
|
count, err := dao.ApiKeys.Ctx(ctx).
|
|
Where("id", keyID).
|
|
Where("tenant_id", tenantID).
|
|
Where("user_id", userID).
|
|
Count()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if count == 0 {
|
|
return nil, lcommon.NewNotFoundError("API key")
|
|
}
|
|
}
|
|
|
|
// 构建更新数据
|
|
data := g.Map{}
|
|
|
|
if req.Name != "" {
|
|
data["name"] = req.Name
|
|
}
|
|
if req.Scope != "" {
|
|
data["scope"] = req.Scope
|
|
}
|
|
if req.Status != "" {
|
|
if req.Status != "active" && req.Status != "disabled" {
|
|
return nil, lcommon.NewBusinessError(consts.CodeBadRequest, "无效的状态值")
|
|
}
|
|
// 从 disabled 恢复为 active 时,检查是否已过期
|
|
if req.Status == "active" && info.Status == "disabled" {
|
|
type expireCheck struct {
|
|
ExpiresAt *gtime.Time `json:"expires_at"`
|
|
}
|
|
var ec *expireCheck
|
|
dao.ApiKeys.Ctx(ctx).
|
|
Where("id", keyID).
|
|
Fields("expires_at").
|
|
Scan(&ec)
|
|
if ec != nil && ec.ExpiresAt != nil && ec.ExpiresAt.Before(gtime.Now()) {
|
|
return nil, lcommon.NewBusinessError(consts.CodeBadRequest, "密钥已过期,无法重新启用")
|
|
}
|
|
}
|
|
data["status"] = req.Status
|
|
}
|
|
if req.ExpiresAt != nil {
|
|
data["expires_at"] = req.ExpiresAt
|
|
}
|
|
if req.RateLimitQps != nil {
|
|
data["rate_limit_qps"] = *req.RateLimitQps
|
|
}
|
|
if req.RateLimitConcurrency != nil {
|
|
data["rate_limit_concurrency"] = *req.RateLimitConcurrency
|
|
}
|
|
if req.TotalQuota != nil {
|
|
if *req.TotalQuota > 0 && *req.TotalQuota < info.UsedQuota {
|
|
return nil, lcommon.NewBusinessError(consts.CodeBadRequest, "总额度不能小于已用额度")
|
|
}
|
|
data["total_quota"] = *req.TotalQuota
|
|
}
|
|
|
|
if len(data) > 0 {
|
|
_, err = dao.ApiKeys.Ctx(ctx).
|
|
Where("id", keyID).
|
|
Where("tenant_id", tenantID).
|
|
Data(data).
|
|
Update()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// 更新模型范围
|
|
if req.ModelNames != nil {
|
|
dao.ApiKeyModelScopes.Ctx(ctx).
|
|
Where("api_key_id", keyID).
|
|
Delete()
|
|
|
|
for _, modelName := range req.ModelNames {
|
|
if modelName != "" {
|
|
dao.ApiKeyModelScopes.Ctx(ctx).Insert(do.ApiKeyModelScopes{
|
|
ApiKeyId: keyID,
|
|
ModelName: modelName,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if info.KeyPrefix != "" {
|
|
g.Log().Infof(ctx, "API key %d updated, cache invalidation needed for prefix %s", keyID, info.KeyPrefix)
|
|
}
|
|
|
|
return &v1.TenantApiKeyUpdateRes{}, nil
|
|
}
|
|
|
|
// ApiKeyUpdateScopes 更新 API Key 的模型 scope
|
|
func (s *sTenant) ApiKeyUpdateScopes(ctx context.Context, req *v1.TenantApiKeyUpdateScopesReq) (*v1.TenantApiKeyUpdateScopesRes, error) {
|
|
tenantID := middleware.GetTenantID(ctx)
|
|
userID := middleware.GetUserID(ctx)
|
|
role := middleware.GetUserRole(ctx)
|
|
keyID := req.Id
|
|
|
|
// 先查询密钥信息以判断类型
|
|
type keyInfo struct {
|
|
KeyType string `json:"key_type"`
|
|
}
|
|
var info *keyInfo
|
|
err := dao.ApiKeys.Ctx(ctx).
|
|
Where("id", keyID).
|
|
Where("tenant_id", tenantID).
|
|
Fields("key_type").
|
|
Scan(&info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if info == nil {
|
|
return nil, lcommon.NewNotFoundError("API key")
|
|
}
|
|
|
|
// 权限检查
|
|
if info.KeyType == "project" {
|
|
if role != "owner" && role != "admin" {
|
|
return nil, lcommon.NewBusinessError(consts.CodeProjectKeyForbidden, consts.MsgProjectKeyForbidden)
|
|
}
|
|
} else {
|
|
// 个人密钥:只能更新自己的
|
|
count, err := dao.ApiKeys.Ctx(ctx).
|
|
Where("id", keyID).
|
|
Where("tenant_id", tenantID).
|
|
Where("user_id", userID).
|
|
Count()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if count == 0 {
|
|
return nil, lcommon.NewNotFoundError("API key")
|
|
}
|
|
}
|
|
|
|
dao.ApiKeyModelScopes.Ctx(ctx).
|
|
Where("api_key_id", keyID).
|
|
Delete()
|
|
|
|
for _, modelName := range req.ModelNames {
|
|
if modelName != "" {
|
|
dao.ApiKeyModelScopes.Ctx(ctx).Insert(do.ApiKeyModelScopes{
|
|
ApiKeyId: keyID,
|
|
ModelName: modelName,
|
|
})
|
|
}
|
|
}
|
|
|
|
return &v1.TenantApiKeyUpdateScopesRes{}, nil
|
|
}
|
|
|
|
// ApiKeyModelScopes 查询 API Key 的模型范围
|
|
func (s *sTenant) ApiKeyModelScopes(ctx context.Context, req *v1.TenantApiKeyModelScopesReq) (*v1.TenantApiKeyModelScopesRes, error) {
|
|
tenantID := middleware.GetTenantID(ctx)
|
|
|
|
// 验证 key 属于该租户
|
|
count, err := dao.ApiKeys.Ctx(ctx).
|
|
Where("id", req.Id).
|
|
Where("tenant_id", tenantID).
|
|
Count()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if count == 0 {
|
|
return nil, lcommon.NewNotFoundError("API key")
|
|
}
|
|
|
|
type scopeRow struct {
|
|
ModelName string `json:"model_name"`
|
|
}
|
|
var rows []scopeRow
|
|
err = dao.ApiKeyModelScopes.Ctx(ctx).
|
|
Where("api_key_id", req.Id).
|
|
Fields("model_name").
|
|
Order("model_name").
|
|
Scan(&rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
names := make([]string, 0, len(rows))
|
|
for _, r := range rows {
|
|
names = append(names, r.ModelName)
|
|
}
|
|
|
|
return &v1.TenantApiKeyModelScopesRes{ModelNames: names}, nil
|
|
}
|
|
|
|
// listProjectApiKeys 列出指定项目的 API Keys
|
|
func listProjectApiKeys(ctx context.Context, tenantID, projectID int64, page, pageSize int) ([]map[string]any, int, error) {
|
|
page, pageSize = lcommon.NormalizePagination(page, pageSize)
|
|
|
|
// 验证项目存在且属于该租户
|
|
var count int
|
|
count, err := dao.TntProjects.Ctx(ctx).
|
|
Where("id", projectID).
|
|
Where("tenant_id", tenantID).
|
|
Count()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if count == 0 {
|
|
return nil, 0, lcommon.NewBusinessError(consts.CodeProjectNotFound, consts.MsgProjectNotFound)
|
|
}
|
|
|
|
query := dao.ApiKeys.Ctx(ctx).
|
|
Where("tenant_id", tenantID).
|
|
Where("project_id", projectID).
|
|
Where("key_type", "project")
|
|
|
|
type keyRow struct {
|
|
Id int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
KeyPrefix string `json:"key_prefix"`
|
|
Scope string `json:"scope"`
|
|
Status string `json:"status"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
RateLimitQps *int `json:"rate_limit_qps"`
|
|
RateLimitConcurrency *int `json:"rate_limit_concurrency"`
|
|
TotalQuota *float64 `json:"total_quota"`
|
|
UsedQuota *float64 `json:"used_quota"`
|
|
CreatedAt *time.Time `json:"created_at"`
|
|
UpdatedAt *time.Time `json:"updated_at"`
|
|
}
|
|
|
|
var keys []keyRow
|
|
var total int
|
|
err = query.Fields("id, name, key_prefix, scope, status, expires_at, rate_limit_qps, rate_limit_concurrency, total_quota, used_quota, created_at, updated_at").
|
|
OrderDesc("created_at").
|
|
Page(page, pageSize).
|
|
ScanAndCount(&keys, &total, false)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if keys == nil {
|
|
keys = []keyRow{}
|
|
}
|
|
|
|
list := make([]map[string]any, 0, len(keys))
|
|
for _, k := range keys {
|
|
list = append(list, map[string]any{
|
|
"id": k.Id,
|
|
"name": k.Name,
|
|
"key_prefix": k.KeyPrefix,
|
|
"scope": k.Scope,
|
|
"status": k.Status,
|
|
"expires_at": k.ExpiresAt,
|
|
"rate_limit_qps": k.RateLimitQps,
|
|
"rate_limit_concurrency": k.RateLimitConcurrency,
|
|
"total_quota": k.TotalQuota,
|
|
"used_quota": k.UsedQuota,
|
|
"created_at": k.CreatedAt,
|
|
"updated_at": k.UpdatedAt,
|
|
})
|
|
}
|
|
|
|
return list, total, nil
|
|
}
|
|
|
|
// ExportApiKeys exports the tenant API key list as CSV or Excel.
|
|
func (s *sTenant) ExportApiKeys(ctx context.Context, req *v1.TenantApiKeyExportReq) (*v1.TenantApiKeyExportRes, error) {
|
|
tenantID := middleware.GetTenantID(ctx)
|
|
userID := middleware.GetUserID(ctx)
|
|
|
|
columns := []export.Column{
|
|
{Field: "id", Header: "ID"},
|
|
{Field: "name", Header: "名称"},
|
|
{Field: "key_prefix", Header: "Key前缀"},
|
|
{Field: "key_type", Header: "类型"},
|
|
{Field: "scope", Header: "范围"},
|
|
{Field: "status", Header: "状态"},
|
|
{Field: "expires_at", Header: "过期时间"},
|
|
{Field: "created_at", Header: "创建时间"},
|
|
}
|
|
|
|
config := export.Config{
|
|
Format: req.Format,
|
|
Filename: "API密钥_" + gtime.Now().Format("Ymd_His"),
|
|
Columns: columns,
|
|
}
|
|
|
|
buildQuery := func() *gdb.Model {
|
|
query := dao.ApiKeys.Ctx(ctx).Where("tenant_id", tenantID)
|
|
if req.KeyType == "project" {
|
|
query = query.Where("key_type", "project")
|
|
if req.ProjectID > 0 {
|
|
query = query.Where("project_id", req.ProjectID)
|
|
}
|
|
} else {
|
|
query = query.Where("user_id", userID).Where("key_type", "personal")
|
|
}
|
|
return query
|
|
}
|
|
|
|
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
|
offset := 0
|
|
for {
|
|
type keyRow struct {
|
|
Id int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
KeyPrefix string `json:"key_prefix"`
|
|
KeyType string `json:"key_type"`
|
|
Scope string `json:"scope"`
|
|
Status string `json:"status"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
CreatedAt *time.Time `json:"created_at"`
|
|
}
|
|
|
|
var keys []keyRow
|
|
err := buildQuery().
|
|
Fields("id, name, key_prefix, key_type, scope, status, expires_at, created_at").
|
|
OrderDesc("created_at").
|
|
Limit(1000).Offset(offset).
|
|
Scan(&keys)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, k := range keys {
|
|
if !yield(map[string]any{
|
|
"id": k.Id,
|
|
"name": k.Name,
|
|
"key_prefix": k.KeyPrefix,
|
|
"key_type": k.KeyType,
|
|
"scope": k.Scope,
|
|
"status": k.Status,
|
|
"expires_at": k.ExpiresAt,
|
|
"created_at": k.CreatedAt,
|
|
}) {
|
|
return
|
|
}
|
|
}
|
|
if len(keys) < 1000 {
|
|
break
|
|
}
|
|
offset += 1000
|
|
}
|
|
})
|
|
}
|