//go:build mysql // +build mysql package mysql import ( "database/sql" "encoding/json" "strings" "time" "github.com/jmoiron/sqlx" t "github.com/tinode/chat/server/store/types" ) func (a *adapter) TenantInternalAdminQuery(id t.TenantID, query string, countQuery string, args []any, page, perPage int) (*t.InternalAdminQueryResult, error) { tenantAdp, err := a.routedTenantAdapter(id) if err != nil { return nil, t.ErrMalformed } if tenantAdp != a { return tenantAdp.TenantInternalAdminQuery(id, query, countQuery, args, page, perPage) } if strings.TrimSpace(query) == "" || strings.TrimSpace(countQuery) == "" { return nil, t.ErrMalformed } if page <= 0 { page = 1 } if perPage <= 0 || perPage > 200 { perPage = 20 } offset := (page - 1) * perPage ctx, cancel := a.getContext() if cancel != nil { defer cancel() } var total int64 if err = a.db.GetContext(ctx, &total, countQuery, args...); err != nil { return nil, err } queryArgs := append(append([]any{}, args...), perPage, offset) rows, err := a.db.QueryxContext(ctx, query+" LIMIT ? OFFSET ?", queryArgs...) if err != nil { return nil, err } defer rows.Close() items, err := scanInternalAdminRows(rows) if err != nil { return nil, err } return &t.InternalAdminQueryResult{Items: items, Total: total, Page: page, PerPage: perPage}, nil } func (a *adapter) TenantInternalAdminQueryOne(id t.TenantID, query string, args []any) (map[string]any, error) { tenantAdp, err := a.routedTenantAdapter(id) if err != nil { return nil, t.ErrMalformed } if tenantAdp != a { return tenantAdp.TenantInternalAdminQueryOne(id, query, args) } if strings.TrimSpace(query) == "" { return nil, t.ErrMalformed } ctx, cancel := a.getContext() if cancel != nil { defer cancel() } rows, err := a.db.QueryxContext(ctx, query+" LIMIT 1", args...) if err != nil { return nil, err } defer rows.Close() items, err := scanInternalAdminRows(rows) if err != nil { return nil, err } if len(items) == 0 { return nil, t.ErrNotFound } return items[0], nil } func scanInternalAdminRows(rows *sqlx.Rows) ([]map[string]any, error) { items := []map[string]any{} for rows.Next() { row := map[string]any{} if err := rows.MapScan(row); err != nil { return nil, err } for key, value := range row { row[key] = normalizeInternalAdminValue(value) } items = append(items, row) } return items, rows.Err() } func normalizeInternalAdminValue(value any) any { switch v := value.(type) { case nil: return nil case []byte: raw := strings.TrimSpace(string(v)) if raw == "" { return "" } if (strings.HasPrefix(raw, "{") && strings.HasSuffix(raw, "}")) || (strings.HasPrefix(raw, "[") && strings.HasSuffix(raw, "]")) { var parsed any if json.Unmarshal([]byte(raw), &parsed) == nil { return parsed } } return raw case time.Time: return v.UTC().Format(time.RFC3339Nano) case sql.NullTime: if v.Valid { return v.Time.UTC().Format(time.RFC3339Nano) } return nil default: return value } }