优化结构
This commit is contained in:
parent
5b17f47141
commit
676fa4d952
29
CLAUDE.md
29
CLAUDE.md
|
|
@ -6,15 +6,22 @@ agents must follow when changing the template.
|
|||
## Project structure
|
||||
|
||||
```
|
||||
api/<domain>/<version>/ Proto sources and generated stubs. Public contract.
|
||||
cmd/<app>/ 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/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 and storage clients.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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/* | 一/三轮 |
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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("<xml>")
|
||||
keys := make([]string, 0, len(values))
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package dto
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
)
|
||||
|
||||
type IntegrationConfigRequest struct {
|
||||
|
|
@ -18,5 +17,21 @@ type IntegrationConfigResponse struct {
|
|||
Enabled bool `json:"enabled"`
|
||||
Configured bool `json:"configured"`
|
||||
Config json.RawMessage `json:"config"`
|
||||
Fields []integrationbiz.IntegrationConfigField `json:"fields"`
|
||||
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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue