From 676fa4d952153a2ad27ae378f11e526d4434678b Mon Sep 17 00:00:00 2001 From: Yvan <8574526@qq,com> Date: Fri, 28 Aug 2026 11:10:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 31 ++++++++++------- docs/code-review-issues.md | 22 ++++++------- internal/config/clone.go | 1 - internal/integration/payment/gopay_test.go | 14 ++++---- internal/integration/payment/qq_test.go | 6 ++-- .../payment/xml_test_helpers_test.go} | 10 +++--- internal/paymentkit/json.go | 26 --------------- internal/paymentkit/json_test.go | 6 ++-- internal/service/dto/integration_config.go | 33 ++++++++++++++----- .../service/integration/integration_config.go | 22 +++++++++++-- 10 files changed, 91 insertions(+), 80 deletions(-) rename internal/{paymentkit/xml.go => integration/payment/xml_test_helpers_test.go} (84%) diff --git a/CLAUDE.md b/CLAUDE.md index 469f8c7..e21cd16 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,15 +6,22 @@ agents must follow when changing the template. ## Project structure ``` -api/// Proto sources and generated stubs. Public contract. -cmd// Entrypoint, Wire injector, main.go. +cmd/ Entrypoint, Wire injector, main.go. configs/ Runtime config (config.yaml). No secrets. internal/config/ Viper config models, loading, snapshots, and reloads. -internal/global/ Process-wide shared runtime resources. -internal/server/ HTTP/gRPC server wiring. -internal/service/ Transport adapters; one file per resource. -internal/biz/ Domain models, usecases, repo interfaces, errors. -internal/data/ Repo implementations and storage clients. +internal/server/ Gin/Kratos handlers, middleware, routers, static files. +internal/service/dto/ Hand-written request and response DTOs. +internal/service/ DTO/DO transport adapters. +internal/biz/ Domain models, usecases, repo interfaces, errors. +internal/data/ Repo implementations, database clients, migrations. +internal/initialize/ First-install and configuration orchestration. +internal/integration/ External I/O adapters (cache, email, payment, storage). +internal/modules/ Built-in module schema, seed, menu, API, and task contributions. +internal/routecatalog/ HTTP route metadata and runtime policies. +internal/worker/ Timed-task runtime and scheduler. +internal/utils/ Stateless internal helpers. +pkg/ Reusable infrastructure packages. +web/ Vue administration frontend. ``` ## Layering & dependency rules @@ -28,7 +35,7 @@ owns the PO; `service` is a pass-through that converts at its boundary. │ declares │ implements └─── repo IF ────┘ - DTO Data Transfer Object — proto request / response. + DTO Data Transfer Object — hand-written HTTP request / response. DO Domain Object — pure biz model, no proto, no storage tags. PO Persistent Object — storage shape, owned by `data`. ``` @@ -39,11 +46,13 @@ owns the PO; `service` is a pass-through that converts at its boundary. | biz | DO | DO | DTO, PO, storage client | | data | PO | DO ↔ PO | DTO | -- `service` imports `api/...` (DTO) and `biz` (DO). Never `data`. -- `biz` imports `api/...` only for error reason enums. Never `service`, - never `data`. The repo interface declared here is the inversion seam. +- `service` imports `internal/service/dto` (DTO) and `biz` (DO). Never `data`. +- `biz` never imports `service` or `data`. The repo interface declared here is + the inversion seam. - `data` imports `biz` to implement the repo interface. Never `service`, never DTOs. +- `integration` implements external I/O boundaries and may import `biz` and + provider SDKs; it is not a utility layer. - `cmd` is the only place that wires all layers via Wire. A change crossing these arrows the wrong way is a layering bug; fix the diff --git a/docs/code-review-issues.md b/docs/code-review-issues.md index 72147a0..d86cb5a 100644 --- a/docs/code-review-issues.md +++ b/docs/code-review-issues.md @@ -26,9 +26,9 @@ |----|--------|------| | ~~biz 接口+data 实现~~ | ~~`RecordDataAccess` 写入链已删除;查询侧仍保留。~~ | ~~相关文件~~ | | ~~service 包装~~ | ~~security_session.go 中 8 个无消费者透传方法已删除。~~ | ~~相关文件~~ | -| biz 注入面 | `RegisterBusinessModule`(W-1 的成因之一,两阶段注册+回滚补偿零调用);`PaymentBusinessModule`/`PayInternal`/`RefundInternal`/`AuthorizeRefund` 接口面仍无生产实现(biz 调用链真实存在,仅实现者缺——与 W-1 一并处理) | biz/payment/payment.go:400-412;payment_order.go:139-152 | +| ~~biz 注入面~~ | ~~经复核保留:`RegisterBusinessModule` 与 `PaymentBusinessModule` 是 `docs/PAYMENT.md` 明确要求的业务接入契约;`PayInternal`/`RefundInternal`/`AuthorizeRefund` 已由 biz 调用链消费,模板无具体业务实现属于预期扩展点,不是死代码。评估完成,保留(2026-08-28)。~~ | ~~biz/payment/payment.go:400-412;payment_order.go:139-152~~ | | ~~dto 死字段(已确认项)~~ | ~~GetAuthorityButtonsRequest.Selected 已删除;其余字段因仍参与响应或兼容契约暂保留。~~ | ~~dto/permission.go~~ | -| 死分支 | export_excel.go 的 `case []byte` 在 data 层按列类型转换(R-2 修复)后成为死分支 | service/system/export_excel.go:85-86 | +| ~~死分支~~ | ~~经复核保留:service 层接收通用 `map[string]any`,`[]byte` 仍是合法 usecase/fake 输入;该分支有针对性测试,不属于可证明死代码。评估完成,保留(2026-08-28)。~~ | ~~service/system/export_excel.go:85-86~~ | ### 2.2 mq / websocket 零消费者基础设施【既定排除范围,历轮明确不处理,现状保持】 @@ -40,12 +40,12 @@ | 死代码 | 位置 | 证据 | |--------|------|------| -| `config.CloneData` | internal/config/clone.go:8 | 全仓零调用(其余 Clone* 均有生产调用) | -| `paymentkit.XMLValues`/`XMLEncode` | internal/paymentkit/xml.go:16,41 | 仅测试调用,生产 XML 走 gopay 库 | -| `paymentkit.NestedString` | internal/paymentkit/json.go:18 | 生产+测试均零调用(旧 shim 删除后的孤儿) | +| ~~`config.CloneData`~~ | ~~已删除:全仓零调用,且 `Clone`/`MergeRuntimeConfig` 已覆盖实际快照复制入口。已完成(2026-08-28)。~~ | ~~internal/config/clone.go~~ | +| ~~`paymentkit.XMLValues`/`XMLEncode`~~ | ~~已删除:仅测试调用;XML 编解码辅助已移入 payment 集成测试文件,生产 XML 继续走 gopay。已完成(2026-08-28)。~~ | ~~internal/paymentkit/xml.go~~ | +| ~~`paymentkit.NestedString`~~ | ~~已删除:生产零调用;旧测试已改为覆盖现存 `JSONObject`/`StringAtPath`。已完成(2026-08-28)。~~ | ~~internal/paymentkit/json.go~~ | | `logging.NewZapLogger` | internal/logging/zap.go:542 | 仅测试调用,生产用 NewReloadableZapLogger | | ~~`data/payment.contains`~~ | ~~已改用 `paymentkit.ContainsFold`,本地实现已删除。~~ | ~~data/payment/payment.go~~ | -| `paymentkit status.go 的 ConfiguredInt64/ConfiguredValues/Text/FirstText/FirstString` 定位漂移 | internal/paymentkit/status.go:36-90 | 属"供应商配置解析"超出 README 声称范围(文档漂移,非死代码) | +| ~~`paymentkit status.go 的 ConfiguredInt64/ConfiguredValues/Text/FirstText/FirstString` 定位漂移~~ | ~~经复核保留:这些函数仍被 payment data/integration 生产路径消费,属于配置解析边界;问题是 README 描述过窄而非死代码。评估完成,保留(2026-08-28)。~~ | ~~internal/paymentkit/status.go:36-90~~ | ## 三、重复实现 / 双份维护 @@ -55,7 +55,7 @@ |---|------|------|------| | ~~D-2(部分完成)~~ | ~~支付方式归一化骨架已统一到 `paymentkit.NormalizePaymentMethod`;各渠道状态词表与退款身份校验因语义不同保留。~~ | ~~integration/payment;internal/paymentkit~~ | ~~部分完成~~ | | ~~D-4~~ | ~~经复核样板虽多,但各 handler 的绑定方式、错误文案、响应 envelope 和鉴权上下文差异明显;抽统一门面会隐藏 transport 语义并扩大回归面,暂不改动。~~ | ~~server/handler/*~~ | ~~评估完成,保留(2026-08-28)~~ | -| D-6 | mq/websocket 两包各写一套 map 解码 helper 且逐字符相同;TestConfig 探测骨架三处同构【属 2.2 排除范围交叉项】 | emqx.go:226-260 vs websocket/server.go:189-240 | 二轮 | +| ~~D-6~~ | ~~经复核暂不改动:重复 helper 与 MQ/WebSocket 零消费者基础设施同属既定排除范围;上收 `internal/utils` 会扩大无消费者包的公共表面积。~~ | ~~emqx.go:226-260 vs websocket/server.go:189-240~~ | ~~评估完成,保留(2026-08-28)~~ | ### 3.2 配置/数据不变式双份维护 @@ -95,9 +95,9 @@ | # | 问题 | 位置 | 轮次 | |---|------|------|------| -| ~~S-1(第一批)~~ | ~~已合并 actor/data-scope 上下文载荷文件;其余微文件仍待按职责归并。~~ | ~~internal/biz/system/context.go~~ | ~~部分完成~~ | +| ~~S-1(第一批)~~ | ~~已合并 actor/data-scope 上下文载荷文件;复核剩余微文件均对应独立职责,不再机械归并。~~ | ~~internal/biz/system/context.go~~ | ~~部分完成(2026-08-28)~~ | | ~~S-2~~ | ~~SystemConfigService 已合并为单文件。~~ | ~~internal/service/system/system.go~~ | ~~已完成~~ | -| ~~S-3(第一批)~~ | ~~routes.go 已改为有序注册表;各领域路由文件仍保留。~~ | ~~internal/server/router/routes.go~~ | ~~部分完成~~ | +| ~~S-3(第一批)~~ | ~~routes.go 已改为有序注册表;各领域路由文件承担独立注册边界,复核后保留。~~ | ~~internal/server/router/routes.go~~ | ~~部分完成(2026-08-28)~~ | | ~~S-4~~ | ~~四个 data 子包的 provider 文件已合并。~~ | ~~internal/data/*/provider.go~~ | ~~已完成~~ | | ~~S-5~~ | ~~经复核保留:payment 大文件承载跨供应商编排与契约,微文件分别对应独立边界;dto 合并会重新混装领域,收益不足。~~ | ~~biz/payment、service/dto~~ | ~~评估完成,保留(2026-08-28)~~ | | ~~S-6~~ | ~~经复核保留:`system.go`/`settings.go` 虽跨域,但移动类型会放大 service DTO 导入与生成契约变化;本批不做机械拆分。~~ | ~~service/dto/system.go、settings.go~~ | ~~评估完成,保留(2026-08-28)~~ | @@ -120,7 +120,7 @@ | P-13 | pkg/module、pkg/task、pkg/database/migration 同层契约组维持现状 | pkg/* | 一轮 | | P-14 | httpx 移动后业务语义未剥离:CodePasswordChangeRequired=10001 与 x-token cookie 仍留在 internal/server/httpx;SetTokenCookie 注释自称 "no KRA business dependency" 与语义不符 | internal/server/httpx/response.go:17,58-64 | 四轮(P-1 移动残留) | | ~~P-15~~ | ~~包移动注释与 logging 栈跳过标记已修正。~~ | ~~相关文件~~ | ~~已完成~~ | -| P-16 | CLAUDE.md 结构描述整体过时(描述 api/、internal/global/ 等不存在目录),与 AGENTS.md 不同步 | CLAUDE.md:9-17 | 四轮 | +| ~~P-16~~ | ~~已同步 CLAUDE.md 的目录结构与分层说明,移除不存在的 `api/`、`internal/global/` 等描述。~~ | ~~CLAUDE.md~~ | ~~已完成(2026-08-28)~~ | ## 七、分层 / 职责违规 @@ -132,7 +132,7 @@ | ~~L-6(部分完成)~~ | ~~`AuthenticationResult` 已在返回 service 前清空密码哈希并补回归测试。~~ `QueryExport` 动态行、export DO 的兼容 SQL 字段、`UserOptions` 选项形状仍保留:前两项涉及公开导入导出契约与存量数据兼容,后者虽命名偏 UI,但实际是稳定的 label/value 投影;当前直接迁移收益不足以覆盖契约风险。 | biz/system/* | ~~部分完成(2026-08-28;针对性测试通过)~~ | | L-7 | data 层纪律:Table("字符串") 绕过 PO;saveRelations 回写入参 DO;OriginSetting 裸转换 | data/system/* | 二轮 | | L-8 | 编排类文件过重:seedSystem 126 行 10 类职责;authority.go 四类职责(权限引擎应独立 accessGuard);BuildVersionBundle 五职责 | data/system/seed.go、authority.go、version.go | 二轮 | -| ~~L-9(部分完成)~~ | ~~`AuthorityResponse.DeletedAt` 已改为 `json:"-"`,不再泄漏软删字段。~~ ID 类型分叉涉及现有 handler/usecase/数据库键类型的兼容迁移;`ErrorRecordMutationRequest` 的指针字段用于区分省略与显式空值且已有测试,原建议不成立,保留;DTO 对 integration biz 字段定义的反向依赖真实存在,但迁移会改变配置元数据契约,留待独立处理。 | service/dto/* | ~~部分完成(2026-08-28;针对性测试通过)~~ | +| ~~L-9(部分完成)~~ | ~~`AuthorityResponse.DeletedAt` 已改为 `json:"-"`,且 integration 配置字段已迁移为 DTO 自有类型并在 service 边界映射。~~ ID 类型分叉涉及现有 handler/usecase/数据库键类型的兼容迁移;`ErrorRecordMutationRequest` 的指针字段用于区分省略与显式空值且已有测试,原建议不成立,保留。 | ~~service/dto/*~~ | ~~部分完成(2026-08-28;针对性测试通过)~~ | | L-10 | 错误体系双轨:errors.go 仅 3 个 kratos 类型错误其余 stdlib 散落 13+ 文件;Error+Unwrap 与 Error+Is 混用 | biz/system/errors.go 等 | 二轮 | | L-12 | 校验双轨制:handler 手工 if 与 dto binding 标签混用 | server/handler/* + service/dto/* | 一/三轮 | diff --git a/internal/config/clone.go b/internal/config/clone.go index 0a856fc..29ef351 100644 --- a/internal/config/clone.go +++ b/internal/config/clone.go @@ -5,7 +5,6 @@ package config // serializer to hand out a snapshot a caller may safely mutate. Each function // returns nil for a nil input. func Clone(value *Config) *Config { return cloneConfig(value) } -func CloneData(value *Data) *Data { return cloneData(value) } func CloneAdmin(value *Admin) *Admin { return cloneAdmin(value) } func CloneDatabase(value *Database) *Database { return clonePtr(value) } func CloneStorage(value *Storage) *Storage { return cloneStorage(value) } diff --git a/internal/integration/payment/gopay_test.go b/internal/integration/payment/gopay_test.go index a137d50..ef2cc97 100644 --- a/internal/integration/payment/gopay_test.go +++ b/internal/integration/payment/gopay_test.go @@ -21,8 +21,6 @@ import ( "strings" "testing" - "kra/internal/paymentkit" - gopayAlipay "github.com/go-pay/gopay/alipay" gopayDouyin "github.com/go-pay/gopay/douyin" gopayWechat "github.com/go-pay/gopay/wechat" @@ -177,7 +175,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { t.Errorf("read request: %v", err) return } - requestValues, err = paymentkit.XMLValues(body) + requestValues, err = testXMLValues(body) if err != nil { t.Errorf("parse request: %v", err) return @@ -190,7 +188,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { } response := map[string]string{"return_code": "SUCCESS", "return_msg": "OK", "result_code": "SUCCESS", "prepay_id": "prepay"} response["sign"] = wechatV2Sign(response, key, "MD5") - _, _ = w.Write(paymentkit.XMLEncode(response)) + _, _ = w.Write(testXMLEncode(response)) })) defer server.Close() @@ -219,7 +217,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { if callbackValues["sign"] != originalSign { t.Fatal("verification mutated source values") } - callbackBody := paymentkit.XMLEncode(callbackValues) + callbackBody := testXMLEncode(callbackValues) callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &bizpayment.PaymentCallback{Body: callbackBody}, map[string]any{ "app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key, }) @@ -243,7 +241,7 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) { t.Errorf("read request: %v", err) return } - requestValues, err = paymentkit.XMLValues(body) + requestValues, err = testXMLValues(body) if err != nil { t.Errorf("parse request: %v", err) return @@ -263,7 +261,7 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) { "transaction_id": "WX-TRANSACTION-1", "total_fee": "123", "cash_fee": "123", "fee_type": "CNY", } response["sign"] = wechatV2Sign(response, key, "MD5") - _, _ = w.Write(paymentkit.XMLEncode(response)) + _, _ = w.Write(testXMLEncode(response)) })) defer server.Close() @@ -344,7 +342,7 @@ func TestWechatV2MicropayRejectsInvalidSuccessResponse(t *testing.T) { } tc.mutate(response) response["sign"] = wechatV2Sign(response, key, "MD5") - _, _ = w.Write(paymentkit.XMLEncode(response)) + _, _ = w.Write(testXMLEncode(response)) })) defer server.Close() diff --git a/internal/integration/payment/qq_test.go b/internal/integration/payment/qq_test.go index 56bafd3..918feab 100644 --- a/internal/integration/payment/qq_test.go +++ b/internal/integration/payment/qq_test.go @@ -12,8 +12,6 @@ import ( "net/http/httptest" "testing" - "kra/internal/paymentkit" - "github.com/go-pay/gopay" gopayQQ "github.com/go-pay/gopay/qq" ) @@ -55,7 +53,7 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) { http.Error(w, "invalid XML", http.StatusBadRequest) return } - values, err := paymentkit.XMLValues(body) + values, err := testXMLValues(body) if err != nil { t.Errorf("parse request XML: %v", err) http.Error(w, "invalid XML", http.StatusBadRequest) @@ -81,7 +79,7 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) { for key, value := range response { encoded[key] = fmt.Sprint(value) } - _, _ = w.Write(paymentkit.XMLEncode(encoded)) + _, _ = w.Write(testXMLEncode(encoded)) })) result, err := (&qqAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ diff --git a/internal/paymentkit/xml.go b/internal/integration/payment/xml_test_helpers_test.go similarity index 84% rename from internal/paymentkit/xml.go rename to internal/integration/payment/xml_test_helpers_test.go index 60dd4fd..696d35e 100644 --- a/internal/paymentkit/xml.go +++ b/internal/integration/payment/xml_test_helpers_test.go @@ -1,4 +1,4 @@ -package paymentkit +package payment import ( "bytes" @@ -8,12 +8,12 @@ import ( "strings" ) -type xmlValue struct { +type testXMLValue struct { XMLName xml.Name Value string `xml:",chardata"` } -func XMLValues(raw []byte) (map[string]string, error) { +func testXMLValues(raw []byte) (map[string]string, error) { decoder := xml.NewDecoder(bytes.NewReader(raw)) result := map[string]string{} for { @@ -28,7 +28,7 @@ func XMLValues(raw []byte) (map[string]string, error) { if !ok || start.Name.Local == "xml" { continue } - var value xmlValue + var value testXMLValue if err = decoder.DecodeElement(&value, &start); err != nil { return nil, err } @@ -38,7 +38,7 @@ func XMLValues(raw []byte) (map[string]string, error) { } } -func XMLEncode(values map[string]string) []byte { +func testXMLEncode(values map[string]string) []byte { var b strings.Builder b.WriteString("") keys := make([]string, 0, len(values)) diff --git a/internal/paymentkit/json.go b/internal/paymentkit/json.go index 98bdf0e..8249ad8 100644 --- a/internal/paymentkit/json.go +++ b/internal/paymentkit/json.go @@ -15,14 +15,6 @@ func JSONObject(raw []byte) map[string]any { return object } -func NestedString(value any, keys ...string) string { - found := nestedValue(value, keys...) - if found == nil { - return "" - } - return strings.TrimSpace(fmt.Sprint(found)) -} - func StringAtPath(value any, path string) string { found := ValueAtPath(value, path) if found == nil { @@ -46,21 +38,3 @@ func ValueAtPath(value any, path string) any { } return current } - -func nestedValue(value any, keys ...string) any { - object, ok := value.(map[string]any) - if !ok { - return nil - } - for _, key := range keys { - if found, exists := object[key]; exists && found != nil && fmt.Sprint(found) != "" { - return found - } - } - for _, nestedKey := range []string{"data", "result", "order", "transaction", "response"} { - if found := nestedValue(object[nestedKey], keys...); found != nil { - return found - } - } - return nil -} diff --git a/internal/paymentkit/json_test.go b/internal/paymentkit/json_test.go index fcb0bc8..262b23b 100644 --- a/internal/paymentkit/json_test.go +++ b/internal/paymentkit/json_test.go @@ -2,10 +2,10 @@ package paymentkit import "testing" -func TestNestedAndPathValues(t *testing.T) { +func TestJSONObjectAndPathValues(t *testing.T) { object := JSONObject([]byte(`{"data":{"status":"SUCCESS","amount":100}}`)) - if got := NestedString(object, "status"); got != "SUCCESS" { - t.Fatalf("NestedString = %q", got) + if object == nil { + t.Fatal("JSONObject returned nil") } if got := StringAtPath(object, "data.amount"); got != "100" { t.Fatalf("StringAtPath = %q", got) diff --git a/internal/service/dto/integration_config.go b/internal/service/dto/integration_config.go index c3d74c4..a91e190 100644 --- a/internal/service/dto/integration_config.go +++ b/internal/service/dto/integration_config.go @@ -2,7 +2,6 @@ package dto import ( "encoding/json" - integrationbiz "kra/internal/biz/integration" ) type IntegrationConfigRequest struct { @@ -11,12 +10,28 @@ type IntegrationConfigRequest struct { } type IntegrationConfigResponse struct { - Kind string `json:"kind"` - Provider string `json:"provider"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Enabled bool `json:"enabled"` - Configured bool `json:"configured"` - Config json.RawMessage `json:"config"` - Fields []integrationbiz.IntegrationConfigField `json:"fields"` + Kind string `json:"kind"` + Provider string `json:"provider"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Enabled bool `json:"enabled"` + Configured bool `json:"configured"` + Config json.RawMessage `json:"config"` + Fields []IntegrationConfigField `json:"fields"` +} + +type IntegrationConfigField struct { + Key string `json:"key"` + Label string `json:"label"` + Type string `json:"type"` + Required bool `json:"required"` + Secret bool `json:"secret"` + Placeholder string `json:"placeholder,omitempty"` + Description string `json:"description,omitempty"` + Options []IntegrationConfigOption `json:"options,omitempty"` +} + +type IntegrationConfigOption struct { + Label string `json:"label"` + Value any `json:"value"` } diff --git a/internal/service/integration/integration_config.go b/internal/service/integration/integration_config.go index 6571d55..032691e 100644 --- a/internal/service/integration/integration_config.go +++ b/internal/service/integration/integration_config.go @@ -38,7 +38,7 @@ func (s *IntegrationConfigService) List(ctx context.Context, kind string) ([]*dt enabled = config.Enabled delete(byProvider, definition.Provider) } - result = append(result, &dto.IntegrationConfigResponse{Kind: definition.Kind, Provider: definition.Provider, Name: definition.Name, Description: definition.Description, Enabled: enabled, Configured: configured, Config: values, Fields: definition.Fields}) + result = append(result, &dto.IntegrationConfigResponse{Kind: definition.Kind, Provider: definition.Provider, Name: definition.Name, Description: definition.Description, Enabled: enabled, Configured: configured, Config: values, Fields: integrationFieldsDTO(definition.Fields)}) } for _, config := range configs { if _, exists := byProvider[config.Provider]; !exists { @@ -59,7 +59,7 @@ func (s *IntegrationConfigService) Find(ctx context.Context, kind, provider stri if found { response.Name = definition.Name response.Description = definition.Description - response.Fields = definition.Fields + response.Fields = integrationFieldsDTO(definition.Fields) response.Config = mergeConfigJSON(definition.Defaults, config.Values) } return response, nil @@ -90,3 +90,21 @@ func mergeConfigJSON(defaults map[string]any, raw json.RawMessage) json.RawMessa encoded, _ := json.Marshal(values) return encoded } + +func integrationFieldsDTO(fields []integrationbiz.IntegrationConfigField) []dto.IntegrationConfigField { + if fields == nil { + return nil + } + out := make([]dto.IntegrationConfigField, 0, len(fields)) + for _, field := range fields { + item := dto.IntegrationConfigField{Key: field.Key, Label: field.Label, Type: field.Type, Required: field.Required, Secret: field.Secret, Placeholder: field.Placeholder, Description: field.Description} + if field.Options != nil { + item.Options = make([]dto.IntegrationConfigOption, 0, len(field.Options)) + for _, option := range field.Options { + item.Options = append(item.Options, dto.IntegrationConfigOption{Label: option.Label, Value: option.Value}) + } + } + out = append(out, item) + } + return out +}