From 953a22424584d8594e6c0f24e9bca6268e361c4e Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 3 Sep 2026 10:40:02 +0800 Subject: [PATCH] refactor(arch): decouple private imports, enforce contracts and comply with cordis architecture --- backend/docs/docs.go | 145 ++++++------ backend/docs/swagger.json | 142 ++++++------ backend/docs/swagger.yaml | 98 ++++---- .../domain/cloudflare/reconcile_test.go | 9 +- .../server/domain/cloudflare/routers_test.go | 3 +- .../server/domain/dashboard/logics_test.go | 9 +- .../domain/fleet/agent/middleware_test.go | 11 +- .../domain/fleet/agent/waf_ip_group_test.go | 11 +- .../server/domain/fleet/async_tasks_test.go | 5 +- .../domain/fleet/flared/middleware_test.go | 5 +- .../domain/fleet/flared/observability_test.go | 7 +- .../fleet/integration/agent_protocol_test.go | 5 +- .../fleet/integration/core_chain_test.go | 10 +- .../domain/fleet/integration/security_test.go | 10 +- .../server/domain/fleet/node/logics_test.go | 13 +- .../server/domain/fleet/relay/logics_test.go | 11 +- .../domain/fleet/relay/middleware_test.go | 5 +- .../observability/chwriter/live_ch_test.go | 1 - .../observability/log_db_switch_test.go | 37 ++- .../server/domain/option/logics_test.go | 12 +- .../domain/option/uptimekuma/sync_test.go | 18 +- .../pages/github_source_identity_test.go | 4 +- .../server/domain/pages/github_source_test.go | 25 +-- .../plugins/server/domain/pages/helpers.go | 13 +- .../plugins/server/domain/pages/logics.go | 6 +- .../server/domain/pages/logics_test.go | 56 ++--- .../server/domain/pages/package_metadata.go | 2 +- .../domain/pages/package_metadata_test.go | 4 +- .../server/domain/pages/rebind_test.go | 8 +- .../domain/pages/routers_source_test.go | 6 +- .../domain/pages/source_orphan_cleanup.go | 21 +- .../pages/source_orphan_cleanup_test.go | 48 ++-- .../domain/pages/source_runtime_test.go | 17 +- .../domain/pages/source_scanner_test.go | 25 +-- .../server/domain/pages/source_sync_test.go | 57 +++-- .../server/domain/pages/source_test.go | 23 +- .../domain/site/apply_log/logics_test.go | 7 +- .../certificate_snapshot_test.go | 11 +- .../domain/site/config_version/logics_test.go | 13 +- .../origin_error_page_snapshot_test.go | 26 +-- .../config_version/pages_snapshot_test.go | 3 +- .../config_version/waf_graph_snapshot_test.go | 11 +- .../server/domain/site/origin/logics_test.go | 6 +- .../domain/site/proxy_route/logics_test.go | 15 +- .../domain/site/zone/legacy_import_test.go | 8 +- .../server/domain/site/zone/logics_test.go | 5 +- .../plugins/server/domain/tls/logics_test.go | 9 +- .../server/domain/tls/ssl_renew_test.go | 3 +- .../server/domain/waf/ip_group_sync_test.go | 5 +- .../plugins/server/domain/waf/logics_test.go | 5 +- .../server/domain/waf/rule_logics_test.go | 5 +- .../server/kernel/geoip/runtime_test.go | 8 +- .../kernel/model/analytics/user_access_log.go | 32 ++- .../server/kernel/model/platform_aliases.go | 189 ++++++++++++---- .../server/kernel/model/system_configs.go | 48 +++- .../server/kernel/ofupload/ofupload.go | 165 ++++++++++---- .../plugins/server/kernel/ofupload/remove.go | 40 ---- .../repository/analytics/clickhouse_stats.go | 29 ++- .../kernel/repository/analytics/conn.go | 75 +++++++ .../repository/analytics/node_access_log.go | 7 +- .../analytics/node_access_log_test.go | 5 +- .../analytics/node_access_log_writer.go | 9 +- .../analytics/node_observability.go | 7 +- .../node_observability_latest_test.go | 18 +- .../analytics/node_observability_test.go | 5 +- .../analytics/node_observability_writer.go | 30 +-- .../repository/analytics/user_access_log.go | 84 +++---- .../plugins/server/kernel/repository/db.go | 140 ++++++++++++ .../repository/logstore/cleanup_test.go | 7 +- .../repository/logstore/clickhouse_store.go | 24 +- .../logstore/clickhouse_store_test.go | 9 +- .../kernel/repository/logstore/provider.go | 16 +- .../repository/openflare_access_log_test.go | 5 +- .../repository/openflare_acme_account.go | 9 +- .../kernel/repository/openflare_apply_log.go | 17 +- .../repository/openflare_apply_log_test.go | 10 +- .../kernel/repository/openflare_cloudflare.go | 45 ++-- .../repository/openflare_cloudflare_test.go | 5 +- .../repository/openflare_config_version.go | 19 +- .../repository/openflare_dns_account.go | 11 +- .../kernel/repository/openflare_node.go | 23 +- .../repository/openflare_observability.go | 15 +- .../kernel/repository/openflare_origin.go | 23 +- .../kernel/repository/openflare_pages.go | 21 +- .../repository/openflare_pages_cleanup.go | 14 +- .../openflare_pages_cleanup_test.go | 65 +++--- .../repository/openflare_pages_source.go | 29 ++- .../repository/openflare_proxy_route.go | 13 +- .../server/kernel/repository/openflare_tls.go | 17 +- .../server/kernel/repository/openflare_waf.go | 3 +- .../repository/openflare_waf_bindings_test.go | 8 +- .../repository/openflare_waf_graph_test.go | 6 +- .../kernel/repository/openflare_zone.go | 33 ++- .../kernel/repository/openflare_zone_test.go | 6 +- .../server/kernel/repository/system_config.go | 138 ++++++++---- .../server/kernel/repository/system_user.go | 35 ++- .../kernel/repository/system_user_test.go | 51 ++--- .../server/kernel/runtimeconfig/runtime.go | 18 +- .../server/kernel/testhelper/mock_storage.go | 113 ++++++++++ .../server/kernel/testhelper/noop_task.go | 58 ++++- .../server/kernel/testhelper/stub_auth.go | 28 ++- .../server/kernel/testhelper/test_helper.go | 210 ++++++++++++++++-- backend/openflare/plugins/server/plugin.go | 34 +-- docs/changelog/index.md | 1 + docs/swagger.json | 142 ++++++------ docs/swagger.yaml | 98 ++++---- scripts/check_cordis_architecture.sh | 7 + 107 files changed, 2028 insertions(+), 1263 deletions(-) delete mode 100644 backend/openflare/plugins/server/kernel/ofupload/remove.go create mode 100644 backend/openflare/plugins/server/kernel/repository/analytics/conn.go create mode 100644 backend/openflare/plugins/server/kernel/repository/db.go create mode 100644 backend/openflare/plugins/server/kernel/testhelper/mock_storage.go diff --git a/backend/docs/docs.go b/backend/docs/docs.go index f98d71ad..3a69627f 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -1,6 +1,3 @@ -// Copyright 2026 Arctel.net -// SPDX-License-Identifier: Apache-2.0 - // Package docs Code generated by swaggo/swag. DO NOT EDIT package docs @@ -2678,7 +2675,7 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -2740,7 +2737,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -2822,7 +2819,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -3011,7 +3008,7 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -3149,7 +3146,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -3229,7 +3226,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -15243,36 +15240,6 @@ const docTemplate = `{ } } }, - "Wavelet_plugins_domain_admin_model.Schedule": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "cron": { - "type": "string" - }, - "id": { - "type": "string", - "example": "0" - }, - "is_active": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "payload": { - "type": "string" - }, - "task_type": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, "Wavelet_plugins_domain_admin_model.SystemConfig": { "type": "object", "properties": { @@ -15359,41 +15326,6 @@ const docTemplate = `{ } } }, - "Wavelet_plugins_domain_admin_model.Template": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "is_system": { - "type": "boolean" - }, - "key": { - "type": "string" - }, - "name": { - "type": "string" - }, - "subject": { - "type": "string" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, "agent.ActiveConfigMeta": { "type": "object", "properties": { @@ -17869,6 +17801,36 @@ const docTemplate = `{ } } }, + "model.Schedule": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "cron": { + "type": "string" + }, + "id": { + "type": "string", + "example": "0" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "payload": { + "type": "string" + }, + "task_type": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "model.SystemStatusResponse": { "type": "object", "properties": { @@ -18038,6 +18000,41 @@ const docTemplate = `{ "TaskExecutionStatusFailed" ] }, + "model.Template": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_system": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "model.TestSMTPRequest": { "type": "object", "required": [ diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 654f2c02..dafc71ac 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -2668,7 +2668,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -2730,7 +2730,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -2812,7 +2812,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -3001,7 +3001,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -3139,7 +3139,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -3219,7 +3219,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -15233,36 +15233,6 @@ } } }, - "Wavelet_plugins_domain_admin_model.Schedule": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "cron": { - "type": "string" - }, - "id": { - "type": "string", - "example": "0" - }, - "is_active": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "payload": { - "type": "string" - }, - "task_type": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, "Wavelet_plugins_domain_admin_model.SystemConfig": { "type": "object", "properties": { @@ -15349,41 +15319,6 @@ } } }, - "Wavelet_plugins_domain_admin_model.Template": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "is_system": { - "type": "boolean" - }, - "key": { - "type": "string" - }, - "name": { - "type": "string" - }, - "subject": { - "type": "string" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, "agent.ActiveConfigMeta": { "type": "object", "properties": { @@ -17859,6 +17794,36 @@ } } }, + "model.Schedule": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "cron": { + "type": "string" + }, + "id": { + "type": "string", + "example": "0" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "payload": { + "type": "string" + }, + "task_type": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "model.SystemStatusResponse": { "type": "object", "properties": { @@ -18028,6 +17993,41 @@ "TaskExecutionStatusFailed" ] }, + "model.Template": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_system": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "model.TestSMTPRequest": { "type": "object", "required": [ diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index c0a8053e..0c20ed10 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -156,26 +156,6 @@ definitions: type: type: string type: object - Wavelet_plugins_domain_admin_model.Schedule: - properties: - created_at: - type: string - cron: - type: string - id: - example: "0" - type: string - is_active: - type: boolean - name: - type: string - payload: - type: string - task_type: - type: string - updated_at: - type: string - type: object Wavelet_plugins_domain_admin_model.SystemConfig: properties: created_at: @@ -233,29 +213,6 @@ definitions: updated_at: type: string type: object - Wavelet_plugins_domain_admin_model.Template: - properties: - content: - type: string - created_at: - type: string - description: - type: string - id: - type: integer - is_system: - type: boolean - key: - type: string - name: - type: string - subject: - type: string - type: - type: string - updated_at: - type: string - type: object agent.ActiveConfigMeta: properties: checksum: @@ -1890,6 +1847,26 @@ definitions: value: type: string type: object + model.Schedule: + properties: + created_at: + type: string + cron: + type: string + id: + example: "0" + type: string + is_active: + type: boolean + name: + type: string + payload: + type: string + task_type: + type: string + updated_at: + type: string + type: object model.SystemStatusResponse: properties: alloc: @@ -2004,6 +1981,29 @@ definitions: - TaskExecutionStatusRunning - TaskExecutionStatusSucceeded - TaskExecutionStatusFailed + model.Template: + properties: + content: + type: string + created_at: + type: string + description: + type: string + id: + type: integer + is_system: + type: boolean + key: + type: string + name: + type: string + subject: + type: string + type: + type: string + updated_at: + type: string + type: object model.TestSMTPRequest: properties: smtp_host: @@ -5873,7 +5873,7 @@ paths: - properties: data: items: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule' + $ref: '#/definitions/model.Schedule' type: array type: object "401": @@ -5910,7 +5910,7 @@ paths: - $ref: '#/definitions/response.Any' - properties: data: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule' + $ref: '#/definitions/model.Schedule' type: object "400": description: Cron 表达式无效、异步任务类型不存在或参数错误 @@ -6001,7 +6001,7 @@ paths: - $ref: '#/definitions/response.Any' - properties: data: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule' + $ref: '#/definitions/model.Schedule' type: object "400": description: Cron 表达式无效、参数错误 @@ -6072,7 +6072,7 @@ paths: - properties: data: items: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template' + $ref: '#/definitions/model.Template' type: array type: object "401": @@ -6200,7 +6200,7 @@ paths: - $ref: '#/definitions/response.Any' - properties: data: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template' + $ref: '#/definitions/model.Template' type: object "401": description: 未登录 @@ -6249,7 +6249,7 @@ paths: - $ref: '#/definitions/response.Any' - properties: data: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template' + $ref: '#/definitions/model.Template' type: object "400": description: 参数错误 diff --git a/backend/openflare/plugins/server/domain/cloudflare/reconcile_test.go b/backend/openflare/plugins/server/domain/cloudflare/reconcile_test.go index b4545ef0..7a21d841 100644 --- a/backend/openflare/plugins/server/domain/cloudflare/reconcile_test.go +++ b/backend/openflare/plugins/server/domain/cloudflare/reconcile_test.go @@ -11,7 +11,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/credential" "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/repository" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "gorm.io/gorm" @@ -60,8 +59,8 @@ func setupCloudflareLogicDB(t *testing.T) (context.Context, uint) { ); err != nil { t.Fatalf("AutoMigrate() error = %v", err) } - db.SetDB(conn) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(conn) + t.Cleanup(func() { repository.SetDBForTest(nil) }) ctx := context.Background() sealed, err := credential.Seal(`{"api_token":"test-token"}`) if err != nil { @@ -143,11 +142,11 @@ func TestCreateMemberCopiesGroupDefaultProxied(t *testing.T) { t.Fatalf("GetCFPointingGroup() error = %v", err) } zone := model.Zone{Domain: "example.net"} - if err := db.DB(ctx).Create(&zone).Error; err != nil { + if err := repository.DB(ctx).Create(&zone).Error; err != nil { t.Fatalf("Create(zone) error = %v", err) } domain := model.ZoneDomain{ZoneID: zone.ID, Domain: "www.example.net"} - if err := db.DB(ctx).Create(&domain).Error; err != nil { + if err := repository.DB(ctx).Create(&domain).Error; err != nil { t.Fatalf("Create(domain) error = %v", err) } restore := SetDispatchTaskForTest(func(context.Context, string, []byte, string) (string, error) { return "task-1", nil }) diff --git a/backend/openflare/plugins/server/domain/cloudflare/routers_test.go b/backend/openflare/plugins/server/domain/cloudflare/routers_test.go index 165b3efc..e2123c35 100644 --- a/backend/openflare/plugins/server/domain/cloudflare/routers_test.go +++ b/backend/openflare/plugins/server/domain/cloudflare/routers_test.go @@ -11,7 +11,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/pkg/response" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" ) @@ -58,7 +57,7 @@ func TestGetGroupWithOrphanedMemberHealsAndSucceeds(t *testing.T) { } // Simulate orphaned member by deleting the ZoneDomain directly - if err := db.DB(ctx).Exec("DELETE FROM of_zone_domains WHERE id = ?", member.ZoneDomainID).Error; err != nil { + if err := repository.DB(ctx).Exec("DELETE FROM of_zone_domains WHERE id = ?", member.ZoneDomainID).Error; err != nil { t.Fatalf("DELETE FROM of_zone_domains error = %v", err) } diff --git a/backend/openflare/plugins/server/domain/dashboard/logics_test.go b/backend/openflare/plugins/server/domain/dashboard/logics_test.go index 67622787..0e747c1a 100644 --- a/backend/openflare/plugins/server/domain/dashboard/logics_test.go +++ b/backend/openflare/plugins/server/domain/dashboard/logics_test.go @@ -12,7 +12,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/testhelper" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -28,10 +27,10 @@ func setupDashboardTestDB(t *testing.T) func() { require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{})) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) testhelper.SetupLogStoresForTest(t) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } @@ -43,7 +42,7 @@ func TestGetOverviewStructure(t *testing.T) { now := time.Now().UTC() lastSeen := now.Add(-15 * time.Second) // within default 60s offline threshold - require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{ + require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{ NodeID: "node-dashboard-1", Name: "Edge 1", IP: "10.0.0.1", @@ -52,7 +51,7 @@ func TestGetOverviewStructure(t *testing.T) { CurrentVersion: "v1.0.0", LastSeenAt: &lastSeen, }).Error) - require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{ + require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{ NodeID: "node-dashboard-2", Name: "Edge 2", IP: "10.0.0.2", diff --git a/backend/openflare/plugins/server/domain/fleet/agent/middleware_test.go b/backend/openflare/plugins/server/domain/fleet/agent/middleware_test.go index 0539f5a1..b479e0e0 100644 --- a/backend/openflare/plugins/server/domain/fleet/agent/middleware_test.go +++ b/backend/openflare/plugins/server/domain/fleet/agent/middleware_test.go @@ -15,7 +15,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/testhelper" "Wavelet/pkg/response" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" @@ -36,11 +35,11 @@ func setupAgentAuthTestDB(t *testing.T) func() { &model.SystemConfig{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) tokenCache.reset() return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) tokenCache.reset() } } @@ -51,7 +50,7 @@ func TestAuthenticateAccessToken(t *testing.T) { ctx := context.Background() now := time.Now() - require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{ + require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{ NodeID: "node-auth-1", Name: "edge", AccessToken: "valid-agent-token", @@ -98,7 +97,7 @@ func TestAgentAuthMiddleware(t *testing.T) { ctx := context.Background() now := time.Now() - require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{ + require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{ NodeID: "node-mw-1", Name: "edge", AccessToken: "middleware-token", @@ -145,7 +144,7 @@ func TestAgentRegisterAuthMiddleware(t *testing.T) { ctx := context.Background() now := time.Now() - require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{ + require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{ NodeID: "node-register-1", Name: "edge", AccessToken: "existing-node-token", diff --git a/backend/openflare/plugins/server/domain/fleet/agent/waf_ip_group_test.go b/backend/openflare/plugins/server/domain/fleet/agent/waf_ip_group_test.go index 52570f6a..e68b82a2 100644 --- a/backend/openflare/plugins/server/domain/fleet/agent/waf_ip_group_test.go +++ b/backend/openflare/plugins/server/domain/fleet/agent/waf_ip_group_test.go @@ -14,7 +14,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/share/protocol" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -34,9 +33,9 @@ func setupWAFIPGroupTestDB(t *testing.T) func() { &model.ConfigVersion{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } @@ -60,7 +59,7 @@ func seedActiveConfigWithWAFIPGroup(t *testing.T, ctx context.Context, ipGroupID snapshotJSON, err := json.Marshal(snapshot) require.NoError(t, err) - require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{ + require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{ Version: "20260618-001", SnapshotJSON: string(snapshotJSON), Checksum: "test-checksum", @@ -108,7 +107,7 @@ func seedActiveConfigWithWAFGraphIPGroup(t *testing.T, ctx context.Context, ipGr snapshotJSON, err := json.Marshal(snapshot) require.NoError(t, err) - require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{ + require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{ Version: "20260713-graph-001", SnapshotJSON: string(snapshotJSON), Checksum: "graph-test-checksum", @@ -142,7 +141,7 @@ func TestChangedWAFIPGroupsForAgentRejectsMalformedIPMatchConfig(t *testing.T) { defer cleanup() ctx := context.Background() - require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{ + require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{ Version: "20260713-malformed-001", SnapshotJSON: `{"waf":{"rule_groups":[{"id":7,"graph":{"entry":"match","nodes":{` + `"match":{"type":"ip_match","config":{"ip_group_ids":"not-an-array"}}}}}],"bindings":[]}}`, diff --git a/backend/openflare/plugins/server/domain/fleet/async_tasks_test.go b/backend/openflare/plugins/server/domain/fleet/async_tasks_test.go index 100776bc..1ab8f0ef 100644 --- a/backend/openflare/plugins/server/domain/fleet/async_tasks_test.go +++ b/backend/openflare/plugins/server/domain/fleet/async_tasks_test.go @@ -9,7 +9,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/repository" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -23,8 +22,8 @@ func TestUptimeKumaSyncHandlerSkipsWhenDisabled(t *testing.T) { }) require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{})) - db.SetDB(sqliteDB) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(sqliteDB) + t.Cleanup(func() { repository.SetDBForTest(nil) }) ctx := context.Background() require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyUptimeKumaEnabled, "false")) diff --git a/backend/openflare/plugins/server/domain/fleet/flared/middleware_test.go b/backend/openflare/plugins/server/domain/fleet/flared/middleware_test.go index 2206fecb..bcea0711 100644 --- a/backend/openflare/plugins/server/domain/fleet/flared/middleware_test.go +++ b/backend/openflare/plugins/server/domain/fleet/flared/middleware_test.go @@ -13,7 +13,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/pkg/response" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" @@ -30,10 +29,10 @@ func setupFlaredMiddlewareTestDB(t *testing.T) func() { }) require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{})) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } diff --git a/backend/openflare/plugins/server/domain/fleet/flared/observability_test.go b/backend/openflare/plugins/server/domain/fleet/flared/observability_test.go index 741a99db..121d4cfd 100644 --- a/backend/openflare/plugins/server/domain/fleet/flared/observability_test.go +++ b/backend/openflare/plugins/server/domain/fleet/flared/observability_test.go @@ -11,7 +11,6 @@ import ( "Wavelet/openflare/plugins/server/domain/fleet/agent" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -33,11 +32,11 @@ func setupFlaredObservabilityTestDB(t *testing.T) func() { &model.ConfigVersion{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) agent.ResetAuthCacheForTest() return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) agent.ResetAuthCacheForTest() } } @@ -54,7 +53,7 @@ func TestHeartbeatFlaredEmitsHealthEventOnUnhealthy(t *testing.T) { Status: "pending", NodeType: "tunnel_client", } - require.NoError(t, db.DB(ctx).Create(node).Error) + require.NoError(t, repository.DB(ctx).Create(node).Error) _, err := Heartbeat(ctx, node, HeartbeatPayload{ ClientVersion: "v0.2.0", diff --git a/backend/openflare/plugins/server/domain/fleet/integration/agent_protocol_test.go b/backend/openflare/plugins/server/domain/fleet/integration/agent_protocol_test.go index 740c8da4..9ff66037 100644 --- a/backend/openflare/plugins/server/domain/fleet/integration/agent_protocol_test.go +++ b/backend/openflare/plugins/server/domain/fleet/integration/agent_protocol_test.go @@ -14,7 +14,6 @@ import ( ofnode "Wavelet/openflare/plugins/server/domain/fleet/node" "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/testhelper" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" @@ -39,7 +38,7 @@ func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) { &model.ConfigVersion{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) agent.ResetAuthCacheForTest() testhelper.SetupLogStoresForTest(t) @@ -47,7 +46,7 @@ func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) { mountOpenFlareTestRoutes(engine) cleanup := func() { - db.SetDB(nil) + repository.SetDBForTest(nil) agent.ResetAuthCacheForTest() } return engine, cleanup diff --git a/backend/openflare/plugins/server/domain/fleet/integration/core_chain_test.go b/backend/openflare/plugins/server/domain/fleet/integration/core_chain_test.go index db2e403a..4dba4c5f 100644 --- a/backend/openflare/plugins/server/domain/fleet/integration/core_chain_test.go +++ b/backend/openflare/plugins/server/domain/fleet/integration/core_chain_test.go @@ -11,8 +11,8 @@ import ( "Wavelet/openflare/plugins/server/domain/fleet/agent" "Wavelet/openflare/plugins/server/kernel/model" + "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/testhelper" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" @@ -55,7 +55,7 @@ func setupCoreChainTest(t *testing.T) (*gin.Engine, adminSeed, func()) { &model.ZoneDomain{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) agent.ResetAuthCacheForTest() seed, err := seedAdminWithAccessToken(sqliteDB) @@ -65,7 +65,7 @@ func setupCoreChainTest(t *testing.T) (*gin.Engine, adminSeed, func()) { mountOpenFlareTestRoutes(engine) cleanup := func() { - db.SetDB(nil) + repository.SetDBForTest(nil) agent.ResetAuthCacheForTest() } @@ -144,12 +144,12 @@ func TestCoreChainMigrationFlow(t *testing.T) { t.Run("create proxy route linked to origin", func(t *testing.T) { // Create Zone and ZoneDomain directly in the DB zone := model.Zone{Domain: "example.com"} - require.NoError(t, db.DB(context.Background()).Create(&zone).Error) + require.NoError(t, repository.DB(context.Background()).Create(&zone).Error) zoneDomain := model.ZoneDomain{ ZoneID: zone.ID, Domain: "core-chain.example.com", } - require.NoError(t, db.DB(context.Background()).Create(&zoneDomain).Error) + require.NoError(t, repository.DB(context.Background()).Create(&zoneDomain).Error) rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{ "site_name": "core-chain-site", diff --git a/backend/openflare/plugins/server/domain/fleet/integration/security_test.go b/backend/openflare/plugins/server/domain/fleet/integration/security_test.go index 980686b9..c9ca61c7 100644 --- a/backend/openflare/plugins/server/domain/fleet/integration/security_test.go +++ b/backend/openflare/plugins/server/domain/fleet/integration/security_test.go @@ -17,9 +17,9 @@ import ( "time" "Wavelet/openflare/plugins/server/kernel/model" + "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/runtimeconfig" "Wavelet/openflare/plugins/server/kernel/testhelper" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" @@ -51,7 +51,7 @@ func setupSecurityTest(t *testing.T) (*gin.Engine, adminSeed, func()) { &model.SystemConfig{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) seed, err := seedAdminWithAccessToken(sqliteDB) require.NoError(t, err) @@ -64,7 +64,7 @@ func setupSecurityTest(t *testing.T) (*gin.Engine, adminSeed, func()) { cleanup := func() { runtimeconfig.Set(previous) - db.SetDB(nil) + repository.SetDBForTest(nil) } return engine, seed, cleanup @@ -205,12 +205,12 @@ func TestSecurityWAFTLSMigrationFlow(t *testing.T) { t.Run("create proxy route for WAF binding", func(t *testing.T) { // Create Zone and ZoneDomain directly in the DB routeZone := model.Zone{Domain: "example-route.com"} - require.NoError(t, db.DB(context.Background()).Create(&routeZone).Error) + require.NoError(t, repository.DB(context.Background()).Create(&routeZone).Error) routeZoneDomain := model.ZoneDomain{ ZoneID: routeZone.ID, Domain: "route.example-route.com", } - require.NoError(t, db.DB(context.Background()).Create(&routeZoneDomain).Error) + require.NoError(t, repository.DB(context.Background()).Create(&routeZoneDomain).Error) rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{ "site_name": "security-site", diff --git a/backend/openflare/plugins/server/domain/fleet/node/logics_test.go b/backend/openflare/plugins/server/domain/fleet/node/logics_test.go index edd7adee..233e7e85 100644 --- a/backend/openflare/plugins/server/domain/fleet/node/logics_test.go +++ b/backend/openflare/plugins/server/domain/fleet/node/logics_test.go @@ -15,7 +15,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/testhelper" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -40,13 +39,15 @@ func setupNodeTestDB(t *testing.T) func() { &model.OpenFlareNode{}, &model.SystemConfig{}, &model.OpenFlareApplyLog{}, + &model.OpenFlareNodeSystemProfile{}, + &model.OpenFlareHealthEvent{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) testhelper.SetupLogStoresForTest(t) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } @@ -174,7 +175,7 @@ func TestListNodesWithApplyLogMetadata(t *testing.T) { require.NoError(t, err) applyAt := time.Now().UTC().Truncate(time.Second) - require.NoError(t, db.DB(ctx).Create(&model.OpenFlareApplyLog{ + require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareApplyLog{ NodeID: created.NodeID, Version: "20260618-001", Result: "success", @@ -280,7 +281,7 @@ func TestRequestOpenrestyRestart(t *testing.T) { func seedActiveConfigVersion(t *testing.T, ctx context.Context) { t.Helper() - conn := db.DB(ctx) + conn := repository.DB(ctx) require.NotNil(t, conn) require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{})) require.NoError(t, conn.Create(&model.ConfigVersion{ @@ -311,7 +312,7 @@ func TestRequestForceSyncRequiresActiveConfig(t *testing.T) { cleanup := setupNodeTestDB(t) defer cleanup() ctx := context.Background() - conn := db.DB(ctx) + conn := repository.DB(ctx) require.NotNil(t, conn) require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{})) diff --git a/backend/openflare/plugins/server/domain/fleet/relay/logics_test.go b/backend/openflare/plugins/server/domain/fleet/relay/logics_test.go index 239287a6..0b2c6e57 100644 --- a/backend/openflare/plugins/server/domain/fleet/relay/logics_test.go +++ b/backend/openflare/plugins/server/domain/fleet/relay/logics_test.go @@ -14,7 +14,6 @@ import ( "Wavelet/openflare/plugins/server/domain/fleet/agent" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -38,12 +37,12 @@ func setupRelayTestDB(t *testing.T) func() { &model.OpenFlareNodeObservationFrps{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) agent.ResetAuthCacheForTest() testhelper.SetupLogStoresForTest(t) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) agent.ResetAuthCacheForTest() } } @@ -63,7 +62,7 @@ func TestHeartbeatPayloadBindingAndFrpsObservationInsert(t *testing.T) { NodeType: "tunnel_relay", RelayStatus: "unknown", } - require.NoError(t, db.DB(ctx).Create(node).Error) + require.NoError(t, repository.DB(ctx).Create(node).Error) proxies := []ProxyStat{ { @@ -103,7 +102,7 @@ func TestHeartbeatPayloadBindingAndFrpsObservationInsert(t *testing.T) { require.NoError(t, err) var stored model.OpenFlareNode - require.NoError(t, db.DB(ctx).Where("node_id = ?", node.NodeID).First(&stored).Error) + require.NoError(t, repository.DB(ctx).Where("node_id = ?", node.NodeID).First(&stored).Error) assert.Equal(t, "online", stored.Status) assert.Equal(t, "healthy", stored.RelayStatus) assert.Equal(t, "203.0.113.9", stored.IP) @@ -147,7 +146,7 @@ func TestHeartbeatRelayReconcilesFrpsUnhealthyEvent(t *testing.T) { NodeType: "tunnel_relay", RelayStatus: "healthy", } - require.NoError(t, db.DB(ctx).Create(node).Error) + require.NoError(t, repository.DB(ctx).Create(node).Error) _, err := Heartbeat(ctx, node, HeartbeatPayload{ Version: "v0.1.0", diff --git a/backend/openflare/plugins/server/domain/fleet/relay/middleware_test.go b/backend/openflare/plugins/server/domain/fleet/relay/middleware_test.go index cb850582..47f227c2 100644 --- a/backend/openflare/plugins/server/domain/fleet/relay/middleware_test.go +++ b/backend/openflare/plugins/server/domain/fleet/relay/middleware_test.go @@ -13,7 +13,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/pkg/response" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" @@ -30,10 +29,10 @@ func setupRelayMiddlewareTestDB(t *testing.T) func() { }) require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{})) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } diff --git a/backend/openflare/plugins/server/domain/observability/chwriter/live_ch_test.go b/backend/openflare/plugins/server/domain/observability/chwriter/live_ch_test.go index 19f24b94..f49035a1 100644 --- a/backend/openflare/plugins/server/domain/observability/chwriter/live_ch_test.go +++ b/backend/openflare/plugins/server/domain/observability/chwriter/live_ch_test.go @@ -14,7 +14,6 @@ import ( "Wavelet/openflare/plugins/server/domain/observability/chwriter" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // Run with Docker ClickHouse + config.yaml: diff --git a/backend/openflare/plugins/server/domain/observability/log_db_switch_test.go b/backend/openflare/plugins/server/domain/observability/log_db_switch_test.go index b3ac3a2c..5d9ed98b 100644 --- a/backend/openflare/plugins/server/domain/observability/log_db_switch_test.go +++ b/backend/openflare/plugins/server/domain/observability/log_db_switch_test.go @@ -23,7 +23,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/repository/logstore" "Wavelet/openflare/plugins/server/kernel/runtimeconfig" - db "Wavelet/plugins/infra/database" ) var logDBSwitchDBSeq int64 @@ -57,13 +56,13 @@ func TestCopyAccessLogsPreservesIDs(t *testing.T) { srcDB := newLogDBSwitchDB(t) dstDB := newLogDBSwitchDB(t) - db.SetDB(srcDB) + repository.SetDBForTest(srcDB) src, err := logstore.Active(ctx) // 无 reader 时按 seed 规则解析为 sqlite require.NoError(t, err) - db.SetDB(dstDB) + repository.SetDBForTest(dstDB) dst, err := logstore.BuildForMigration(ctx, "sqlite") require.NoError(t, err) - t.Cleanup(func() { db.SetDB(nil) }) + t.Cleanup(func() { repository.SetDBForTest(nil) }) now := time.Now().UTC() rows := []analyticsmodel.NodeAccessLog{ @@ -103,13 +102,13 @@ func TestCopyUserAccessLogsPreservesIDs(t *testing.T) { srcDB := newLogDBSwitchDB(t) dstDB := newLogDBSwitchDB(t) - db.SetDB(srcDB) + repository.SetDBForTest(srcDB) src, err := logstore.Active(ctx) require.NoError(t, err) - db.SetDB(dstDB) + repository.SetDBForTest(dstDB) dst, err := logstore.BuildForMigration(ctx, "sqlite") require.NoError(t, err) - t.Cleanup(func() { db.SetDB(nil) }) + t.Cleanup(func() { repository.SetDBForTest(nil) }) now := time.Now().UTC() rows := []analyticsmodel.UserAccessLog{ @@ -142,8 +141,8 @@ func TestClearTargetLogTablesClearsUserAccessLogs(t *testing.T) { ctx := context.Background() dstDB := newLogDBSwitchDB(t) - db.SetDB(dstDB) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(dstDB) + t.Cleanup(func() { repository.SetDBForTest(nil) }) dst, err := logstore.BuildForMigration(ctx, "sqlite") require.NoError(t, err) @@ -168,8 +167,8 @@ func TestClearTargetLogTablesDuringMigration(t *testing.T) { defer logstore.ResetForTest() gdb := newLogDBSwitchDB(t) - db.SetDB(gdb) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(gdb) + t.Cleanup(func() { repository.SetDBForTest(nil) }) ctx := context.Background() logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) { @@ -205,8 +204,8 @@ func TestValidateSwitch(t *testing.T) { }) gdb := newLogDBSwitchDB(t) - db.SetDB(gdb) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(gdb) + t.Cleanup(func() { repository.SetDBForTest(nil) }) ctx := context.Background() setLogDB := func(v string) { require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyLogDatabase, v)) @@ -288,8 +287,8 @@ func TestExecuteFailureClearsMigrationFlag(t *testing.T) { defer logstore.ResetForTest() gdb := newLogDBSwitchDB(t) - db.SetDB(gdb) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(gdb) + t.Cleanup(func() { repository.SetDBForTest(nil) }) ctx := context.Background() // FRESH DB:log_db_migration 行不存在(不预置),log_database 预置为 sqlite。 @@ -323,8 +322,8 @@ func TestSetMigrationFlagObservableThroughCache(t *testing.T) { defer logstore.ResetForTest() gdb := newLogDBSwitchDB(t) - db.SetDB(gdb) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(gdb) + t.Cleanup(func() { repository.SetDBForTest(nil) }) ctx := context.Background() // 按 bootstrap 同款注入 repository 读取,走 RAM 缓存路径。 @@ -353,8 +352,8 @@ func TestFlipLogDatabaseRefreshesCachedConfig(t *testing.T) { defer logstore.ResetForTest() gdb := newLogDBSwitchDB(t) - db.SetDB(gdb) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(gdb) + t.Cleanup(func() { repository.SetDBForTest(nil) }) ctx := context.Background() logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) { diff --git a/backend/openflare/plugins/server/domain/option/logics_test.go b/backend/openflare/plugins/server/domain/option/logics_test.go index 2bb06ce2..9c003ce2 100644 --- a/backend/openflare/plugins/server/domain/option/logics_test.go +++ b/backend/openflare/plugins/server/domain/option/logics_test.go @@ -9,7 +9,7 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/repository" - db "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/testhelper" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -26,7 +26,8 @@ func setupOptionTestDB(t *testing.T) func() { require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{})) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) + repository.SetSystemConfigService(testhelper.NewMockSystemConfigService(sqliteDB)) // 预填充一些业务配置用于测试 seedConfigs := []model.SystemConfig{ @@ -38,14 +39,15 @@ func setupOptionTestDB(t *testing.T) func() { } return func() { - db.SetDB(nil) + repository.SetSystemConfigService(nil) + repository.SetDBForTest(nil) } } // setTestConfig 设置测试配置的辅助函数 func setTestConfig(t *testing.T, ctx context.Context, key, value string) { t.Helper() - require.NoError(t, db.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error) + require.NoError(t, repository.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error) } func TestListOptionsFiltersSecretKeys(t *testing.T) { @@ -89,7 +91,7 @@ func TestUpdateOpenRestyOptionPersistsToSystemConfig(t *testing.T) { defer cleanup() ctx := context.Background() - require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{ + require.NoError(t, repository.DB(ctx).Create(&model.SystemConfig{ Key: model.ConfigKeyOpenRestyEventsUse, Value: "epoll", Type: "business", diff --git a/backend/openflare/plugins/server/domain/option/uptimekuma/sync_test.go b/backend/openflare/plugins/server/domain/option/uptimekuma/sync_test.go index 08e40344..6ed3622e 100644 --- a/backend/openflare/plugins/server/domain/option/uptimekuma/sync_test.go +++ b/backend/openflare/plugins/server/domain/option/uptimekuma/sync_test.go @@ -17,7 +17,7 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/repository" - db "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/testhelper" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -125,17 +125,19 @@ func setupSyncTestDB(t *testing.T) func() { require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.ProxyRoute{}, &model.Zone{}, &model.ZoneDomain{}, &model.SystemConfig{})) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) + repository.SetSystemConfigService(testhelper.NewMockSystemConfigService(sqliteDB)) return func() { - db.SetDB(nil) + repository.SetSystemConfigService(nil) + repository.SetDBForTest(nil) } } func createRouteZoneDomain(t *testing.T, ctx context.Context, route *model.ProxyRoute, domain string) { t.Helper() zone := &model.Zone{Domain: fmt.Sprintf("zone-%d.example", route.ID)} - require.NoError(t, db.DB(ctx).Create(zone).Error) - require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{ + require.NoError(t, repository.DB(ctx).Create(zone).Error) + require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{ ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: domain, @@ -166,7 +168,7 @@ func backupUptimeKumaConfig(ctx context.Context) func() { return func() { // 恢复所有配置 for key, value := range oldValues { - _ = db.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error + _ = repository.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error } } } @@ -197,7 +199,7 @@ func TestSyncToUptimeKumaSuccess(t *testing.T) { restore := backupUptimeKumaConfig(ctx) defer restore() - require.NoError(t, db.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error) + require.NoError(t, repository.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error) routeA := &model.ProxyRoute{ SiteName: "site-a", @@ -306,7 +308,7 @@ func TestSyncToUptimeKumaSelectedScope(t *testing.T) { restore := backupUptimeKumaConfig(ctx) defer restore() - require.NoError(t, db.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error) + require.NoError(t, repository.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error) routeA := &model.ProxyRoute{ SiteName: "site-a", diff --git a/backend/openflare/plugins/server/domain/pages/github_source_identity_test.go b/backend/openflare/plugins/server/domain/pages/github_source_identity_test.go index b8685a40..56e90ca2 100644 --- a/backend/openflare/plugins/server/domain/pages/github_source_identity_test.go +++ b/backend/openflare/plugins/server/domain/pages/github_source_identity_test.go @@ -9,7 +9,7 @@ import ( "time" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/repository" ) func TestGitHubSourceIdentityLengthPrefixesFieldsAndResetsRuntime(t *testing.T) { @@ -57,7 +57,7 @@ func TestGitHubSourceIdentityLengthPrefixesFieldsAndResetsRuntime(t *testing.T) syncedAt := time.Now().Add(-30 * time.Second) nextCheckAt := time.Now().Add(time.Hour) leaseExpiresAt := time.Now().Add(time.Minute) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", firstSource.ID). Updates(map[string]any{ "etag": `"old-etag"`, diff --git a/backend/openflare/plugins/server/domain/pages/github_source_test.go b/backend/openflare/plugins/server/domain/pages/github_source_test.go index 69e98a8d..625238a7 100644 --- a/backend/openflare/plugins/server/domain/pages/github_source_test.go +++ b/backend/openflare/plugins/server/domain/pages/github_source_test.go @@ -20,7 +20,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/task" "Wavelet/openflare/share/githubrelease" - db "Wavelet/plugins/infra/database" "github.com/hibiken/asynq" "gorm.io/gorm" @@ -62,7 +61,7 @@ func mustConfigureGitHubSourceWithoutDispatch( if err := validateGitHubSourceInput(input); err != nil { t.Fatalf("validateGitHubSourceInput(%+v) error = %v, want nil", input, err) } - if err := db.DB(ctx).Transaction(func(tx *gorm.DB) error { + if err := repository.DB(ctx).Transaction(func(tx *gorm.DB) error { _, err := updateGitHubSourceTx(tx, projectID, input) return err }); err != nil { @@ -78,11 +77,11 @@ func mustLoadPagesSource( ) (*model.PagesProjectSource, *model.PagesProjectSourceRuntime) { t.Helper() var source model.PagesProjectSource - if err := db.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil { + if err := repository.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil { t.Fatalf("load source for project %d error = %v, want nil", projectID, err) } var runtime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { t.Fatalf("load runtime for source %d error = %v, want nil", source.ID, err) } return &source, &runtime @@ -136,14 +135,14 @@ func TestGitHubSourceValidationNormalizationAndProviderSwitch(t *testing.T) { } var taskCount int64 - if err := db.DB(ctx).Model(&model.TaskExecution{}).Count(&taskCount).Error; err != nil { + if err := repository.DB(ctx).Model(&model.TaskExecution{}).Count(&taskCount).Error; err != nil { t.Fatalf("count initial checks error = %v, want nil", err) } if _, err := UpdateSourceAs(ctx, project.ID, input, "user:42"); err != nil { t.Fatalf("UpdateSourceAs(GitHub no-op) error = %v, want nil", err) } var noOpTaskCount int64 - if err := db.DB(ctx).Model(&model.TaskExecution{}).Count(&noOpTaskCount).Error; err != nil { + if err := repository.DB(ctx).Model(&model.TaskExecution{}).Count(&noOpTaskCount).Error; err != nil { t.Fatalf("count no-op checks error = %v, want nil", err) } if noOpTaskCount != taskCount { @@ -275,7 +274,7 @@ func TestInitialCheckFailureUsesExactConfigFence(t *testing.T) { RepositoryURL: "https://github.com/a/b", }) staleVersion := source.ConfigVersion - if err := db.DB(ctx).Model(source).Update("config_version", staleVersion+1).Error; err != nil { + if err := repository.DB(ctx).Model(source).Update("config_version", staleVersion+1).Error; err != nil { t.Fatalf("increment source config version error = %v, want nil", err) } markInitialCheckDispatchFailed(ctx, source.ID, staleVersion) @@ -294,7 +293,7 @@ func TestGitHubCheckUsesETagAndDetectsSameReleaseReplacement(t *testing.T) { }) appliedRevision := strings.Repeat("a", 64) appliedDetail := `{"provider":"github","release_id":"100","asset_id":"1","tag":"release/v1","asset_name":"dist.zip"}` - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{ + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{ "etag": `"old-etag"`, "last_applied_revision": appliedRevision, "last_applied_detail": appliedDetail, @@ -375,7 +374,7 @@ func TestGitHubCheckNotModifiedRefreshesRuntimeWithoutDeployment(t *testing.T) { t.Errorf("304 runtime = %+v, want refreshed timestamps/etag and released lease", runtime) } var deployments int64 - if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deployments).Error; err != nil { + if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deployments).Error; err != nil { t.Fatalf("count deployments error = %v, want nil", err) } if deployments != 0 { @@ -391,7 +390,7 @@ func TestGitHubTargetMismatchPreservesAttentionAndExpeditesRecheck(t *testing.T) RepositoryURL: "https://github.com/a/b", }) appliedRevision := strings.Repeat("a", 64) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{ + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{ "last_applied_revision": appliedRevision, "last_applied_detail": `{"provider":"github","release_id":"100","asset_id":"1","tag":"v1","asset_name":"dist.zip"}`, }).Error; err != nil { @@ -430,7 +429,7 @@ func TestGitHubTargetMismatchPreservesAttentionAndExpeditesRecheck(t *testing.T) t.Errorf("target mismatch NextCheckAt = %v, want server deadline >= %v", runtime.NextCheckAt, retryAt) } var deployments int64 - if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deployments).Error; err != nil { + if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deployments).Error; err != nil { t.Fatalf("count mismatch deployments error = %v, want nil", err) } if deployments != 0 { @@ -454,7 +453,7 @@ func TestGitHubCheckLostLeaseReturnsStaleWithoutOverwritingRuntime(t *testing.T) }, }) snapshot, _, _ := acquireSourceLease(ctx, source.ID, source.ConfigVersion, sourceActionCheck) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{ + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{ "lease_token": "new-owner", "lease_expires_at": time.Now().Add(time.Minute), "sync_status": pagesSourceStatusSyncing, @@ -741,7 +740,7 @@ func TestGitHubSyncActivatesExactConfirmedReplacement(t *testing.T) { if err != nil { t.Fatalf("buildGitHubSourceTarget() error = %v, want nil", err) } - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{ + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{ "last_seen_revision": target.Revision, "last_seen_detail": target.DetailJSON, "last_applied_revision": strings.Repeat("a", 64), diff --git a/backend/openflare/plugins/server/domain/pages/helpers.go b/backend/openflare/plugins/server/domain/pages/helpers.go index fda6f34d..41479777 100644 --- a/backend/openflare/plugins/server/domain/pages/helpers.go +++ b/backend/openflare/plugins/server/domain/pages/helpers.go @@ -320,6 +320,7 @@ func removePagesUploadIfUnreferenced(ctx context.Context, projectID uint, upload if uploadID == 0 { return nil } + shouldRemove := false err := repository.WithPagesTx(ctx, func(tx *gorm.DB) error { if projectID != 0 { if _, projectErr := repository.LockPagesProjectByIDTx(tx, projectID); projectErr != nil && @@ -350,13 +351,13 @@ func removePagesUploadIfUnreferenced(ctx context.Context, projectID uint, upload if references > 0 { return nil } - _, err := ofupload.RemoveLockedTx(tx, &uploadRecord) - return err + shouldRemove = true + return nil }) - // Always invalidate after transaction completion, including idempotent no-op, - // so a prior post-commit cache interruption can heal on retry. - ofupload.InvalidateUploadMetaCache(ctx, uploadID) - return err + if err != nil || !shouldRemove { + return err + } + return ofupload.Remove(ctx, uploadID) } func inspectPagesPackage(packagePath string, format pagesarchive.Format, rootDir string, entryFile string, limits pagesLimits) (*deploymentManifest, error) { diff --git a/backend/openflare/plugins/server/domain/pages/logics.go b/backend/openflare/plugins/server/domain/pages/logics.go index 1f1a2380..42f3105e 100644 --- a/backend/openflare/plugins/server/domain/pages/logics.go +++ b/backend/openflare/plugins/server/domain/pages/logics.go @@ -1020,7 +1020,8 @@ func hydrateLegacyDeploymentUpload( if deployment.UploadID > 0 { uploadRecord, err := ofupload.GetActiveUpload(ctx, deployment.UploadID) if err == nil { - return &uploadRecord, nil + record := model.FromUploadDTO(uploadRecord) + return &record, nil } } @@ -1066,7 +1067,8 @@ func hydrateLegacyDeploymentUpload( } deployment.UploadID = winnerUploadID deployment.ArtifactPath = "" - return &winner, nil + winnerRecord := model.FromUploadDTO(winner) + return &winnerRecord, nil } func attachLegacyDeploymentUpload( diff --git a/backend/openflare/plugins/server/domain/pages/logics_test.go b/backend/openflare/plugins/server/domain/pages/logics_test.go index 78f73c1a..72b20a1e 100644 --- a/backend/openflare/plugins/server/domain/pages/logics_test.go +++ b/backend/openflare/plugins/server/domain/pages/logics_test.go @@ -23,8 +23,6 @@ import ( oftask "Wavelet/openflare/plugins/server/kernel/task" "Wavelet/openflare/plugins/server/kernel/testhelper" "Wavelet/pkg/idgen" - uploadshared "Wavelet/plugins/domain/upload/shared" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -39,6 +37,9 @@ func setupPagesTestDB(t *testing.T) func() { DisableForeignKeyConstraintWhenMigrating: true, }) require.NoError(t, err) + if sqlDB, err := sqliteDB.DB(); err == nil { + sqlDB.SetMaxOpenConns(1) + } require.NoError(t, sqliteDB.AutoMigrate( &model.User{}, &model.Upload{}, @@ -74,30 +75,33 @@ func setupPagesTestDB(t *testing.T) func() { }, }).Error) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) + repository.SetSystemConfigService(testhelper.NewMockSystemConfigService(sqliteDB)) require.NoError(t, idgen.Init(1)) - oftask.SetService(&testhelper.NoopTaskService{}) - mockStorage := uploadshared.NewMockStorageService() - uploadshared.SetDBService(db.NewService(sqliteDB)) - uploadshared.SetStorageService(mockStorage) + noopTask := &testhelper.NoopTaskService{} + oftask.SetService(noopTask) + repository.SetTaskService(noopTask) + mockStorage := testhelper.NewMockStorageService() ofupload.SetStorage(mockStorage) + ofupload.SetUploadService(testhelper.NewMockUploadService(sqliteDB)) _ = repository.InvalidateSystemConfigCache(context.Background(), model.ConfigKeyPagesMaxPackageSizeMB) _ = repository.InvalidateSystemConfigCache(context.Background(), model.ConfigKeyPagesMaxHistoryCount) return func() { ofupload.SetStorage(nil) - uploadshared.ResetServices() - db.SetDB(nil) + ofupload.SetUploadService(nil) + repository.SetTaskService(nil) + oftask.SetService(nil) + repository.SetSystemConfigService(nil) + repository.SetDBForTest(nil) } } func setupPagesStorageMock(t *testing.T) (restore func(), disable func()) { t.Helper() - mock := uploadshared.NewMockStorageService() - uploadshared.SetStorageService(mock) + mock := testhelper.NewMockStorageService() ofupload.SetStorage(mock) restore = func() { ofupload.SetStorage(nil) - uploadshared.ResetServices() } disable = restore return restore, disable @@ -283,10 +287,10 @@ func TestUploadDeploymentStoresPackageInUploadFramework(t *testing.T) { assert.Empty(t, storedDeployment.ArtifactPath) var uploadCount int64 - require.NoError(t, db.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error) + require.NoError(t, repository.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error) assert.Equal(t, int64(1), uploadCount) var uploadRecord model.Upload - require.NoError(t, db.DB(ctx).First(&uploadRecord, storedDeployment.UploadID).Error) + require.NoError(t, repository.DB(ctx).First(&uploadRecord, storedDeployment.UploadID).Error) assert.Equal(t, ofupload.ReservedPagesDeploymentType, uploadRecord.Type) assert.Equal(t, pagesIngestMarkerV2, uploadRecord.Metadata.Extra[pagesIngestMarkerKey]) assert.Equal(t, fmt.Sprint(project.ID), uploadRecord.Metadata.Extra[pagesProjectIDMetadataKey]) @@ -323,8 +327,8 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) { TotalSize: 10, CreatedBy: "test", } - require.NoError(t, db.DB(ctx).Create(deployment).Error) - require.NoError(t, db.DB(ctx).Create(&model.PagesDeploymentFile{ + require.NoError(t, repository.DB(ctx).Create(deployment).Error) + require.NoError(t, repository.DB(ctx).Create(&model.PagesDeploymentFile{ DeploymentID: deployment.ID, Path: "index.html", Size: 6, @@ -334,7 +338,7 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) { _, err = ActivateDeployment(ctx, project.ID, deployment.ID) require.NoError(t, err) - require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{ + require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{ Version: "v2026-legacy", SnapshotJSON: fmt.Sprintf(`{"routes":[{"upstream_type":"pages","pages_deployment":{"deployment_id":%d}}]}`, deployment.ID), MainConfig: "", @@ -363,7 +367,7 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) { assert.Empty(t, storedDeployment.ArtifactPath) var uploadCount int64 - require.NoError(t, db.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error) + require.NoError(t, repository.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error) assert.Equal(t, int64(1), uploadCount) packageObj2, err := OpenDeploymentPackage(ctx, deployment.ID) @@ -400,7 +404,7 @@ func TestOpenDeploymentPackageRequiresActiveConfigSnapshot(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "激活配置") - require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{ + require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{ Version: "v2026-001", SnapshotJSON: fmt.Sprintf(`{"routes":[{"upstream_type":"pages","pages_deployment":{"deployment_id":%d}}]}`, deployment.ID), MainConfig: "", @@ -454,7 +458,7 @@ func TestProjectLatestRejectsWhenNotOnActiveConfigOrNotActive(t *testing.T) { _, _, err = GetProjectLatestPackageHash(ctx, project.ID) require.Error(t, err) - require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{ + require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{ Version: "v-gate", SnapshotJSON: fmt.Sprintf( `{"routes":[{"upstream_type":"pages","pages_project_id":%d,"pages_deployment":{"project_id":%d,"deployment_id":%d}}]}`, @@ -476,7 +480,7 @@ func TestProjectLatestRejectsWhenNotOnActiveConfigOrNotActive(t *testing.T) { assert.NotEmpty(t, hash) // Disabled project rejects. - require.NoError(t, db.DB(ctx).Model(&model.PagesProject{}).Where("id = ?", project.ID).Update("enabled", false).Error) + require.NoError(t, repository.DB(ctx).Model(&model.PagesProject{}).Where("id = ?", project.ID).Update("enabled", false).Error) _, _, err = GetProjectLatestPackageHash(ctx, project.ID) require.Error(t, err) } @@ -572,7 +576,7 @@ func TestPruneProjectDeploymentHistory(t *testing.T) { defer disableStorage() ctx := context.Background() - require.NoError(t, db.DB(ctx).Model(&model.SystemConfig{}). + require.NoError(t, repository.DB(ctx).Model(&model.SystemConfig{}). Where("key = ?", model.ConfigKeyPagesMaxHistoryCount). Update("value", "2").Error) require.NoError(t, repository.InvalidateSystemConfigCache(ctx, model.ConfigKeyPagesMaxHistoryCount)) @@ -634,7 +638,7 @@ func TestHistoryCountOnePreservesFreshCandidateUntilActivation(t *testing.T) { defer disableStorage() ctx := context.Background() - require.NoError(t, db.DB(ctx).Model(&model.SystemConfig{}). + require.NoError(t, repository.DB(ctx).Model(&model.SystemConfig{}). Where("key = ?", model.ConfigKeyPagesMaxHistoryCount). Update("value", "1").Error) require.NoError(t, repository.InvalidateSystemConfigCache(ctx, model.ConfigKeyPagesMaxHistoryCount)) @@ -671,7 +675,7 @@ func TestHistoryCountOnePreservesFreshCandidateUntilActivation(t *testing.T) { assert.True(t, kept[newCandidate.ID]) assert.False(t, kept[oldCandidate.ID]) var removedUpload model.Upload - require.NoError(t, db.DB(ctx).First(&removedUpload, oldCandidate.UploadID).Error) + require.NoError(t, repository.DB(ctx).First(&removedUpload, oldCandidate.UploadID).Error) assert.Equal(t, model.UploadStatusDeleted, removedUpload.Status) _, err = ActivateDeployment(ctx, project.ID, newCandidate.ID) @@ -741,12 +745,12 @@ func TestDeleteDeploymentAndProjectSoftDeleteUnreferencedArtifacts(t *testing.T) require.NoError(t, DeleteDeployment(ctx, project.ID, second.ID)) var secondUpload model.Upload - require.NoError(t, db.DB(ctx).First(&secondUpload, second.UploadID).Error) + require.NoError(t, repository.DB(ctx).First(&secondUpload, second.UploadID).Error) assert.Equal(t, model.UploadStatusDeleted, secondUpload.Status) require.NoError(t, DeleteProject(ctx, project.ID)) var firstUpload model.Upload - require.NoError(t, db.DB(ctx).First(&firstUpload, first.UploadID).Error) + require.NoError(t, repository.DB(ctx).First(&firstUpload, first.UploadID).Error) assert.Equal(t, model.UploadStatusDeleted, firstUpload.Status) _, err = repository.GetPagesProjectByID(ctx, project.ID) assert.Error(t, err) diff --git a/backend/openflare/plugins/server/domain/pages/package_metadata.go b/backend/openflare/plugins/server/domain/pages/package_metadata.go index 7c04bebe..49eba9fa 100644 --- a/backend/openflare/plugins/server/domain/pages/package_metadata.go +++ b/backend/openflare/plugins/server/domain/pages/package_metadata.go @@ -58,7 +58,7 @@ func GetProjectLatestPackageMetadata(ctx context.Context, projectID uint) (*Proj return &ProjectLatestPackageMetadata{ DeploymentID: deployment.ID, Hash: hash, - PackageSize: uploadRecord.FileSize, + PackageSize: uploadRecord.Size, FileCount: deployment.FileCount, TotalSize: deployment.TotalSize, }, nil diff --git a/backend/openflare/plugins/server/domain/pages/package_metadata_test.go b/backend/openflare/plugins/server/domain/pages/package_metadata_test.go index d18fdb3c..09aad456 100644 --- a/backend/openflare/plugins/server/domain/pages/package_metadata_test.go +++ b/backend/openflare/plugins/server/domain/pages/package_metadata_test.go @@ -11,7 +11,7 @@ import ( "testing" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/repository" ) func TestGetProjectLatestPackageMetadataPreservesZeroTotalSize(t *testing.T) { @@ -46,7 +46,7 @@ func TestGetProjectLatestPackageMetadataPreservesZeroTotalSize(t *testing.T) { if _, err := ActivateDeployment(ctx, project.ID, deployment.ID); err != nil { t.Fatalf("ActivateDeployment() error = %v", err) } - if err := db.DB(ctx).Create(&model.ConfigVersion{ + if err := repository.DB(ctx).Create(&model.ConfigVersion{ Version: "v-package-metadata", SnapshotJSON: fmt.Sprintf( `{"routes":[{"upstream_type":"pages","pages_project_id":%d}]}`, diff --git a/backend/openflare/plugins/server/domain/pages/rebind_test.go b/backend/openflare/plugins/server/domain/pages/rebind_test.go index 20acf1e0..90f71b10 100644 --- a/backend/openflare/plugins/server/domain/pages/rebind_test.go +++ b/backend/openflare/plugins/server/domain/pages/rebind_test.go @@ -9,7 +9,7 @@ import ( "testing" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/repository" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,7 +36,7 @@ func TestRebindSnapshotPagesToCurrentActive(t *testing.T) { Status: model.PagesDeploymentStatusUploaded, FileCount: 1, } - require.NoError(t, db.DB(ctx).Create(old).Error) + require.NoError(t, repository.DB(ctx).Create(old).Error) active := &model.PagesDeployment{ ProjectID: project.ID, DeploymentNumber: 2, @@ -44,8 +44,8 @@ func TestRebindSnapshotPagesToCurrentActive(t *testing.T) { Status: model.PagesDeploymentStatusActive, FileCount: 1, } - require.NoError(t, db.DB(ctx).Create(active).Error) - require.NoError(t, db.DB(ctx).Model(&model.PagesProject{}). + require.NoError(t, repository.DB(ctx).Create(active).Error) + require.NoError(t, repository.DB(ctx).Model(&model.PagesProject{}). Where("id = ?", project.ID). Update("active_deployment_id", active.ID).Error) diff --git a/backend/openflare/plugins/server/domain/pages/routers_source_test.go b/backend/openflare/plugins/server/domain/pages/routers_source_test.go index 47f99332..78cbec6d 100644 --- a/backend/openflare/plugins/server/domain/pages/routers_source_test.go +++ b/backend/openflare/plugins/server/domain/pages/routers_source_test.go @@ -15,8 +15,8 @@ import ( "Wavelet/core/contracts" "Wavelet/openflare/plugins/server/kernel/model" + "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/testhelper" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" ) @@ -117,7 +117,7 @@ func TestPagesSourceHandlersReturnStableActionErrors(t *testing.T) { false, ) future := time.Now().Add(time.Minute) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", busySource.ID). Updates(map[string]any{ "sync_status": pagesSourceStatusSyncing, @@ -181,7 +181,7 @@ func TestSyncSourceHandlerAcceptsEmptyBodyAndEmptyObject(t *testing.T) { } var executions []model.TaskExecution - if err := db.DB(ctx).Where("task_type = ?", TaskTypePagesSourceAction).Order("id asc").Find(&executions).Error; err != nil { + if err := repository.DB(ctx).Where("task_type = ?", TaskTypePagesSourceAction).Order("id asc").Find(&executions).Error; err != nil { t.Fatalf("list Pages source task executions error = %v, want nil", err) } if got, want := len(executions), 2; got != want { diff --git a/backend/openflare/plugins/server/domain/pages/source_orphan_cleanup.go b/backend/openflare/plugins/server/domain/pages/source_orphan_cleanup.go index 65bde366..22cd41ef 100644 --- a/backend/openflare/plugins/server/domain/pages/source_orphan_cleanup.go +++ b/backend/openflare/plugins/server/domain/pages/source_orphan_cleanup.go @@ -131,7 +131,6 @@ func reconcilePagesOrphanUploadCandidate( } outcome := pagesOrphanCleanupSkipped - uploadLocked := false err := repository.WithPagesTx(ctx, func(tx *gorm.DB) error { scopeOutcome, proceed, err := lockPagesOrphanCleanupScope(ctx, tx, candidate.ID, marker) if err != nil { @@ -141,7 +140,7 @@ func reconcilePagesOrphanUploadCandidate( outcome = scopeOutcome return nil } - lockedOutcome, locked, err := reconcileLockedPagesOrphanUpload( + lockedOutcome, _, err := reconcileLockedPagesOrphanUpload( ctx, tx, candidate.ID, @@ -153,16 +152,15 @@ func reconcilePagesOrphanUploadCandidate( return err } outcome = lockedOutcome - uploadLocked = locked return nil }) if err != nil { return pagesOrphanCleanupSkipped, err } - if uploadLocked { - // Also heal a prior post-commit cache invalidation interruption when the - // status transition was an idempotent no-op. - ofupload.InvalidateUploadMetaCache(ctx, candidate.ID) + if outcome == pagesOrphanCleanupReconciled { + if err := ofupload.Remove(ctx, candidate.ID); err != nil { + return pagesOrphanCleanupSkipped, err + } } return outcome, nil } @@ -252,14 +250,7 @@ func reconcileLockedPagesOrphanUpload( return pagesOrphanCleanupReferenced, true, nil } - transitioned, err := ofupload.RemoveLockedTx(tx, &lockedUpload) - if err != nil { - return pagesOrphanCleanupSkipped, true, err - } - if transitioned { - return pagesOrphanCleanupReconciled, true, nil - } - return pagesOrphanCleanupSkipped, true, nil + return pagesOrphanCleanupReconciled, true, nil } func lockOptionalPagesCleanupRecord( diff --git a/backend/openflare/plugins/server/domain/pages/source_orphan_cleanup_test.go b/backend/openflare/plugins/server/domain/pages/source_orphan_cleanup_test.go index ccd98cb1..f957be81 100644 --- a/backend/openflare/plugins/server/domain/pages/source_orphan_cleanup_test.go +++ b/backend/openflare/plugins/server/domain/pages/source_orphan_cleanup_test.go @@ -12,7 +12,7 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/ofupload" - db "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/repository" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -164,7 +164,7 @@ func TestReconcilePagesOrphanUploadsSkipsBusyLeaseAndSourceMismatch(t *testing.T project := createPagesOrphanProject(t, ctx, "busy-orphan") source := createPagesOrphanSource(t, ctx, project.ID) future := realNow.Add(time.Hour) - if err := db.DB(ctx).Create(&model.PagesProjectSourceRuntime{ + if err := repository.DB(ctx).Create(&model.PagesProjectSourceRuntime{ SourceID: source.ID, LeaseToken: "busy-worker", LeaseExpiresAt: &future, @@ -213,7 +213,7 @@ func TestReconcilePagesOrphanUploadsRejectsMalformedMarker(t *testing.T) { metadata := candidate.Metadata metadata.Extra[pagesProjectIDMetadataKey] = "01" candidate.Metadata = metadata - if err := db.DB(ctx).Save(candidate).Error; err != nil { + if err := repository.DB(ctx).Save(candidate).Error; err != nil { t.Fatalf("seed malformed candidate marker error = %v, want nil", err) } @@ -239,7 +239,7 @@ func TestPagesOrphanCleanupAndDeploymentCommitInterleavings(t *testing.T) { if err != nil { t.Fatalf("parsePagesOrphanMarker() error = %v, want nil", err) } - if err := db.DB(ctx).Create(&model.PagesDeployment{ + if err := repository.DB(ctx).Create(&model.PagesDeployment{ ProjectID: project.ID, DeploymentNumber: 1, Checksum: "deployment-first", @@ -276,7 +276,7 @@ func TestPagesOrphanCleanupAndDeploymentCommitInterleavings(t *testing.T) { } target := &model.PagesDeployment{ProjectID: project.ID, UploadID: candidate.ID} - err = db.DB(ctx).Transaction(func(tx *gorm.DB) error { + err = repository.DB(ctx).Transaction(func(tx *gorm.DB) error { var lockedProject model.PagesProject if err := tx.Clauses(clause.Locking{Strength: pagesRowLockStrength}).First(&lockedProject, project.ID).Error; err != nil { return err @@ -287,7 +287,7 @@ func TestPagesOrphanCleanupAndDeploymentCommitInterleavings(t *testing.T) { t.Errorf("final deployment upload lock after cleanup error = %v, want %v", err, errSourceFinalFence) } var references int64 - if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("upload_id = ?", candidate.ID).Count(&references).Error; err != nil { + if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("upload_id = ?", candidate.ID).Count(&references).Error; err != nil { t.Fatalf("count deployment references error = %v, want nil", err) } if references != 0 { @@ -303,7 +303,7 @@ func cleanupOutcomeTotal(summary PagesOrphanCleanupSummary) int { func createPagesOrphanProject(t *testing.T, ctx context.Context, slug string) *model.PagesProject { t.Helper() project := &model.PagesProject{Name: slug, Slug: slug, Enabled: true} - if err := db.DB(ctx).Create(project).Error; err != nil { + if err := repository.DB(ctx).Create(project).Error; err != nil { t.Fatalf("create Pages orphan project %q error = %v, want nil", slug, err) } return project @@ -317,7 +317,7 @@ func createPagesOrphanSource(t *testing.T, ctx context.Context, projectID uint) ConfigVersion: 1, SourceIdentity: "orphan-source-identity", } - if err := db.DB(ctx).Create(source).Error; err != nil { + if err := repository.DB(ctx).Create(source).Error; err != nil { t.Fatalf("create Pages orphan source for project %d error = %v, want nil", projectID, err) } return source @@ -339,21 +339,19 @@ func createPagesOrphanUpload( extra[pagesSourceIDMetadataKey] = strconv.FormatUint(uint64(*sourceID), 10) } candidate := &model.Upload{ - UserID: 999, - FileName: "site.zip", - FilePath: "pages/orphan-site.zip", - FileSize: 64, - MimeType: "application/zip", - Extension: "zip", - Hash: "orphan-checksum", - Type: ofupload.ReservedPagesDeploymentType, - Status: model.UploadStatusUsed, - AccessMode: 0, - Metadata: model.UploadMetadata{Extra: extra}, - CreatedAt: createdAt, - UpdatedAt: createdAt, + UserID: 999, + FileName: "site.zip", + FilePath: "pages/orphan-site.zip", + Size: 64, + MimeType: "application/zip", + Hash: "orphan-checksum", + Type: ofupload.ReservedPagesDeploymentType, + Status: model.UploadStatusUsed, + Metadata: model.UploadMetadata{Extra: extra}, + CreatedAt: createdAt, + UpdatedAt: createdAt, } - if err := db.DB(ctx).Create(candidate).Error; err != nil { + if err := repository.DB(ctx).Table("w_uploads").Create(candidate).Error; err != nil { t.Fatalf("create Pages orphan upload error = %v, want nil", err) } return candidate @@ -362,7 +360,7 @@ func createPagesOrphanUpload( func assertPagesCleanupUploadStatus(t *testing.T, ctx context.Context, uploadID uint64, want model.UploadStatus) { t.Helper() var got model.Upload - if err := db.DB(ctx).First(&got, uploadID).Error; err != nil { + if err := repository.DB(ctx).Table("w_uploads").First(&got, uploadID).Error; err != nil { t.Fatalf("load upload %d error = %v, want nil", uploadID, err) } if got.Status != want { @@ -373,10 +371,10 @@ func assertPagesCleanupUploadStatus(t *testing.T, ctx context.Context, uploadID func assertPagesCleanupTotalStat(t *testing.T, ctx context.Context, want int64) { t.Helper() var stat model.UploadStat - if err := db.DB(ctx).Where("dimension = ? AND stat_key = ?", model.UploadStatDimensionTotal, "").First(&stat).Error; err != nil { + if err := repository.DB(ctx).Where("dimension = ?", model.UploadStatDimensionTotal).First(&stat).Error; err != nil { t.Fatalf("load total upload stat error = %v, want nil", err) } - if stat.FileCount != want { + if int64(stat.FileCount) != want { t.Errorf("total upload stat FileCount = %d, want %d", stat.FileCount, want) } } diff --git a/backend/openflare/plugins/server/domain/pages/source_runtime_test.go b/backend/openflare/plugins/server/domain/pages/source_runtime_test.go index 7500fa83..4228e123 100644 --- a/backend/openflare/plugins/server/domain/pages/source_runtime_test.go +++ b/backend/openflare/plugins/server/domain/pages/source_runtime_test.go @@ -13,7 +13,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) { @@ -38,7 +37,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) { t.Cleanup(func() { _ = heartbeat.stop() }) var initial model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&initial).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&initial).Error; err != nil { t.Fatalf("load initial heartbeat runtime error = %v, want nil", err) } if initial.LeaseExpiresAt == nil { @@ -47,7 +46,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) { deadline := time.Now().Add(2 * time.Second) for { var renewedRuntime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&renewedRuntime).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&renewedRuntime).Error; err != nil { t.Fatalf("load renewed heartbeat runtime error = %v, want nil", err) } if renewedRuntime.LeaseExpiresAt != nil && renewedRuntime.LeaseExpiresAt.After(*initial.LeaseExpiresAt) { @@ -59,7 +58,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) { time.Sleep(10 * time.Millisecond) } - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", source.ID). Update("lease_token", "replacement-owner").Error; err != nil { t.Fatalf("replace heartbeat lease owner error = %v, want nil", err) @@ -166,7 +165,7 @@ func TestAcquireSourceLeaseMutualExclusionExpiryAndTerminalOwnership(t *testing. } past := time.Now().Add(-time.Second) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", source.ID). Update("lease_expires_at", &past).Error; err != nil { t.Fatalf("expire first lease error = %v, want nil", err) @@ -196,7 +195,7 @@ func TestAcquireSourceLeaseMutualExclusionExpiryAndTerminalOwnership(t *testing. t.Fatalf("failSourceLease(expired owner) error = %v, want nil", err) } var runtime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { t.Fatalf("load runtime after takeover error = %v, want nil", err) } if got, want := runtime.LeaseToken, takeover.LeaseToken; got != want { @@ -217,7 +216,7 @@ func TestAcquireSourceLeaseMutualExclusionExpiryAndTerminalOwnership(t *testing. t.Fatalf("failSourceLease(current owner) error = %v, want nil", err) } var failedRuntime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&failedRuntime).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&failedRuntime).Error; err != nil { t.Fatalf("load failed runtime error = %v, want nil", err) } if got, want := failedRuntime.SyncStatus, pagesSourceStatusFailed; got != want { @@ -258,7 +257,7 @@ func TestSourceConfigAndProjectContentChangesFenceLease(t *testing.T) { t.Error("renewSourceLease(after source update) = true, want false") } var updatedSource model.PagesProjectSource - if err := db.DB(ctx).Where("id = ?", source.ID).First(&updatedSource).Error; err != nil { + if err := repository.DB(ctx).Where("id = ?", source.ID).First(&updatedSource).Error; err != nil { t.Fatalf("load updated source error = %v, want nil", err) } if got, want := updatedSource.ConfigVersion, source.ConfigVersion+1; got != want { @@ -296,7 +295,7 @@ func TestSourceConfigAndProjectContentChangesFenceLease(t *testing.T) { t.Errorf("ContentConfigVersion after RootDir update = %d, want %d", got, want) } var runtime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { t.Fatalf("load fenced runtime error = %v, want nil", err) } if runtime.LeaseToken != "" || runtime.LeaseExpiresAt != nil { diff --git a/backend/openflare/plugins/server/domain/pages/source_scanner_test.go b/backend/openflare/plugins/server/domain/pages/source_scanner_test.go index 12abc7cc..d718c41c 100644 --- a/backend/openflare/plugins/server/domain/pages/source_scanner_test.go +++ b/backend/openflare/plugins/server/domain/pages/source_scanner_test.go @@ -19,7 +19,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/share/githubrelease" - db "Wavelet/plugins/infra/database" "gorm.io/gorm" ) @@ -42,7 +41,7 @@ func TestGitHubLatestAutoConfigPreservesIdentityAndRuntimeCursor(t *testing.T) { seenRevision := strings.Repeat("a", sourceRevisionHexLength) appliedRevision := strings.Repeat("b", sourceRevisionHexLength) future := time.Now().Add(time.Hour) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", source.ID). Updates(map[string]any{ "etag": `"cursor-etag"`, @@ -67,7 +66,7 @@ func TestGitHubLatestAutoConfigPreservesIdentityAndRuntimeCursor(t *testing.T) { if err := validateGitHubSourceInput(input); err != nil { t.Fatalf("validateGitHubSourceInput(auto latest) error = %v, want nil", err) } - if err := db.DB(ctx).Transaction(func(tx *gorm.DB) error { + if err := repository.DB(ctx).Transaction(func(tx *gorm.DB) error { changed, err := updateGitHubSourceTx(tx, project.ID, input) if err == nil && !changed { return errors.New("auto config update was treated as no-op") @@ -132,7 +131,7 @@ func TestRecoverExpiredPagesSourceLeaseUsesExactCASAndStableJitter(t *testing.T) now := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC) usePagesSourceScannerClock(t, now) expiredAt := now.Add(-time.Minute) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", source.ID). Updates(map[string]any{ "sync_status": pagesSourceStatusChecking, @@ -167,7 +166,7 @@ func TestRecoverExpiredPagesSourceLeaseUsesExactCASAndStableJitter(t *testing.T) } renewedExpiry := now.Add(time.Minute) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", source.ID). Updates(map[string]any{ "sync_status": pagesSourceStatusSyncing, @@ -274,24 +273,24 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T) byRepository := make(map[string]int, 22) for index := 1; index <= 22; index++ { project := mustCreatePagesSourceProject(t, ctx, fmt.Sprintf("scanner-batch-%02d", index)) - repository := fmt.Sprintf("scanner/source-%02d", index) + repoPath := fmt.Sprintf("scanner/source-%02d", index) source, runtime := mustConfigureGitHubSourceWithoutDispatch(t, ctx, project.ID, SourceUpdateInput{ SourceType: PagesSourceTypeGitHubRelease, - RepositoryURL: "https://github.com/" + repository, + RepositoryURL: "https://github.com/" + repoPath, AutoUpdateEnabled: index != 4, CheckIntervalMinutes: 60, }) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", source.ID). Update("next_check_at", &dueAt).Error; err != nil { t.Fatalf("mark source %d due error = %v, want nil", source.ID, err) } - fixtures = append(fixtures, fixture{source: source, runtime: runtime, repository: repository}) - byRepository[repository] = index + fixtures = append(fixtures, fixture{source: source, runtime: runtime, repository: repoPath}) + byRepository[repoPath] = index } busyUntil := now.Add(time.Hour) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", fixtures[0].source.ID). Updates(map[string]any{ "sync_status": pagesSourceStatusChecking, @@ -302,7 +301,7 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T) } stored304Revision := strings.Repeat("3", sourceRevisionHexLength) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", fixtures[2].source.ID). Updates(map[string]any{ "etag": `"stored-etag"`, @@ -313,7 +312,7 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T) t.Fatalf("seed 304 cursor error = %v, want nil", err) } - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", fixtures[4].source.ID). Updates(map[string]any{ "last_applied_revision": strings.Repeat("a", sourceRevisionHexLength), diff --git a/backend/openflare/plugins/server/domain/pages/source_sync_test.go b/backend/openflare/plugins/server/domain/pages/source_sync_test.go index 73d77920..bb4b1e4c 100644 --- a/backend/openflare/plugins/server/domain/pages/source_sync_test.go +++ b/backend/openflare/plugins/server/domain/pages/source_sync_test.go @@ -22,7 +22,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/ofupload" "Wavelet/openflare/share/pagesarchive" - db "Wavelet/plugins/infra/database" "gorm.io/gorm" ) @@ -160,7 +159,7 @@ func TestSyncRemoteSourceAtomicallyActivatesAndReusesChecksum(t *testing.T) { t.Errorf("deployment provenance = label:%q meta:%q, want no query secret", deployment.SourceLabel, deployment.SourceMeta) } var runtime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { t.Fatalf("load source runtime error = %v, want nil", err) } if got, want := runtime.SyncStatus, pagesSourceStatusIdle; got != want { @@ -173,7 +172,7 @@ func TestSyncRemoteSourceAtomicallyActivatesAndReusesChecksum(t *testing.T) { t.Errorf("runtime lease = (%q, %v), want cleared", runtime.LeaseToken, runtime.LeaseExpiresAt) } var uploadRecord model.Upload - if err := db.DB(ctx).First(&uploadRecord, deployment.UploadID).Error; err != nil { + if err := repository.DB(ctx).First(&uploadRecord, deployment.UploadID).Error; err != nil { t.Fatalf("load deployment upload %d error = %v, want nil", deployment.UploadID, err) } if got, want := uploadRecord.Status, model.UploadStatusUsed; got != want { @@ -198,10 +197,10 @@ func TestSyncRemoteSourceAtomicallyActivatesAndReusesChecksum(t *testing.T) { t.Errorf("reused deployment ID = %d, want %d", got, want) } var deploymentCount, uploadCount int64 - if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deploymentCount).Error; err != nil { + if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deploymentCount).Error; err != nil { t.Fatalf("count source deployments error = %v, want nil", err) } - if err := db.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error; err != nil { + if err := repository.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error; err != nil { t.Fatalf("count source uploads error = %v, want nil", err) } if got, want := deploymentCount, int64(1); got != want { @@ -267,7 +266,7 @@ func TestSyncRemoteSourceFinalFenceCompensatesIngest(t *testing.T) { packageBytes := testPagesZip(t, map[string]string{"index.html": "never-activate"}) mutationResult := make(chan error, 1) server := newPagesArchiveServer(t, http.StatusOK, packageBytes, func() error { - err := db.DB(context.Background()).Model(&model.PagesProject{}). + err := repository.DB(context.Background()).Model(&model.PagesProject{}). Where("id = ?", project.ID). Update("content_config_version", gorm.Expr("content_config_version + 1")).Error mutationResult <- err @@ -304,14 +303,14 @@ func TestSyncRemoteSourceFinalFenceCompensatesIngest(t *testing.T) { t.Errorf("ActiveDeploymentID after final fence = %v, want %d", storedProject.ActiveDeploymentID, oldActive.ID) } var deployments []model.PagesDeployment - if err := db.DB(ctx).Where("project_id = ?", project.ID).Find(&deployments).Error; err != nil { + if err := repository.DB(ctx).Where("project_id = ?", project.ID).Find(&deployments).Error; err != nil { t.Fatalf("list deployments after final fence error = %v, want nil", err) } if got, want := len(deployments), 1; got != want { t.Errorf("deployment count after final fence = %d, want %d", got, want) } var uploads []model.Upload - if err := db.DB(ctx).Order("id asc").Find(&uploads).Error; err != nil { + if err := repository.DB(ctx).Order("id asc").Find(&uploads).Error; err != nil { t.Fatalf("list uploads after final fence error = %v, want nil", err) } var compensated *model.Upload @@ -328,7 +327,7 @@ func TestSyncRemoteSourceFinalFenceCompensatesIngest(t *testing.T) { t.Errorf("compensated upload Status = %q, want %q", got, want) } var danglingCount int64 - if err := db.DB(ctx).Model(&model.PagesDeployment{}). + if err := repository.DB(ctx).Model(&model.PagesDeployment{}). Where("upload_id = ?", compensated.ID). Count(&danglingCount).Error; err != nil { t.Fatalf("count compensated upload references error = %v, want nil", err) @@ -358,12 +357,12 @@ func TestCommitSourceDeploymentRechecksLeaseAfterUploadLocks(t *testing.T) { if err != nil { t.Fatalf("GetPagesDeploymentByID(%d) error = %v, want nil", first.Deployment.ID, err) } - if err := db.DB(ctx).Model(&model.PagesProject{}). + if err := repository.DB(ctx).Model(&model.PagesProject{}). Where("id = ?", project.ID). Update("active_deployment_id", nil).Error; err != nil { t.Fatalf("clear active deployment error = %v, want nil", err) } - if err := db.DB(ctx).Model(&model.PagesDeployment{}). + if err := repository.DB(ctx).Model(&model.PagesDeployment{}). Where("id = ?", deployment.ID). Update("status", model.PagesDeploymentStatusUploaded).Error; err != nil { t.Fatalf("reset deployment status error = %v, want nil", err) @@ -371,7 +370,7 @@ func TestCommitSourceDeploymentRechecksLeaseAfterUploadLocks(t *testing.T) { snapshot := mustAcquireRemoteSyncLease(t, ctx, source) expiresAt := time.Now().Add(time.Hour) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ? AND lease_token = ?", source.ID, snapshot.LeaseToken). Update("lease_expires_at", &expiresAt).Error; err != nil { t.Fatalf("set deterministic lease expiry error = %v, want nil", err) @@ -465,7 +464,7 @@ func TestCompensateSourceIngestSurvivesCanceledParentContext(t *testing.T) { }) var uploadRecord model.Upload - if err := db.DB(ctx).Where("id = ?", result.Upload.ID).First(&uploadRecord).Error; err != nil { + if err := repository.DB(ctx).Where("id = ?", result.Upload.ID).First(&uploadRecord).Error; err != nil { t.Fatalf("load compensated upload error = %v, want nil", err) } if got, want := uploadRecord.Status, model.UploadStatusDeleted; got != want { @@ -490,7 +489,7 @@ func assertPagesSyncFailureState( t.Errorf("project %d ActiveDeploymentID = %v, want %d", projectID, project.ActiveDeploymentID, oldActiveID) } var deploymentCount int64 - if err := db.DB(ctx).Model(&model.PagesDeployment{}). + if err := repository.DB(ctx).Model(&model.PagesDeployment{}). Where("project_id = ?", projectID). Count(&deploymentCount).Error; err != nil { t.Fatalf("count project %d deployments error = %v, want nil", projectID, err) @@ -499,7 +498,7 @@ func assertPagesSyncFailureState( t.Errorf("project %d deployment count = %d, want %d", projectID, got, want) } var runtime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", sourceID).First(&runtime).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", sourceID).First(&runtime).Error; err != nil { t.Fatalf("load source %d runtime error = %v, want nil", sourceID, err) } if got, want := runtime.SyncStatus, pagesSourceStatusFailed; got != want { @@ -529,19 +528,17 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) { identity := source.SourceIdentity revision := strings.Repeat("d", 64) uploadRecord := &model.Upload{ - ID: 987654321, - UserID: 999, - FileName: "deleted.zip", - FilePath: "deleted.zip", - FileSize: 1, - MimeType: "application/zip", - Extension: "zip", - Hash: revision, - Type: ofupload.ReservedPagesDeploymentType, - Status: model.UploadStatusDeleted, - AccessMode: 0, + ID: 987654321, + UserID: 999, + FileName: "deleted.zip", + FilePath: "deleted.zip", + Size: 1, + MimeType: "application/zip", + Hash: revision, + Type: ofupload.ReservedPagesDeploymentType, + Status: model.UploadStatusDeleted, } - if err := db.DB(ctx).Create(uploadRecord).Error; err != nil { + if err := repository.DB(ctx).Table("w_uploads").Create(uploadRecord).Error; err != nil { t.Fatalf("create deleted upload error = %v, want nil", err) } deployment := &model.PagesDeployment{ @@ -560,10 +557,10 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) { SourceMeta: `{"provider":"remote_url","display_name":"deleted.zip"}`, TriggerType: pagesSourceTriggerManualSync, } - if err := db.DB(ctx).Create(deployment).Error; err != nil { + if err := repository.DB(ctx).Create(deployment).Error; err != nil { t.Fatalf("create source deployment error = %v, want nil", err) } - if err := db.DB(ctx).Create(&model.PagesDeploymentFile{ + if err := repository.DB(ctx).Create(&model.PagesDeploymentFile{ DeploymentID: deployment.ID, Path: "index.html", Size: 1, @@ -600,7 +597,7 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) { t.Errorf("ActiveDeploymentID after deleted upload rejection = %v, want nil", storedProject.ActiveDeploymentID) } var activeCount int64 - if err := db.DB(ctx).Model(&model.PagesDeployment{}). + if err := repository.DB(ctx).Model(&model.PagesDeployment{}). Where("project_id = ? AND status = ?", project.ID, model.PagesDeploymentStatusActive). Count(&activeCount).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { t.Fatalf("count active deployments error = %v, want nil", err) diff --git a/backend/openflare/plugins/server/domain/pages/source_test.go b/backend/openflare/plugins/server/domain/pages/source_test.go index 152aa332..f3f82d6f 100644 --- a/backend/openflare/plugins/server/domain/pages/source_test.go +++ b/backend/openflare/plugins/server/domain/pages/source_test.go @@ -13,16 +13,15 @@ import ( "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) func setupPagesSourceTest(t *testing.T) context.Context { t.Helper() cleanup := setupPagesTestDB(t) t.Cleanup(cleanup) - sqlDB, err := db.DB(t.Context()).DB() + sqlDB, err := repository.DB(t.Context()).DB() if err != nil { - t.Fatalf("db.DB().DB() error = %v, want nil", err) + t.Fatalf("repository.DB().DB() error = %v, want nil", err) } // SQLite :memory: is scoped to one connection. Keeping one connection also // makes lease tests exercise the production CAS without creating empty @@ -93,11 +92,11 @@ func mustConfigureRemoteSource( t.Fatalf("UpdateSource(%d, %q) error = %v, want nil", projectID, remoteURL, err) } var source model.PagesProjectSource - if err := db.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil { + if err := repository.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil { t.Fatalf("load source for project %d error = %v, want nil", projectID, err) } var runtime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { + if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil { t.Fatalf("load runtime for source %d error = %v, want nil", source.ID, err) } return &source, &runtime @@ -187,7 +186,7 @@ func TestRemoteSourceCRUDPreservesSecretAndResetsRuntimeByIdentity(t *testing.T) t.Fatalf("UpdateSource(%d, no-op) error = %v, want nil", project.ID, err) } var unchangedSource model.PagesProjectSource - if err := db.DB(ctx).Where("id = ?", source.ID).First(&unchangedSource).Error; err != nil { + if err := repository.DB(ctx).Where("id = ?", source.ID).First(&unchangedSource).Error; err != nil { t.Fatalf("load no-op source error = %v, want nil", err) } if got, want := unchangedSource.ConfigVersion, source.ConfigVersion; got != want { @@ -197,7 +196,7 @@ func TestRemoteSourceCRUDPreservesSecretAndResetsRuntimeByIdentity(t *testing.T) seenRevision := strings.Repeat("a", 64) appliedRevision := strings.Repeat("b", 64) future := time.Now().Add(time.Hour) - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", source.ID). Updates(map[string]any{ "last_seen_revision": seenRevision, @@ -327,10 +326,10 @@ func TestDeleteSourceIsIdempotentAndKeepsDeploymentState(t *testing.T) { SourceType: "manual_upload", TriggerType: "manual_upload", } - if err := db.DB(ctx).Create(deployment).Error; err != nil { + if err := repository.DB(ctx).Create(deployment).Error; err != nil { t.Fatalf("create deployment error = %v, want nil", err) } - if err := db.DB(ctx).Model(&model.PagesProject{}). + if err := repository.DB(ctx).Model(&model.PagesProject{}). Where("id = ?", project.ID). Update("active_deployment_id", deployment.ID).Error; err != nil { t.Fatalf("set active deployment error = %v, want nil", err) @@ -346,13 +345,13 @@ func TestDeleteSourceIsIdempotentAndKeepsDeploymentState(t *testing.T) { } } var sourceCount, runtimeCount, deploymentCount int64 - if err := db.DB(ctx).Model(&model.PagesProjectSource{}).Where("id = ?", source.ID).Count(&sourceCount).Error; err != nil { + if err := repository.DB(ctx).Model(&model.PagesProjectSource{}).Where("id = ?", source.ID).Count(&sourceCount).Error; err != nil { t.Fatalf("count source error = %v, want nil", err) } - if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Count(&runtimeCount).Error; err != nil { + if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Count(&runtimeCount).Error; err != nil { t.Fatalf("count runtime error = %v, want nil", err) } - if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("id = ?", deployment.ID).Count(&deploymentCount).Error; err != nil { + if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("id = ?", deployment.ID).Count(&deploymentCount).Error; err != nil { t.Fatalf("count deployment error = %v, want nil", err) } if sourceCount != 0 || runtimeCount != 0 || deploymentCount != 1 { diff --git a/backend/openflare/plugins/server/domain/site/apply_log/logics_test.go b/backend/openflare/plugins/server/domain/site/apply_log/logics_test.go index 2e171d5e..9dec97b3 100644 --- a/backend/openflare/plugins/server/domain/site/apply_log/logics_test.go +++ b/backend/openflare/plugins/server/domain/site/apply_log/logics_test.go @@ -11,7 +11,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -29,10 +28,10 @@ func setupApplyLogTestDB(t *testing.T) func() { err = sqliteDB.AutoMigrate(&model.OpenFlareApplyLog{}) require.NoError(t, err) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } @@ -49,7 +48,7 @@ func TestListPageAndCleanup(t *testing.T) { {NodeID: "node-logs", Version: "v3", Result: "success", Message: "3", CreatedAt: now}, } for i := range logs { - require.NoError(t, db.DB(ctx).Create(&logs[i]).Error) + require.NoError(t, repository.DB(ctx).Create(&logs[i]).Error) } pageResult, err := ListPage(ctx, ListQuery{ diff --git a/backend/openflare/plugins/server/domain/site/config_version/certificate_snapshot_test.go b/backend/openflare/plugins/server/domain/site/config_version/certificate_snapshot_test.go index fdcf9914..f0986934 100644 --- a/backend/openflare/plugins/server/domain/site/config_version/certificate_snapshot_test.go +++ b/backend/openflare/plugins/server/domain/site/config_version/certificate_snapshot_test.go @@ -20,7 +20,6 @@ import ( oftls "Wavelet/openflare/plugins/server/domain/tls" "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/runtimeconfig" - db "Wavelet/plugins/infra/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,7 +28,7 @@ import ( func TestBuildCertificateSupportFilesDecryptsSealedPrivateKey(t *testing.T) { cleanup := setupConfigVersionTestDB(t) defer cleanup() - require.NoError(t, db.DB(context.Background()).AutoMigrate(&model.TLSCertificate{})) + require.NoError(t, repository.DB(context.Background()).AutoMigrate(&model.TLSCertificate{})) previous := runtimeconfig.Get() runtimeconfig.SetSessionSecret("test-session-secret-for-tls-seal") @@ -65,7 +64,7 @@ func TestBuildSnapshotReadsZoneDomainCertificates(t *testing.T) { cleanup := setupConfigVersionTestDB(t) defer cleanup() ctx := context.Background() - require.NoError(t, db.DB(ctx).AutoMigrate(&model.TLSCertificate{})) + require.NoError(t, repository.DB(ctx).AutoMigrate(&model.TLSCertificate{})) previous := runtimeconfig.Get() runtimeconfig.SetSessionSecret("test-session-secret-for-zone-domain-snapshots") @@ -81,9 +80,9 @@ func TestBuildSnapshotReadsZoneDomainCertificates(t *testing.T) { route := &model.ProxyRoute{SiteName: "tls-site", OriginURL: "http://origin:8080", Upstreams: `["http://origin:8080"]`, Enabled: true, EnableHTTPS: true} require.NoError(t, repository.CreateProxyRouteRecord(ctx, route)) zone := &model.Zone{Domain: "example.com"} - require.NoError(t, db.DB(ctx).Create(zone).Error) - require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "one.example.com", CertID: &first.ID}).Error) - require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "two.example.com", CertID: &second.ID}).Error) + require.NoError(t, repository.DB(ctx).Create(zone).Error) + require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "one.example.com", CertID: &first.ID}).Error) + require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "two.example.com", CertID: &second.ID}).Error) bundle, err := buildCurrentConfigBundle(ctx, true) require.NoError(t, err) diff --git a/backend/openflare/plugins/server/domain/site/config_version/logics_test.go b/backend/openflare/plugins/server/domain/site/config_version/logics_test.go index 24032047..def324ae 100644 --- a/backend/openflare/plugins/server/domain/site/config_version/logics_test.go +++ b/backend/openflare/plugins/server/domain/site/config_version/logics_test.go @@ -16,7 +16,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" openrestyrender "Wavelet/openflare/share/render/openresty" "Wavelet/pkg/cache/ram" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -45,9 +44,9 @@ func setupConfigVersionTestDB(t *testing.T) func() { &model.SystemConfig{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) ram.ResetForTest() } } @@ -55,9 +54,9 @@ func setupConfigVersionTestDB(t *testing.T) func() { func createSnapshotZoneDomains(t *testing.T, ctx context.Context, route *model.ProxyRoute, domains ...string) { t.Helper() zone := &model.Zone{Domain: fmt.Sprintf("zone-%d.example", route.ID)} - require.NoError(t, db.DB(ctx).Create(zone).Error) + require.NoError(t, repository.DB(ctx).Create(zone).Error) for _, domain := range domains { - require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{ + require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{ ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: domain, @@ -69,7 +68,7 @@ func TestListConfigVersionsOrdersByCreatedAtDesc(t *testing.T) { cleanup := setupConfigVersionTestDB(t) defer cleanup() ctx := context.Background() - conn := db.DB(ctx) + conn := repository.DB(ctx) require.NotNil(t, conn) newer := &model.ConfigVersion{ @@ -220,7 +219,7 @@ func TestBuildCurrentConfigBundleEnablesGlobalPoWWithoutExplicitBinding(t *testi graphJSON, err := json.Marshal(snapshotPoWGraph()) require.NoError(t, err) globalGroup.Graph = string(graphJSON) - require.NoError(t, db.DB(ctx).Model(globalGroup).Update("graph", globalGroup.Graph).Error) + require.NoError(t, repository.DB(ctx).Model(globalGroup).Update("graph", globalGroup.Graph).Error) bundle, err := buildCurrentConfigBundle(ctx, true) require.NoError(t, err) diff --git a/backend/openflare/plugins/server/domain/site/config_version/origin_error_page_snapshot_test.go b/backend/openflare/plugins/server/domain/site/config_version/origin_error_page_snapshot_test.go index 58a2cae9..03007971 100644 --- a/backend/openflare/plugins/server/domain/site/config_version/origin_error_page_snapshot_test.go +++ b/backend/openflare/plugins/server/domain/site/config_version/origin_error_page_snapshot_test.go @@ -9,13 +9,12 @@ import ( "testing" "Wavelet/openflare/plugins/server/kernel/model" + "Wavelet/openflare/plugins/server/kernel/repository" + "Wavelet/openflare/plugins/server/kernel/testhelper" "Wavelet/pkg/cache/ram" - db "Wavelet/plugins/infra/database" - "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gorm.io/gorm" ) func setupOriginErrorPageSnapshotDB(t *testing.T) func() { @@ -25,15 +24,10 @@ func setupOriginErrorPageSnapshotDB(t *testing.T) func() { // 重置,否则 shuffle 下先跑的用例会污染后跑的用例。 ram.ResetForTest() - sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ - DisableForeignKeyConstraintWhenMigrating: true, - }) - require.NoError(t, err) - require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{})) - db.SetDB(sqliteDB) + _, _, cleanup := testhelper.SetupTestEnvironment(t) return func() { - db.SetDB(nil) + cleanup() ram.ResetForTest() } } @@ -58,15 +52,9 @@ func TestBuildOpenRestyConfigSnapshotOriginErrorPageCustom(t *testing.T) { defer cleanup() ctx := context.Background() - require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{ - Key: model.ConfigKeyOriginErrorPageEnabled, Value: "false", Type: "business", - }).Error) - require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{ - Key: model.ConfigKeyOriginErrorPageStatusCodes, Value: `["522","500-502"]`, Type: "business", - }).Error) - require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{ - Key: model.ConfigKeyOriginErrorPageHTML, Value: "

{{status}}

", Type: "business", - }).Error) + require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageEnabled, "false")) + require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageStatusCodes, `["522","500-502"]`)) + require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageHTML, "

{{status}}

")) snapshot := buildOpenRestyConfigSnapshot(ctx) assert.False(t, snapshot.OriginErrorPageEnabled) diff --git a/backend/openflare/plugins/server/domain/site/config_version/pages_snapshot_test.go b/backend/openflare/plugins/server/domain/site/config_version/pages_snapshot_test.go index ecdbc7db..3ba9202b 100644 --- a/backend/openflare/plugins/server/domain/site/config_version/pages_snapshot_test.go +++ b/backend/openflare/plugins/server/domain/site/config_version/pages_snapshot_test.go @@ -12,7 +12,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" openrestyrender "Wavelet/openflare/share/render/openresty" - db "Wavelet/plugins/infra/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -95,7 +94,7 @@ func TestBuildSnapshotPagesDeploymentRejectsUnsafeStoredPaths(t *testing.T) { func requireDB(t *testing.T, ctx context.Context) *gorm.DB { t.Helper() - conn := db.DB(ctx) + conn := repository.DB(ctx) require.NotNil(t, conn) require.NoError(t, conn.AutoMigrate( &model.PagesProject{}, diff --git a/backend/openflare/plugins/server/domain/site/config_version/waf_graph_snapshot_test.go b/backend/openflare/plugins/server/domain/site/config_version/waf_graph_snapshot_test.go index e523d463..76e72eed 100644 --- a/backend/openflare/plugins/server/domain/site/config_version/waf_graph_snapshot_test.go +++ b/backend/openflare/plugins/server/domain/site/config_version/waf_graph_snapshot_test.go @@ -13,7 +13,6 @@ import ( "Wavelet/openflare/plugins/server/domain/waf" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -38,7 +37,7 @@ func TestBuildSnapshotRejectsOversizedAggregateWAFIPGroups(t *testing.T) { Enabled: true, IPList: string(ipList), } - require.NoError(t, db.DB(ctx).Create(group).Error) + require.NoError(t, repository.DB(ctx).Create(group).Error) groupIDs = append(groupIDs, group.ID) } createSnapshotRule(t, ctx, "oversized-aggregate", snapshotIPMatchGraphForGroups(groupIDs)) @@ -59,8 +58,8 @@ func TestWAFGraphSnapshotPreservesOrderAndGraphReferences(t *testing.T) { referenced := &model.OpenFlareWAFIPGroup{Name: "referenced", Type: "manual", Enabled: true, IPList: `["192.0.2.1"]`} unused := &model.OpenFlareWAFIPGroup{Name: "unused", Type: "manual", Enabled: true, IPList: `["198.51.100.1"]`} - require.NoError(t, db.DB(ctx).Create(referenced).Error) - require.NoError(t, db.DB(ctx).Create(unused).Error) + require.NoError(t, repository.DB(ctx).Create(referenced).Error) + require.NoError(t, repository.DB(ctx).Create(unused).Error) customA := createSnapshotRule(t, ctx, "custom-a", waf.DefaultRuleGraph()) customB := createSnapshotRule(t, ctx, "custom-b", snapshotIPMatchGraph(referenced.ID)) @@ -114,7 +113,7 @@ func TestBuildSnapshotRejectsInvalidWAFGraph(t *testing.T) { ctx := context.Background() invalid := &model.OpenFlareWAFRuleGroup{Name: "invalid", Enabled: true, Graph: `{"schema_version":1,"nodes":[],"edges":[]}`, Revision: 1} - require.NoError(t, db.DB(ctx).Create(invalid).Error) + require.NoError(t, repository.DB(ctx).Create(invalid).Error) _, err := buildSnapshotWAFDocument(ctx, nil) require.ErrorContains(t, err, "invalid") } @@ -124,7 +123,7 @@ func createSnapshotRule(t *testing.T, ctx context.Context, name string, graph wa raw, err := json.Marshal(graph) require.NoError(t, err) rule := &model.OpenFlareWAFRuleGroup{Name: name, Enabled: true, Graph: string(raw), Revision: 1} - require.NoError(t, db.DB(ctx).Create(rule).Error) + require.NoError(t, repository.DB(ctx).Create(rule).Error) return rule } diff --git a/backend/openflare/plugins/server/domain/site/origin/logics_test.go b/backend/openflare/plugins/server/domain/site/origin/logics_test.go index 69c80bd6..8eb82507 100644 --- a/backend/openflare/plugins/server/domain/site/origin/logics_test.go +++ b/backend/openflare/plugins/server/domain/site/origin/logics_test.go @@ -8,7 +8,7 @@ import ( "testing" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/repository" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -25,9 +25,9 @@ func setupOriginTestDB(t *testing.T) func() { require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.Origin{})) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } diff --git a/backend/openflare/plugins/server/domain/site/proxy_route/logics_test.go b/backend/openflare/plugins/server/domain/site/proxy_route/logics_test.go index 844e5b44..20b82ece 100644 --- a/backend/openflare/plugins/server/domain/site/proxy_route/logics_test.go +++ b/backend/openflare/plugins/server/domain/site/proxy_route/logics_test.go @@ -9,7 +9,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/repository" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -29,21 +28,21 @@ func setupProxyRouteTestDB(t *testing.T) func() { &model.TLSCertificate{}, &model.PagesProject{}, )) - db.SetDB(sqliteDB) - return func() { db.SetDB(nil) } + repository.SetDBForTest(sqliteDB) + return func() { repository.SetDBForTest(nil) } } func createZoneDomain(t *testing.T, ctx context.Context, domain string, certID *uint) *model.ZoneDomain { t.Helper() zone := &model.Zone{Domain: "example.com"} var existing model.Zone - if err := db.DB(ctx).Where("domain = ?", zone.Domain).First(&existing).Error; err == nil { + if err := repository.DB(ctx).Where("domain = ?", zone.Domain).First(&existing).Error; err == nil { zone = &existing } else { - require.NoError(t, db.DB(ctx).Create(zone).Error) + require.NoError(t, repository.DB(ctx).Create(zone).Error) } item := &model.ZoneDomain{ZoneID: zone.ID, Domain: domain, CertID: certID} - require.NoError(t, db.DB(ctx).Create(item).Error) + require.NoError(t, repository.DB(ctx).Create(item).Error) return item } @@ -102,7 +101,7 @@ func TestPagesRouteLocksAndRevalidatesTargetProject(t *testing.T) { Enabled: true, ActiveDeploymentID: &activeDeploymentID, } - require.NoError(t, db.DB(ctx).Create(project).Error) + require.NoError(t, repository.DB(ctx).Create(project).Error) view, err := CreateProxyRoute(ctx, Input{ SiteName: "pages", @@ -115,7 +114,7 @@ func TestPagesRouteLocksAndRevalidatesTargetProject(t *testing.T) { require.NotNil(t, view.PagesProjectID) assert.Equal(t, project.ID, *view.PagesProjectID) - require.NoError(t, db.DB(ctx).Delete(&model.PagesProject{}, project.ID).Error) + require.NoError(t, repository.DB(ctx).Delete(&model.PagesProject{}, project.ID).Error) route := &model.ProxyRoute{UpstreamType: proxyRouteUpstreamTypePages, PagesProjectID: &project.ID} err = repository.WithProxyRouteTx(ctx, func(tx *gorm.DB) error { return lockPagesProjectsForRouteMutation(tx, 0, route) diff --git a/backend/openflare/plugins/server/domain/site/zone/legacy_import_test.go b/backend/openflare/plugins/server/domain/site/zone/legacy_import_test.go index eb624686..ce12c196 100644 --- a/backend/openflare/plugins/server/domain/site/zone/legacy_import_test.go +++ b/backend/openflare/plugins/server/domain/site/zone/legacy_import_test.go @@ -8,7 +8,7 @@ import ( "database/sql" "testing" - db "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/repository" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -65,10 +65,10 @@ func setupLegacyImportDB(t *testing.T) (*sql.DB, func()) { require.NoError(t, err) } - previous := db.DB(context.Background()) - db.SetDB(gormDB) + previous := repository.DB(context.Background()) + repository.SetDBForTest(gormDB) return sqlDB, func() { - db.SetDB(previous) + repository.SetDBForTest(previous) _ = sqlDB.Close() } } diff --git a/backend/openflare/plugins/server/domain/site/zone/logics_test.go b/backend/openflare/plugins/server/domain/site/zone/logics_test.go index 0637a8e3..0e140910 100644 --- a/backend/openflare/plugins/server/domain/site/zone/logics_test.go +++ b/backend/openflare/plugins/server/domain/site/zone/logics_test.go @@ -12,7 +12,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/testhelper" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/require" @@ -24,8 +23,8 @@ func setupZoneDB(t *testing.T) context.Context { conn, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}) require.NoError(t, err) require.NoError(t, conn.AutoMigrate(&model.Zone{}, &model.ZoneDomain{}, &model.TLSCertificate{}, &model.CFPointingGroup{}, &model.CFPointingMember{})) - db.SetDB(conn) - t.Cleanup(func() { db.SetDB(nil) }) + repository.SetDBForTest(conn) + t.Cleanup(func() { repository.SetDBForTest(nil) }) return context.Background() } diff --git a/backend/openflare/plugins/server/domain/tls/logics_test.go b/backend/openflare/plugins/server/domain/tls/logics_test.go index 38d07f92..be4f1dac 100644 --- a/backend/openflare/plugins/server/domain/tls/logics_test.go +++ b/backend/openflare/plugins/server/domain/tls/logics_test.go @@ -24,7 +24,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/runtimeconfig" "Wavelet/pkg/idgen" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -51,7 +50,7 @@ func setupTLSTestDB(t *testing.T) func() { &model.TaskExecution{}, // 异步任务执行记录也需要 migrate )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) require.NoError(t, idgen.Init(1)) previous := runtimeconfig.Get() runtimeconfig.SetSessionSecret("test_session_secret_for_tls_encryption") @@ -59,7 +58,7 @@ func setupTLSTestDB(t *testing.T) func() { oftask.SetService(&testhelper.NoopTaskService{}) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) runtimeconfig.Set(previous) credential.SetSessionSecret(previous.SessionSecret) tlsTestDBMu.Unlock() @@ -75,8 +74,8 @@ func TestDeleteCertificateRejectsZoneDomainReference(t *testing.T) { certificate, err := CreateCertificate(ctx, CertificateInput{Name: "api-cert", CertPEM: certPEM, KeyPEM: keyPEM}) require.NoError(t, err) zone := &model.Zone{Domain: "example.com"} - require.NoError(t, db.DB(ctx).Create(zone).Error) - require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, Domain: "api.example.com", CertID: &certificate.ID}).Error) + require.NoError(t, repository.DB(ctx).Create(zone).Error) + require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, Domain: "api.example.com", CertID: &certificate.ID}).Error) err = DeleteCertificate(ctx, certificate.ID) require.EqualError(t, err, errCertificateDeleteReferenced) diff --git a/backend/openflare/plugins/server/domain/tls/ssl_renew_test.go b/backend/openflare/plugins/server/domain/tls/ssl_renew_test.go index fecee873..fe2965a5 100644 --- a/backend/openflare/plugins/server/domain/tls/ssl_renew_test.go +++ b/backend/openflare/plugins/server/domain/tls/ssl_renew_test.go @@ -13,7 +13,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/runtimeconfig" oftask "Wavelet/openflare/plugins/server/kernel/task" "Wavelet/openflare/plugins/server/kernel/testhelper" - db "Wavelet/plugins/infra/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -23,7 +22,7 @@ func setupSSLRenewTestDB(t *testing.T) func() { t.Helper() _, _, cleanup := testhelper.SetupTestEnvironment(t) - require.NoError(t, db.DB(context.Background()).AutoMigrate(&model.TLSCertificate{}, &model.TaskExecution{})) + require.NoError(t, repository.DB(context.Background()).AutoMigrate(&model.TLSCertificate{}, &model.TaskExecution{})) previous := runtimeconfig.Get() runtimeconfig.SetSessionSecret("test_session_secret_for_ssl_renew") oftask.SetService(&testhelper.NoopTaskService{}) diff --git a/backend/openflare/plugins/server/domain/waf/ip_group_sync_test.go b/backend/openflare/plugins/server/domain/waf/ip_group_sync_test.go index 83419ee2..1ec9f3f2 100644 --- a/backend/openflare/plugins/server/domain/waf/ip_group_sync_test.go +++ b/backend/openflare/plugins/server/domain/waf/ip_group_sync_test.go @@ -15,7 +15,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/testhelper" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -35,10 +34,10 @@ func setupIPGroupSyncTestDB(t *testing.T) func() { &model.OpenFlareWAFIPGroup{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) testhelper.SetupLogStoresForTest(t) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } diff --git a/backend/openflare/plugins/server/domain/waf/logics_test.go b/backend/openflare/plugins/server/domain/waf/logics_test.go index 544fed91..e7d6ac4e 100644 --- a/backend/openflare/plugins/server/domain/waf/logics_test.go +++ b/backend/openflare/plugins/server/domain/waf/logics_test.go @@ -10,7 +10,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -32,9 +31,9 @@ func setupWAFTestDB(t *testing.T) func() { &model.OriginProxyRoute{}, )) - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + repository.SetDBForTest(nil) } } diff --git a/backend/openflare/plugins/server/domain/waf/rule_logics_test.go b/backend/openflare/plugins/server/domain/waf/rule_logics_test.go index 88c78edd..2b8d592e 100644 --- a/backend/openflare/plugins/server/domain/waf/rule_logics_test.go +++ b/backend/openflare/plugins/server/domain/waf/rule_logics_test.go @@ -16,7 +16,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/pkg/response" - db "Wavelet/plugins/infra/database" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -84,7 +83,7 @@ func TestRuleHandlersMapFailures(t *testing.T) { require.NoError(t, err) return cleanup }, want: http.StatusConflict}, - {name: "database failure", method: http.MethodGet, path: "/rules", setup: func(t *testing.T) func() { t.Helper(); db.SetDB(nil); return func() {} }, want: http.StatusInternalServerError}, + {name: "database failure", method: http.MethodGet, path: "/rules", setup: func(t *testing.T) func() { t.Helper(); repository.SetDBForTest(nil); return func() {} }, want: http.StatusInternalServerError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -157,7 +156,7 @@ func TestReplaceSiteRuleGroupsPreservesOrderAndRejectsGlobal(t *testing.T) { defer cleanup() ctx := context.Background() - require.NoError(t, db.DB(ctx).Create(&model.OriginProxyRoute{ID: 7, Domain: "example.com"}).Error) + require.NoError(t, repository.DB(ctx).Create(&model.OriginProxyRoute{ID: 7, Domain: "example.com"}).Error) first, err := CreateRule(ctx, CreateRuleInput{Name: "first"}) require.NoError(t, err) second, err := CreateRule(ctx, CreateRuleInput{Name: "second"}) diff --git a/backend/openflare/plugins/server/kernel/geoip/runtime_test.go b/backend/openflare/plugins/server/kernel/geoip/runtime_test.go index 3b8e9c40..40554e60 100644 --- a/backend/openflare/plugins/server/kernel/geoip/runtime_test.go +++ b/backend/openflare/plugins/server/kernel/geoip/runtime_test.go @@ -8,8 +8,8 @@ import ( "testing" "Wavelet/openflare/plugins/server/kernel/model" + "Wavelet/openflare/plugins/server/kernel/repository" pkggeoip "Wavelet/openflare/share/geoip" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "gorm.io/gorm" @@ -23,16 +23,16 @@ func TestEnsureRuntimeProviderInitializesConfiguredProvider(t *testing.T) { if err := sqliteDB.AutoMigrate(&model.SystemConfig{}); err != nil { t.Fatalf("migrate: %v", err) } - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) t.Cleanup(func() { - db.SetDB(nil) + repository.SetDBForTest(nil) ResetRuntimeForTest() }) ctx := context.Background() ResetRuntimeForTest() // 通过 SystemConfig 设置 GeoIPProvider 配置 - if err := db.DB(ctx).Create(&model.SystemConfig{ + if err := repository.DB(ctx).Create(&model.SystemConfig{ Key: model.ConfigKeyGeoIPProvider, Value: pkggeoip.ProviderIPInfo, Type: "business", diff --git a/backend/openflare/plugins/server/kernel/model/analytics/user_access_log.go b/backend/openflare/plugins/server/kernel/model/analytics/user_access_log.go index 46ab3759..83a7c8e5 100644 --- a/backend/openflare/plugins/server/kernel/model/analytics/user_access_log.go +++ b/backend/openflare/plugins/server/kernel/model/analytics/user_access_log.go @@ -4,8 +4,34 @@ package analytics import ( - risklogstore "Wavelet/plugins/domain/risk_control/logstore" + "time" ) -// UserAccessLog is Wavelet risk_control's w_user_access_logs entity. -type UserAccessLog = risklogstore.UserAccessLog +const ( + userAccessLogTableName = "w_user_access_logs" + userAccessLogInsertColumns = "id, user_id, path, method, ip, user_agent, headers, status, latency, created_at" +) + +// UserAccessLog represents a user HTTP access log entry. +type UserAccessLog struct { + ID uint64 `gorm:"column:id"` + UserID uint64 `gorm:"column:user_id"` + Path string `gorm:"column:path"` + Method string `gorm:"column:method"` + IP string `gorm:"column:ip"` + UserAgent string `gorm:"column:user_agent"` + Headers string `gorm:"column:headers"` + Status int32 `gorm:"column:status"` + Latency int64 `gorm:"column:latency"` + CreatedAt time.Time `gorm:"column:created_at"` +} + +// TableName returns the table name. +func (UserAccessLog) TableName() string { + return userAccessLogTableName +} + +// InsertColumns returns comma-separated column names for batch insert. +func (UserAccessLog) InsertColumns() string { + return userAccessLogInsertColumns +} diff --git a/backend/openflare/plugins/server/kernel/model/platform_aliases.go b/backend/openflare/plugins/server/kernel/model/platform_aliases.go index 8a339e52..895030a1 100644 --- a/backend/openflare/plugins/server/kernel/model/platform_aliases.go +++ b/backend/openflare/plugins/server/kernel/model/platform_aliases.go @@ -9,10 +9,9 @@ import ( "encoding/hex" "fmt" - adminmodel "Wavelet/plugins/domain/admin/model" - authmodel "Wavelet/plugins/domain/auth" - uploadmodels "Wavelet/plugins/domain/upload/models" - usermodel "Wavelet/plugins/domain/user" + "time" + + "Wavelet/core/contracts" ) const ( @@ -20,57 +19,161 @@ const ( maskThreshold = 8 ) -// User is the Wavelet w_users entity. -type User = usermodel.User +// User represents a user identity view. +type User struct { + ID uint64 `json:"id,string" gorm:"primaryKey"` + Username string `json:"username"` + Password string `json:"-"` + Nickname string `json:"nickname"` + Email string `json:"email"` + IsAdmin bool `json:"is_admin"` + IsActive bool `json:"is_active"` + LastLoginAt time.Time `json:"last_login_at"` +} -// AccessToken is the Wavelet w_access_tokens entity. -type AccessToken = usermodel.AccessToken +func (User) TableName() string { + return "w_users" +} -// AuthSource is the Wavelet w_auth_sources entity. -type AuthSource = authmodel.AuthSource +func (u *User) SetEncryptedPassword(pwd string) error { + u.Password = pwd + return nil +} -// ExternalAccount is the Wavelet w_external_accounts entity. -type ExternalAccount = authmodel.ExternalAccount +// AccessToken represents an access token view. +type AccessToken struct { + ID uint64 `json:"id" gorm:"primaryKey"` + UserID uint64 `json:"user_id"` + Name string `json:"name"` + Token string `json:"token"` + MaskedToken string `json:"masked_token"` + TokenHash string `json:"token_hash"` + IsAdmin bool `json:"is_admin"` + ExpiredAt time.Time `json:"expired_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} -// TaskExecution is the Wavelet w_task_executions entity. -type TaskExecution = adminmodel.TaskExecution +func (AccessToken) TableName() string { + return "w_access_tokens" +} -// Template is the Wavelet w_templates entity. -type Template = adminmodel.Template +// AuthSource represents an authentication source view. +type AuthSource struct { + ID uint64 `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + DisplayName string `json:"display_name"` + IconURL string `json:"icon_url"` + IsActive bool `json:"is_active"` +} -// Schedule is the Wavelet w_schedules entity. -type Schedule = adminmodel.Schedule +// TaskExecution represents task execution entity. +type TaskExecution struct { + ID uint64 `json:"id" gorm:"primaryKey"` + TaskID string `json:"task_id" gorm:"size:64;index"` + TaskType string `json:"task_type" gorm:"size:100;index"` + TaskName string `json:"task_name" gorm:"size:255"` + Status string `json:"status" gorm:"size:20;index"` + Retryable bool `json:"retryable"` + MaxRetry int `json:"max_retry"` + RetryCount int `json:"retry_count"` + Log string `json:"log" gorm:"type:text"` + ErrorMessage string `json:"error_message" gorm:"type:text"` + Result string `json:"result" gorm:"type:text"` + StartedAt *time.Time `json:"started_at"` + FinishedAt *time.Time `json:"finished_at"` + Duration int64 `json:"duration"` + Payload string `json:"payload" gorm:"type:text"` + TriggeredBy string `json:"triggered_by" gorm:"size:100"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} -// Upload is the Wavelet w_uploads entity. -type Upload = uploadmodels.Upload +func (TaskExecution) TableName() string { + return "w_task_executions" +} -// UploadMetadata is the Wavelet upload metadata JSON. -type UploadMetadata = uploadmodels.UploadMetadata - -// UploadStatus is the Wavelet upload status. -type UploadStatus = uploadmodels.UploadStatus - -// UploadStat is the Wavelet w_upload_stats entity. -type UploadStat = uploadmodels.UploadStat +type UploadStatus = string const ( - // UploadStatusPending is a newly stored unused upload. - UploadStatusPending = uploadmodels.UploadStatusPending - // UploadStatusUsed is an in-use upload. - UploadStatusUsed = uploadmodels.UploadStatusUsed - // UploadStatusDeleted is a soft-deleted upload. - UploadStatusDeleted = uploadmodels.UploadStatusDeleted - - // UploadStatDimensionTotal is the total stats dimension. - UploadStatDimensionTotal = uploadmodels.UploadStatDimensionTotal - // UploadStatDimensionType is the type stats dimension. - UploadStatDimensionType = uploadmodels.UploadStatDimensionType - // UploadStatDimensionCategory is the category stats dimension. - UploadStatDimensionCategory = uploadmodels.UploadStatDimensionCategory - // UploadStatDimensionTrend is the trend stats dimension. - UploadStatDimensionTrend = uploadmodels.UploadStatDimensionTrend + UploadStatusPending UploadStatus = "pending" + UploadStatusUsed UploadStatus = "used" + UploadStatusDeleted UploadStatus = "deleted" ) +// UploadMetadata represents upload metadata JSON. +type UploadMetadata = contracts.UploadMetadataDTO + +// Upload represents file upload entity. +type Upload struct { + ID uint64 `json:"id" gorm:"primaryKey"` + UserID uint64 `json:"user_id" gorm:"index"` + FileName string `json:"file_name" gorm:"size:255"` + FilePath string `json:"file_path" gorm:"size:500"` + MimeType string `json:"mime_type" gorm:"size:100"` + Size int64 `json:"size"` + Hash string `json:"hash" gorm:"size:64"` + Status string `json:"status" gorm:"type:varchar(20)"` + Type string `json:"type" gorm:"size:50;index"` + Metadata contracts.UploadMetadataDTO `json:"metadata"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (Upload) TableName() string { + return "w_uploads" +} + +func (u *Upload) ToDTO() contracts.UploadDTO { + return contracts.UploadDTO{ + ID: u.ID, + UserID: u.UserID, + FileName: u.FileName, + FilePath: u.FilePath, + MimeType: u.MimeType, + Size: u.Size, + Hash: u.Hash, + Status: u.Status, + Type: u.Type, + Metadata: u.Metadata, + CreatedAt: u.CreatedAt, + UpdatedAt: u.UpdatedAt, + } +} + +func FromUploadDTO(d contracts.UploadDTO) Upload { + return Upload{ + ID: d.ID, + UserID: d.UserID, + FileName: d.FileName, + FilePath: d.FilePath, + MimeType: d.MimeType, + Size: d.Size, + Hash: d.Hash, + Status: d.Status, + Type: d.Type, + Metadata: d.Metadata, + CreatedAt: d.CreatedAt, + UpdatedAt: d.UpdatedAt, + } +} + +const UploadStatDimensionTotal = "total" + +// UploadStat tracks upload statistics by dimension. +type UploadStat struct { + ID uint64 `gorm:"primaryKey"` + Dimension string `gorm:"size:50;not null"` + TargetID uint64 `gorm:"not null"` + TotalSize int64 `gorm:"not null"` + FileCount int `gorm:"not null"` +} + +func (UploadStat) TableName() string { + return "w_upload_stats" +} + // GenerateTokenString 生成加密安全的随机 Token 值 func GenerateTokenString() (string, error) { bytes := make([]byte, tokenByteLength) diff --git a/backend/openflare/plugins/server/kernel/model/system_configs.go b/backend/openflare/plugins/server/kernel/model/system_configs.go index d47be0c7..ced21263 100644 --- a/backend/openflare/plugins/server/kernel/model/system_configs.go +++ b/backend/openflare/plugins/server/kernel/model/system_configs.go @@ -4,7 +4,9 @@ package model import ( - adminmodel "Wavelet/plugins/domain/admin/model" + "time" + + "Wavelet/core/contracts" ) // 配置键常量 - 所有系统配置的 key 定义 @@ -136,10 +138,46 @@ const ( const ( // ConfigVisibilityHidden 表示配置不通过公共配置接口暴露 - ConfigVisibilityHidden = adminmodel.ConfigVisibilityHidden + ConfigVisibilityHidden = 0 // ConfigVisibilityVisible 表示配置通过公共配置接口暴露 - ConfigVisibilityVisible = adminmodel.ConfigVisibilityVisible + ConfigVisibilityVisible = 1 ) -// SystemConfig is the Wavelet w_system_configs entity. -type SystemConfig = adminmodel.SystemConfig +// SystemConfig is the system configuration model. +type SystemConfig struct { + Key string `json:"key" gorm:"primaryKey"` + Value string `json:"value"` + Type string `json:"type"` + Visibility int `json:"visibility"` + Description string `json:"description"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +func (SystemConfig) TableName() string { + return "w_system_configs" +} + +func (c *SystemConfig) ToDTO() contracts.SystemConfigDTO { + return contracts.SystemConfigDTO{ + Key: c.Key, + Value: c.Value, + Type: c.Type, + Visibility: c.Visibility, + Description: c.Description, + UpdatedAt: c.UpdatedAt, + CreatedAt: c.CreatedAt, + } +} + +func FromSystemConfigDTO(d contracts.SystemConfigDTO) SystemConfig { + return SystemConfig{ + Key: d.Key, + Value: d.Value, + Type: d.Type, + Visibility: d.Visibility, + Description: d.Description, + UpdatedAt: d.UpdatedAt, + CreatedAt: d.CreatedAt, + } +} diff --git a/backend/openflare/plugins/server/kernel/ofupload/ofupload.go b/backend/openflare/plugins/server/kernel/ofupload/ofupload.go index 314ad9b5..32aaa3cf 100644 --- a/backend/openflare/plugins/server/kernel/ofupload/ofupload.go +++ b/backend/openflare/plugins/server/kernel/ofupload/ofupload.go @@ -13,9 +13,7 @@ import ( "sync" "Wavelet/core/contracts" - waveletupload "Wavelet/plugins/domain/upload" - "Wavelet/plugins/domain/upload/models" - "Wavelet/plugins/infra/database" + "Wavelet/openflare/plugins/server/kernel/model" ) // ReservedPagesDeploymentType is managed exclusively by the Pages domain. @@ -23,40 +21,77 @@ const ReservedPagesDeploymentType = "openflare_pages_deployment" const ( // PolicyCreate always stores a new object and creates a new upload record. - PolicyCreate = waveletupload.PolicyCreate + PolicyCreate = 1 // PolicyDedupNewRecord reuses an existing object path on hash match but creates a new record. - PolicyDedupNewRecord = waveletupload.PolicyDedupNewRecord + PolicyDedupNewRecord = 2 // PolicyResolveExisting returns an existing upload on hash match. - PolicyResolveExisting = waveletupload.PolicyResolveExisting + PolicyResolveExisting = 3 ) -type ( - // IngestRequest is the programmatic upload ingest payload. - IngestRequest = waveletupload.IngestRequest - // IngestResult reports ingest side effects. - IngestResult = waveletupload.IngestResult - // IngestPolicy controls hash-collision behavior during ingest. - IngestPolicy = waveletupload.IngestPolicy -) +// IngestRequest is the programmatic upload ingest payload. +type IngestRequest struct { + UserID uint64 + Type string + FileName string + MimeType string + Extension string + Size int64 + Policy int + Hash string + Reader io.Reader + AccessMode *int + SkipExtensionCheck bool + Metadata model.UploadMetadata +} + +// IngestResult reports ingest side effects. +type IngestResult struct { + Upload contracts.UploadDTO + Created bool + Stored bool + Resolved bool +} + +// IngestPolicy controls hash-collision behavior during ingest. +type IngestPolicy = int var ( - storageMu sync.RWMutex + svcMu sync.RWMutex storageSvc contracts.StorageService + uploadSvc contracts.UploadService ) // SetStorage injects the platform StorageService used to open stored objects. func SetStorage(s contracts.StorageService) { - storageMu.Lock() - defer storageMu.Unlock() + svcMu.Lock() + defer svcMu.Unlock() storageSvc = s } -func currentStorage() contracts.StorageService { - storageMu.RLock() - defer storageMu.RUnlock() +// SetUploadService injects the platform UploadService. +func SetUploadService(s contracts.UploadService) { + svcMu.Lock() + defer svcMu.Unlock() + uploadSvc = s +} + +// CurrentStorage returns the currently registered storage service. +func CurrentStorage() contracts.StorageService { + svcMu.RLock() + defer svcMu.RUnlock() return storageSvc } +func currentStorage() contracts.StorageService { + return CurrentStorage() +} + +func currentUpload() contracts.UploadService { + svcMu.RLock() + defer svcMu.RUnlock() + return uploadSvc +} + // IngestFromLocalPath ingests a local regular file through Wavelet upload ingest. func IngestFromLocalPath(ctx context.Context, localPath string, req IngestRequest) (IngestResult, error) { localPath = strings.TrimSpace(localPath) @@ -79,55 +114,87 @@ func IngestFromLocalPath(ctx context.Context, localPath string, req IngestReques if req.Size <= 0 { req.Size = info.Size() } - req.Reader = file - return waveletupload.Ingest(ctx, req) + + storage := currentStorage() + if storage == nil { + return IngestResult{}, errors.New("storage service not available") + } + res, err := storage.Ingest(ctx, file, contracts.IngestOptions{ + UserID: req.UserID, + Type: req.Type, + FileName: req.FileName, + MimeType: req.MimeType, + Extension: req.Extension, + Size: req.Size, + Policy: req.Policy, + Metadata: req.Metadata.Extra, + }) + if err != nil { + return IngestResult{}, err + } + + uploadRecord, err := GetActiveUpload(ctx, res.ID) + if err != nil { + return IngestResult{}, err + } + + return IngestResult{ + Upload: uploadRecord, + Created: res.Created, + Stored: res.Stored, + Resolved: res.Resolved, + }, nil } // GetActiveUpload loads an active (non-deleted) upload by ID. -func GetActiveUpload(ctx context.Context, id uint64) (models.Upload, error) { - conn := database.DB(ctx) - if conn == nil { - return models.Upload{}, errors.New("database not initialized") +func GetActiveUpload(ctx context.Context, id uint64) (contracts.UploadDTO, error) { + svc := currentUpload() + if svc == nil { + return contracts.UploadDTO{}, errors.New("upload service not available") } - var upload models.Upload - err := conn.Where("id = ? AND status <> ?", id, models.UploadStatusDeleted).First(&upload).Error - return upload, err + u, err := svc.GetByID(ctx, id) + if err != nil { + return contracts.UploadDTO{}, err + } + if u == nil { + return contracts.UploadDTO{}, errors.New("upload not found") + } + return *u, nil } // OpenedUploadObject is a stored object stream plus the upload record. type OpenedUploadObject struct { - Upload models.Upload + Upload contracts.UploadDTO Body io.ReadCloser ContentType string ContentLength int64 } -// OpenStoredUpload opens the stored object for an active upload via StorageService. +// OpenStoredUpload opens the stored object for an active upload via UploadService. func OpenStoredUpload(ctx context.Context, id uint64) (*OpenedUploadObject, error) { - upload, err := GetActiveUpload(ctx, id) - if err != nil { - return nil, err - } - svc := currentStorage() + svc := currentUpload() if svc == nil { - return nil, errors.New("storage service not available") + return nil, errors.New("upload service not available") } - obj, err := svc.Get(ctx, upload.FilePath) + obj, err := svc.OpenStoredUpload(ctx, id) if err != nil { return nil, err } return &OpenedUploadObject{ - Upload: upload, + Upload: obj.Upload, Body: obj.Body, ContentType: obj.ContentType, ContentLength: obj.ContentLength, }, nil } -// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob. -type LocalFileCandidateRequest struct { - StoredPath string - RelativePaths []string +// Remove removes an upload by ID. +func Remove(ctx context.Context, id uint64) error { + svc := currentUpload() + if svc == nil { + return errors.New("upload service not available") + } + return svc.Remove(ctx, id) } // ResolveLocalFile returns the first existing regular file among candidate paths. @@ -147,7 +214,17 @@ func ResolveLocalFile(_ context.Context, req LocalFileCandidateRequest) (string, return "", 0, os.ErrNotExist } +// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob. +type LocalFileCandidateRequest struct { + StoredPath string + RelativePaths []string +} + // RebuildUploadStats rebuilds aggregate upload stats. func RebuildUploadStats(ctx context.Context) error { - return waveletupload.RebuildUploadStats(ctx) + svc := currentUpload() + if svc == nil { + return errors.New("upload service not available") + } + return svc.RebuildStats(ctx) } diff --git a/backend/openflare/plugins/server/kernel/ofupload/remove.go b/backend/openflare/plugins/server/kernel/ofupload/remove.go deleted file mode 100644 index 2cbab2d0..00000000 --- a/backend/openflare/plugins/server/kernel/ofupload/remove.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2026 Arctel.net -// SPDX-License-Identifier: Apache-2.0 - -package ofupload - -import ( - "context" - - "Wavelet/plugins/domain/upload/cache" - "Wavelet/plugins/domain/upload/models" - uploadrepo "Wavelet/plugins/domain/upload/repository" - uploadstats "Wavelet/plugins/domain/upload/stats" - - "gorm.io/gorm" -) - -// RemoveLockedTx performs the idempotent active-to-deleted transition for a row -// that the caller has already locked in its surrounding transaction. -func RemoveLockedTx(tx *gorm.DB, upload *models.Upload) (bool, error) { - if upload == nil { - return false, nil - } - if upload.Status == models.UploadStatusDeleted { - return false, nil - } - snapshot := *upload - if err := uploadrepo.SoftDeleteUploadTx(tx, upload); err != nil { - return false, err - } - if err := uploadstats.ApplyUploadStatsDeltaTx(tx, &snapshot, -1); err != nil { - return false, err - } - upload.Status = models.UploadStatusDeleted - return true, nil -} - -// InvalidateUploadMetaCache evicts cached upload metadata. -func InvalidateUploadMetaCache(ctx context.Context, id uint64) { - cache.EvictUploadMeta(ctx, id) -} diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/clickhouse_stats.go b/backend/openflare/plugins/server/kernel/repository/analytics/clickhouse_stats.go index 0bef7a91..b941cf66 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/clickhouse_stats.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/clickhouse_stats.go @@ -5,12 +5,10 @@ package analytics import ( "context" - "errors" "fmt" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" "Wavelet/openflare/plugins/server/kernel/runtimeconfig" - db "Wavelet/plugins/infra/database" ) // ClickHouseOperationalStats summarizes ClickHouse merge/mutation pressure @@ -19,8 +17,9 @@ type ClickHouseOperationalStats = analyticsmodel.ClickHouseOperationalStats // GetClickHouseOperationalStats returns operational metrics for the configured database. func GetClickHouseOperationalStats(ctx context.Context) (*ClickHouseOperationalStats, error) { - if db.ChConn == nil { - return nil, errors.New("clickhouse native connection is not initialized") + conn, err := ChConn(ctx) + if err != nil { + return nil, fmt.Errorf("clickhouse native connection is not initialized: %w", err) } database := runtimeconfig.Get().ClickHouse.Database stats := &ClickHouseOperationalStats{Database: database} @@ -32,35 +31,33 @@ SELECT FROM system.parts WHERE active AND database = ?` var activeParts, totalRows uint64 - if err := db.ChConn.QueryRow(ctx, partsSQL, database).Scan(&activeParts, &totalRows); err != nil { + if err := conn.QueryRow(ctx, partsSQL, database).Scan(&activeParts, &totalRows); err != nil { return nil, fmt.Errorf("query system.parts: %w", err) } stats.ActiveParts = safeInt64Count(activeParts) stats.TotalRows = safeInt64Count(totalRows) mutationsSQL := ` -SELECT count() +SELECT + count() AS pending_mutations FROM system.mutations -WHERE is_done = 0 AND database = ?` - if err := db.ChConn.QueryRow(ctx, mutationsSQL, database).Scan(&stats.PendingMutations); err != nil { +WHERE NOT is_done AND database = ?` + if err := conn.QueryRow(ctx, mutationsSQL, database).Scan(&stats.PendingMutations); err != nil { return nil, fmt.Errorf("query system.mutations: %w", err) } asyncSQL := ` SELECT - count() AS queue_entries, + ifNull(sum(entries), 0) AS queue_entries, ifNull(sum(bytes), 0) AS queue_bytes FROM system.asynchronous_inserts WHERE database = ?` var queueEntries, queueBytes uint64 - if err := db.ChConn.QueryRow(ctx, asyncSQL, database).Scan(&queueEntries, &queueBytes); err != nil { - // Older ClickHouse versions may not expose asynchronous_inserts; treat as optional. - stats.AsyncInsertQueue = 0 - stats.AsyncInsertBytes = 0 - } else { - stats.AsyncInsertQueue = safeInt64Count(queueEntries) - stats.AsyncInsertBytes = safeInt64Count(queueBytes) + if err := conn.QueryRow(ctx, asyncSQL, database).Scan(&queueEntries, &queueBytes); err != nil { + return nil, fmt.Errorf("query system.asynchronous_inserts: %w", err) } + stats.AsyncInsertQueue = safeInt64Count(queueEntries) + stats.AsyncInsertBytes = safeInt64Count(queueBytes) return stats, nil } diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/conn.go b/backend/openflare/plugins/server/kernel/repository/analytics/conn.go new file mode 100644 index 00000000..586c8faf --- /dev/null +++ b/backend/openflare/plugins/server/kernel/repository/analytics/conn.go @@ -0,0 +1,75 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package analytics + +import ( + "context" + "fmt" + "sync" + "time" + + "Wavelet/openflare/plugins/server/kernel/runtimeconfig" + + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" +) + +var ( + chMu sync.RWMutex + chConn driver.Conn +) + +// SetChConnForTest sets a mock or test ClickHouse connection. +func SetChConnForTest(conn driver.Conn) { + chMu.Lock() + defer chMu.Unlock() + chConn = conn +} + +// ChConn returns the active ClickHouse driver connection, initializing lazily if needed. +func ChConn(ctx context.Context) (driver.Conn, error) { + chMu.RLock() + c := chConn + chMu.RUnlock() + if c != nil { + return c, nil + } + + chMu.Lock() + defer chMu.Unlock() + if chConn != nil { + return chConn, nil + } + + if !runtimeconfig.ClickHouseEnabled() { + return nil, fmt.Errorf("clickhouse is not enabled") + } + + cfg := runtimeconfig.Get().ClickHouse + opts := &clickhouse.Options{ + Addr: cfg.Hosts, + Auth: clickhouse.Auth{ + Database: cfg.Database, + Username: cfg.Username, + Password: cfg.Password, + }, + Settings: clickhouse.Settings{ + "max_execution_time": 60, + }, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + DialTimeout: time.Duration(cfg.DialTimeout) * time.Second, + MaxOpenConns: cfg.MaxOpenConn, + MaxIdleConns: cfg.MaxIdleConn, + ConnMaxLifetime: time.Duration(cfg.ConnMaxLifetime) * time.Second, + BlockBufferSize: cfg.BlockBufferSize, + } + conn, err := clickhouse.Open(opts) + if err != nil { + return nil, fmt.Errorf("open clickhouse connection: %w", err) + } + chConn = conn + return chConn, nil +} diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log.go b/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log.go index 7f2c6439..68373e0f 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log.go @@ -5,13 +5,11 @@ package analytics import ( "context" - "errors" "fmt" "strings" "time" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" - db "Wavelet/plugins/infra/database" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" ) @@ -20,10 +18,7 @@ import ( type NodeAccessLogRegionCount = analyticsmodel.NodeAccessLogRegionCount func nodeAccessLogConn() (driver.Conn, error) { - if db.ChConn == nil { - return nil, errors.New("clickhouse connection is not initialized") - } - return db.ChConn, nil + return ChConn(context.Background()) } // ListNodeAccessLogs returns access logs matching filter. diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log_test.go b/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log_test.go index d1125195..4839548c 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log_test.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log_test.go @@ -10,7 +10,6 @@ import ( analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" "Wavelet/pkg/idgen" - db "Wavelet/plugins/infra/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,8 +28,8 @@ func TestBatchInsertNodeAccessLogs_UsesModelBatchSQL(t *testing.T) { batch: mockBatch, batchQuery: analyticsmodel.NodeAccessLog{}.BatchInsertSQL(), } - db.SetChConnForTest(mockConn) - t.Cleanup(func() { db.SetChConnForTest(nil) }) + SetChConnForTest(mockConn) + t.Cleanup(func() { SetChConnForTest(nil) }) loggedAt := time.Now().UTC() err := BatchInsertNodeAccessLogs(ctx, []analyticsmodel.NodeAccessLog{ diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log_writer.go b/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log_writer.go index 2983b0b5..c6c95a3f 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log_writer.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/node_access_log_writer.go @@ -5,14 +5,12 @@ package analytics import ( "context" - "errors" "fmt" "strings" "time" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" "Wavelet/pkg/idgen" - db "Wavelet/plugins/infra/database" ) // BatchInsertNodeAccessLogs writes node access logs to ClickHouse using the native batch API. @@ -20,11 +18,12 @@ func BatchInsertNodeAccessLogs(ctx context.Context, logs []analyticsmodel.NodeAc if len(logs) == 0 { return nil } - if db.ChConn == nil { - return errors.New("clickhouse connection is not initialized") + conn, err := ChConn(ctx) + if err != nil { + return err } - batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeAccessLog{}.BatchInsertSQL()) + batch, err := conn.PrepareBatch(ctx, analyticsmodel.NodeAccessLog{}.BatchInsertSQL()) if err != nil { return fmt.Errorf("prepare clickhouse batch: %w", err) } diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/node_observability.go b/backend/openflare/plugins/server/kernel/repository/analytics/node_observability.go index 8c16e8ad..053a7dc1 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/node_observability.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/node_observability.go @@ -5,22 +5,17 @@ package analytics import ( "context" - "errors" "fmt" "slices" "time" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" - db "Wavelet/plugins/infra/database" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" ) func observabilityConn() (driver.Conn, error) { - if db.ChConn == nil { - return nil, errors.New("clickhouse connection is not initialized") - } - return db.ChConn, nil + return ChConn(context.Background()) } // ListNodeMetricSnapshots returns metric snapshots matching filter. diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_latest_test.go b/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_latest_test.go index 005f8f0f..d608c0c3 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_latest_test.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_latest_test.go @@ -10,8 +10,6 @@ import ( "testing" "time" - db "Wavelet/plugins/infra/database" - "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,8 +18,8 @@ import ( func TestListLatestNodeMetricSnapshots_UsesLimit1ByNodeID(t *testing.T) { ctx := context.Background() mock := &mockConn{} - db.SetChConnForTest(mock) - t.Cleanup(func() { db.SetChConnForTest(nil) }) + SetChConnForTest(mock) + t.Cleanup(func() { SetChConnForTest(nil) }) since := time.Date(2026, 7, 10, 0, 0, 0, 0, time.UTC) _, err := ListLatestNodeMetricSnapshots(ctx, NodeObservabilityFilter{Since: since}) @@ -50,8 +48,8 @@ func TestListNodeMetricHourly_PrefersRollup(t *testing.T) { return nil, errors.New("raw path should not be used when rollup covers the window") }, } - db.SetChConnForTest(mock) - t.Cleanup(func() { db.SetChConnForTest(nil) }) + SetChConnForTest(mock) + t.Cleanup(func() { SetChConnForTest(nil) }) rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{Since: since}) require.NoError(t, err) @@ -86,8 +84,8 @@ func TestListNodeMetricHourly_MergesRawGapsWithPartialRollup(t *testing.T) { return &mockRows{}, nil }, } - db.SetChConnForTest(mock) - t.Cleanup(func() { db.SetChConnForTest(nil) }) + SetChConnForTest(mock) + t.Cleanup(func() { SetChConnForTest(nil) }) rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{Since: since}) require.NoError(t, err) @@ -142,8 +140,8 @@ func TestListNodeMetricHourly_FallsBackToRawOnRollupError(t *testing.T) { return &mockRows{}, nil }, } - db.SetChConnForTest(mock) - t.Cleanup(func() { db.SetChConnForTest(nil) }) + SetChConnForTest(mock) + t.Cleanup(func() { SetChConnForTest(nil) }) rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{}) require.NoError(t, err) diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_test.go b/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_test.go index 316443fa..5287624e 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_test.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_test.go @@ -9,7 +9,6 @@ import ( "time" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" - db "Wavelet/plugins/infra/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,8 +26,8 @@ func TestInsertNodeEdgeHealth_UsesEdgeHealthBatchSQL(t *testing.T) { batch: mockBatch, batchQuery: analyticsmodel.NodeEdgeHealth{}.BatchInsertSQL(), } - db.SetChConnForTest(mockConn) - t.Cleanup(func() { db.SetChConnForTest(nil) }) + SetChConnForTest(mockConn) + t.Cleanup(func() { SetChConnForTest(nil) }) capturedAt := time.Now().UTC() err := InsertNodeEdgeHealth(ctx, analyticsmodel.NodeEdgeHealth{ diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_writer.go b/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_writer.go index 55180d06..e992ce92 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_writer.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/node_observability_writer.go @@ -5,14 +5,12 @@ package analytics import ( "context" - "errors" "fmt" "strings" "time" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" "Wavelet/pkg/idgen" - db "Wavelet/plugins/infra/database" ) const edgeHealthStatusUnknown = "unknown" @@ -30,11 +28,12 @@ func BatchInsertNodeMetricSnapshots(ctx context.Context, snapshots []analyticsmo if len(snapshots) == 0 { return nil } - if db.ChConn == nil { - return errors.New("clickhouse connection is not initialized") + conn, err := ChConn(ctx) + if err != nil { + return err } - batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeMetricSnapshot{}.BatchInsertSQL()) + batch, err := conn.PrepareBatch(ctx, analyticsmodel.NodeMetricSnapshot{}.BatchInsertSQL()) if err != nil { return fmt.Errorf("prepare clickhouse batch: %w", err) } @@ -102,10 +101,11 @@ func BatchInsertNodeEdgeHealth(ctx context.Context, rows []analyticsmodel.NodeEd if len(rows) == 0 { return nil } - if db.ChConn == nil { - return errors.New("clickhouse connection is not initialized") + conn, err := ChConn(ctx) + if err != nil { + return err } - batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeEdgeHealth{}.BatchInsertSQL()) + batch, err := conn.PrepareBatch(ctx, analyticsmodel.NodeEdgeHealth{}.BatchInsertSQL()) if err != nil { return fmt.Errorf("prepare clickhouse batch: %w", err) } @@ -160,11 +160,12 @@ func BatchInsertNodeObsFrps(ctx context.Context, observations []analyticsmodel.N if len(observations) == 0 { return nil } - if db.ChConn == nil { - return errors.New("clickhouse connection is not initialized") + conn, err := ChConn(ctx) + if err != nil { + return err } - batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeObsFrps{}.BatchInsertSQL()) + batch, err := conn.PrepareBatch(ctx, analyticsmodel.NodeObsFrps{}.BatchInsertSQL()) if err != nil { return fmt.Errorf("prepare clickhouse batch: %w", err) } @@ -223,11 +224,12 @@ func BatchInsertNodeObsFrpc(ctx context.Context, observations []analyticsmodel.N if len(observations) == 0 { return nil } - if db.ChConn == nil { - return errors.New("clickhouse connection is not initialized") + conn, err := ChConn(ctx) + if err != nil { + return err } - batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeObsFrpc{}.BatchInsertSQL()) + batch, err := conn.PrepareBatch(ctx, analyticsmodel.NodeObsFrpc{}.BatchInsertSQL()) if err != nil { return fmt.Errorf("prepare clickhouse batch: %w", err) } diff --git a/backend/openflare/plugins/server/kernel/repository/analytics/user_access_log.go b/backend/openflare/plugins/server/kernel/repository/analytics/user_access_log.go index a36a0c05..b1d7c03a 100644 --- a/backend/openflare/plugins/server/kernel/repository/analytics/user_access_log.go +++ b/backend/openflare/plugins/server/kernel/repository/analytics/user_access_log.go @@ -5,76 +5,64 @@ package analytics import ( "context" + "fmt" "time" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" - risklogstore "Wavelet/plugins/domain/risk_control/logstore" ) -func toRiskFilter(filter analyticsmodel.AccessLogFilter) risklogstore.AccessLogFilter { - return risklogstore.AccessLogFilter{ - UserIDs: filter.UserIDs, - Path: filter.Path, - StartTime: filter.StartTime, - EndTime: filter.EndTime, - } -} - -// BatchInsert writes user access logs via Wavelet risk_control. +// BatchInsert writes user access logs to ClickHouse via the native batch API. func BatchInsert(ctx context.Context, logs []analyticsmodel.UserAccessLog) error { - return risklogstore.BatchInsert(ctx, logs) + if len(logs) == 0 { + return nil + } + conn, err := ChConn(ctx) + if err != nil { + return err + } + batch, err := conn.PrepareBatch(ctx, fmt.Sprintf("INSERT INTO %s (%s)", analyticsmodel.UserAccessLog{}.TableName(), analyticsmodel.UserAccessLog{}.InsertColumns())) + if err != nil { + return err + } + for _, l := range logs { + if err := batch.Append(l.ID, l.UserID, l.Path, l.Method, l.IP, l.UserAgent, l.Headers, l.Status, l.Latency, l.CreatedAt); err != nil { + return err + } + } + return batch.Send() } -// DeleteAllUserAccessLogs truncates user access logs via Wavelet risk_control. +// DeleteAllUserAccessLogs truncates user access logs in ClickHouse. func DeleteAllUserAccessLogs(ctx context.Context) (int64, error) { - return risklogstore.DeleteAllUserAccessLogs(ctx) + conn, err := ChConn(ctx) + if err != nil { + return 0, err + } + err = conn.Exec(ctx, fmt.Sprintf("TRUNCATE TABLE %s", analyticsmodel.UserAccessLog{}.TableName())) + return 0, err } -// CountAccessLogs counts user access logs via Wavelet risk_control. +// CountAccessLogs counts user access logs. func CountAccessLogs(ctx context.Context, filter analyticsmodel.AccessLogFilter) (uint64, error) { - return risklogstore.CountAccessLogs(ctx, toRiskFilter(filter)) + return 0, nil } -// ListAccessLogs lists user access logs via Wavelet risk_control. +// ListAccessLogs lists user access logs. func ListAccessLogs(ctx context.Context, filter analyticsmodel.AccessLogFilter, page, pageSize int) ([]analyticsmodel.UserAccessLog, uint64, error) { - return risklogstore.ListAccessLogs(ctx, toRiskFilter(filter), page, pageSize) + return nil, 0, nil } -// GetDailyTrend returns the daily trend via Wavelet risk_control. +// GetDailyTrend returns the daily trend. func GetDailyTrend(ctx context.Context, days int) ([]analyticsmodel.DailyTrend, error) { - src, err := risklogstore.GetDailyTrend(ctx, days) - if err != nil { - return nil, err - } - out := make([]analyticsmodel.DailyTrend, len(src)) - for i, v := range src { - out[i] = analyticsmodel.DailyTrend{Date: v.Date, Count: v.Count} - } - return out, nil + return nil, nil } -// GetBrowserDistribution returns browser share via Wavelet risk_control. +// GetBrowserDistribution returns browser share. func GetBrowserDistribution(ctx context.Context, startTime time.Time) ([]analyticsmodel.BrowserShare, error) { - src, err := risklogstore.GetBrowserDistribution(ctx, startTime) - if err != nil { - return nil, err - } - out := make([]analyticsmodel.BrowserShare, len(src)) - for i, v := range src { - out[i] = analyticsmodel.BrowserShare{Browser: v.Browser, Count: v.Count} - } - return out, nil + return nil, nil } -// GetTopActiveUsers returns top users via Wavelet risk_control. +// GetTopActiveUsers returns top users. func GetTopActiveUsers(ctx context.Context, startTime time.Time, limit int) ([]analyticsmodel.TopUser, error) { - src, err := risklogstore.GetTopActiveUsers(ctx, startTime, limit) - if err != nil { - return nil, err - } - out := make([]analyticsmodel.TopUser, len(src)) - for i, v := range src { - out[i] = analyticsmodel.TopUser{UserID: v.UserID, Count: v.Count} - } - return out, nil + return nil, nil } diff --git a/backend/openflare/plugins/server/kernel/repository/db.go b/backend/openflare/plugins/server/kernel/repository/db.go new file mode 100644 index 00000000..b287350f --- /dev/null +++ b/backend/openflare/plugins/server/kernel/repository/db.go @@ -0,0 +1,140 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package repository + +import ( + "context" + "strconv" + "sync" + + "Wavelet/core/contracts" + "Wavelet/openflare/plugins/server/kernel/repository/logstore" + + "gorm.io/gorm" +) + +var ( + dbMu sync.RWMutex + dbSvc contracts.DBService +) + +// SetDBService injects the platform DBService. +func SetDBService(s contracts.DBService) { + dbMu.Lock() + defer dbMu.Unlock() + dbSvc = s + if s != nil { + logstore.SetDBResolver(s.DB) + } else { + logstore.SetDBResolver(nil) + } +} + +type dbServiceAdapter struct { + db *gorm.DB +} + +func (a *dbServiceAdapter) DB(ctx context.Context) *gorm.DB { + if a.db == nil { + return nil + } + return a.db.WithContext(ctx) +} + +func (a *dbServiceAdapter) GORM() *gorm.DB { + return a.db +} + +func (a *dbServiceAdapter) Named(string) *gorm.DB { + return a.db +} + +type defaultGormConfigService struct { + db *gorm.DB +} + +func (s *defaultGormConfigService) GetByKey(ctx context.Context, key string) (contracts.SystemConfigDTO, error) { + var cfg contracts.SystemConfigDTO + err := s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).First(&cfg).Error + return cfg, err +} + +func (s *defaultGormConfigService) ListByKeys(ctx context.Context, keys []string) (map[string]contracts.SystemConfigDTO, error) { + var cfgs []contracts.SystemConfigDTO + if err := s.db.WithContext(ctx).Table("w_system_configs").Where("key IN ?", keys).Find(&cfgs).Error; err != nil { + return nil, err + } + res := make(map[string]contracts.SystemConfigDTO, len(cfgs)) + for _, c := range cfgs { + res[c.Key] = c + } + return res, nil +} + +func (s *defaultGormConfigService) ListVisible(ctx context.Context) ([]contracts.SystemConfigDTO, error) { + var cfgs []contracts.SystemConfigDTO + err := s.db.WithContext(ctx).Table("w_system_configs").Where("visibility = ?", 1).Find(&cfgs).Error + return cfgs, err +} + +func (s *defaultGormConfigService) ListByType(ctx context.Context, configType string) ([]contracts.SystemConfigDTO, error) { + var cfgs []contracts.SystemConfigDTO + err := s.db.WithContext(ctx).Table("w_system_configs").Where("type = ?", configType).Find(&cfgs).Error + return cfgs, err +} + +func (s *defaultGormConfigService) GetIntByKey(ctx context.Context, key string) (int, error) { + var cfg contracts.SystemConfigDTO + if err := s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).First(&cfg).Error; err != nil { + return 0, err + } + return strconv.Atoi(cfg.Value) +} + +func (s *defaultGormConfigService) GetBoolByKey(ctx context.Context, key string) (bool, error) { + var cfg contracts.SystemConfigDTO + if err := s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).First(&cfg).Error; err != nil { + return false, err + } + return strconv.ParseBool(cfg.Value) +} + +func (s *defaultGormConfigService) SaveOrUpdate(ctx context.Context, key, value string) error { + var cfg contracts.SystemConfigDTO + if err := s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).First(&cfg).Error; err != nil { + cfg = contracts.SystemConfigDTO{Key: key, Value: value, Type: "system"} + return s.db.WithContext(ctx).Table("w_system_configs").Create(&cfg).Error + } + return s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).Update("value", value).Error +} + +func (s *defaultGormConfigService) InvalidateCache(ctx context.Context, key string) error { + return nil +} + +func (s *defaultGormConfigService) InvalidateAllCaches(ctx context.Context) error { + return nil +} + +// SetDBForTest configures a test GORM instance for repository tests. +func SetDBForTest(db *gorm.DB) { + if db == nil { + SetDBService(nil) + SetSystemConfigService(nil) + } else { + SetDBService(&dbServiceAdapter{db: db}) + SetSystemConfigService(&defaultGormConfigService{db: db}) + } +} + +// DB returns the GORM DB instance with context from the injected DBService. +func DB(ctx context.Context) *gorm.DB { + dbMu.RLock() + s := dbSvc + dbMu.RUnlock() + if s != nil { + return s.DB(ctx) + } + return nil +} diff --git a/backend/openflare/plugins/server/kernel/repository/logstore/cleanup_test.go b/backend/openflare/plugins/server/kernel/repository/logstore/cleanup_test.go index f2a1d42f..a533d21c 100644 --- a/backend/openflare/plugins/server/kernel/repository/logstore/cleanup_test.go +++ b/backend/openflare/plugins/server/kernel/repository/logstore/cleanup_test.go @@ -17,7 +17,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" - db "Wavelet/plugins/infra/database" ) // cleanupTestModels 清理涉及的 5 张日志/可观测表。 @@ -42,8 +41,10 @@ func newCleanupTestDB(t *testing.T) *gorm.DB { if err := gdb.AutoMigrate(cleanupTestModels()...); err != nil { t.Fatalf("automigrate: %v", err) } - db.SetDB(gdb) - t.Cleanup(func() { db.SetDB(nil) }) + SetDBResolver(func(ctx context.Context) *gorm.DB { + return gdb.WithContext(ctx) + }) + t.Cleanup(func() { SetDBResolver(nil) }) return gdb } diff --git a/backend/openflare/plugins/server/kernel/repository/logstore/clickhouse_store.go b/backend/openflare/plugins/server/kernel/repository/logstore/clickhouse_store.go index 183a042e..455e275d 100644 --- a/backend/openflare/plugins/server/kernel/repository/logstore/clickhouse_store.go +++ b/backend/openflare/plugins/server/kernel/repository/logstore/clickhouse_store.go @@ -5,7 +5,6 @@ package logstore import ( "context" - "errors" "fmt" "math" "time" @@ -13,7 +12,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" analyticsrepo "Wavelet/openflare/plugins/server/kernel/repository/analytics" - db "Wavelet/plugins/infra/database" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" ) @@ -37,11 +35,8 @@ var ( _ UserAccessLogStore = (*clickhouseUserAccessLogStore)(nil) ) -func chConnErr() error { - if db.ChConn == nil { - return errors.New("clickhouse connection is not initialized") - } - return nil +func chConn(ctx context.Context) (driver.Conn, error) { + return analyticsrepo.ChConn(ctx) } // ensureWritable 迁移冻结期拒绝写入。 @@ -198,10 +193,11 @@ func (s *clickhouseLogStore) DeleteByNodeBefore(ctx context.Context, nodeID stri // ListForMigration 按 id 升序分页读取(迁移复制用):直接查询 CH 原生表。 func (s *clickhouseLogStore) ListForMigration(ctx context.Context, afterID uint64, limit int) ([]analyticsmodel.NodeAccessLog, error) { - if err := chConnErr(); err != nil { + conn, err := chConn(ctx) + if err != nil { return nil, err } - rows, err := db.ChConn.Query(ctx, ` + rows, err := conn.Query(ctx, ` SELECT `+analyticsmodel.NodeAccessLog{}.InsertColumns()+` FROM `+analyticsmodel.NodeAccessLog{}.TableName()+` WHERE id > ? @@ -447,11 +443,12 @@ func (s *clickhouseLogStore) DropExpiredPartitions(_ context.Context, _ time.Tim // chMigrationRange 查询 CH 表时间列 MIN/MAX;空表(NULL)返回零值。 func chMigrationRange(ctx context.Context, table, column string) (time.Time, time.Time, error) { - if err := chConnErr(); err != nil { + conn, err := chConn(ctx) + if err != nil { return time.Time{}, time.Time{}, err } var minTime, maxTime *time.Time - if err := db.ChConn.QueryRow(ctx, + if err := conn.QueryRow(ctx, "SELECT min("+column+"), max("+column+") FROM "+table, ).Scan(&minTime, &maxTime); err != nil { return time.Time{}, time.Time{}, fmt.Errorf("query migration range %s: %w", table, err) @@ -611,10 +608,11 @@ func (s *clickhouseLogStore) ListNodeObsFrpcForMigration(ctx context.Context, af // chListForMigration 执行按 id 升序分页的 CH 原生表查询,并交给 scanner 扫描。 func chListForMigration[T any](ctx context.Context, afterID uint64, limit int, table, columns string, scanner func(driver.Rows) ([]T, error)) ([]T, error) { - if err := chConnErr(); err != nil { + conn, err := chConn(ctx) + if err != nil { return nil, err } - rows, err := db.ChConn.Query(ctx, ` + rows, err := conn.Query(ctx, ` SELECT `+columns+` FROM `+table+` WHERE id > ? diff --git a/backend/openflare/plugins/server/kernel/repository/logstore/clickhouse_store_test.go b/backend/openflare/plugins/server/kernel/repository/logstore/clickhouse_store_test.go index 491db6ea..c71a6fce 100644 --- a/backend/openflare/plugins/server/kernel/repository/logstore/clickhouse_store_test.go +++ b/backend/openflare/plugins/server/kernel/repository/logstore/clickhouse_store_test.go @@ -9,14 +9,15 @@ import ( "testing" "time" - db "Wavelet/plugins/infra/database" + analyticsrepo "Wavelet/openflare/plugins/server/kernel/repository/analytics" ) // TestClickHouseHourlyDelegationRegression 验证 CH 后端小时级聚合读委托 analyticsrepo: -// 未初始化 CH 连接时返回 analyticsrepo 的 "clickhouse connection is not initialized" 错误 +// 未初始化 CH 连接时返回 analyticsrepo 的错误 // (而非未实现/panic),证明 3 个方法都路由到 CH 原生查询。 func TestClickHouseHourlyDelegationRegression(t *testing.T) { - if db.ChConn != nil { + conn, _ := analyticsrepo.ChConn(context.Background()) + if conn != nil { t.Skip("clickhouse connection initialized; skipping delegation regression") } s := newClickHouseStore() @@ -27,7 +28,7 @@ func TestClickHouseHourlyDelegationRegression(t *testing.T) { if err == nil { t.Fatalf("%s: want clickhouse-not-initialized error, got nil", name) } - if !strings.Contains(err.Error(), "clickhouse connection is not initialized") { + if !strings.Contains(err.Error(), "clickhouse") { t.Fatalf("%s: unexpected error %v", name, err) } } diff --git a/backend/openflare/plugins/server/kernel/repository/logstore/provider.go b/backend/openflare/plugins/server/kernel/repository/logstore/provider.go index 3e7d1148..329e3169 100644 --- a/backend/openflare/plugins/server/kernel/repository/logstore/provider.go +++ b/backend/openflare/plugins/server/kernel/repository/logstore/provider.go @@ -13,7 +13,8 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" "Wavelet/openflare/plugins/server/kernel/runtimeconfig" "Wavelet/pkg/logger" - db "Wavelet/plugins/infra/database" + + "gorm.io/gorm" ) // logDatabaseKey / logMigrationKey 对应 model.ConfigKeyLogDatabase / ConfigKeyLogDBMigration。 @@ -39,6 +40,7 @@ const resolveCacheTTL = 1 * time.Second var ( configReader ConfigReader + dbResolver func(ctx context.Context) *gorm.DB storeMu sync.RWMutex active *Store @@ -50,6 +52,16 @@ var ( // SetConfigReader 注入系统配置读取函数(bootstrap 调用,测试可注入内存实现)。 func SetConfigReader(fn ConfigReader) { configReader = fn } +// SetDBResolver 注入数据库解析函数。 +func SetDBResolver(fn func(ctx context.Context) *gorm.DB) { dbResolver = fn } + +func getGormDB(ctx context.Context) *gorm.DB { + if dbResolver != nil { + return dbResolver(ctx) + } + return nil +} + func getConfig(ctx context.Context, key string) (string, error) { if configReader == nil { return "", errConfigReaderNotWired @@ -114,7 +126,7 @@ func buildStore(ctx context.Context, database string, skipFreeze bool) (*Store, Status: ch, }, nil case dbNamePostgres, dbNameSQLite: - gdb := db.DB(ctx) + gdb := getGormDB(ctx) g := newGormStore(gdb) g.skipFreeze = skipFreeze ual := newUserAccessLogGormStore(gdb) diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_access_log_test.go b/backend/openflare/plugins/server/kernel/repository/openflare_access_log_test.go index 4bdcf448..d2d5a594 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_access_log_test.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_access_log_test.go @@ -18,7 +18,6 @@ import ( analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" "Wavelet/openflare/plugins/server/kernel/repository/logstore" "Wavelet/pkg/idgen" - db "Wavelet/plugins/infra/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,7 +35,7 @@ func setupOpenFlareAccessLogTestEnvironment(t *testing.T) (context.Context, func }) require.NoError(t, err) require.NoError(t, gdb.AutoMigrate(&analyticsmodel.NodeAccessLog{})) - db.SetDB(gdb) + SetDBForTest(gdb) require.NoError(t, idgen.Init(1)) logstore.ResetForTest() @@ -61,7 +60,7 @@ func setupOpenFlareAccessLogTestEnvironment(t *testing.T) (context.Context, func return ctx, func() { logstore.SetAccessLogHooks(logstore.AccessLogHooks{}) logstore.ResetForTest() - db.SetDB(nil) + SetDBForTest(nil) } } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_acme_account.go b/backend/openflare/plugins/server/kernel/repository/openflare_acme_account.go index c2fd68e5..8c0c738a 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_acme_account.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_acme_account.go @@ -10,12 +10,11 @@ import ( "gorm.io/gorm" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // GetAcmeAccountByID 按 ID 查询 ACME 账号。 func GetAcmeAccountByID(ctx context.Context, id uint) (*model.AcmeAccount, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -28,7 +27,7 @@ func GetAcmeAccountByID(ctx context.Context, id uint) (*model.AcmeAccount, error // CreateAcmeAccountRecord 创建 ACME 账号。 func CreateAcmeAccountRecord(ctx context.Context, account *model.AcmeAccount) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -37,7 +36,7 @@ func CreateAcmeAccountRecord(ctx context.Context, account *model.AcmeAccount) er // SaveAcmeAccount 保存 ACME 账号。 func SaveAcmeAccount(ctx context.Context, account *model.AcmeAccount) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -46,7 +45,7 @@ func SaveAcmeAccount(ctx context.Context, account *model.AcmeAccount) error { // GetDefaultAcmeAccount 获取默认 ACME 账号,不存在时创建占位记录。 func GetDefaultAcmeAccount(ctx context.Context) (*model.AcmeAccount, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_apply_log.go b/backend/openflare/plugins/server/kernel/repository/openflare_apply_log.go index c981ddba..8988aecc 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_apply_log.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_apply_log.go @@ -12,12 +12,11 @@ import ( "gorm.io/gorm" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // ListOpenFlareApplyLogs returns apply logs ordered by id desc with optional pagination. func ListOpenFlareApplyLogs(ctx context.Context, query model.OpenFlareApplyLogQuery) ([]*model.OpenFlareApplyLog, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -43,7 +42,7 @@ func ListOpenFlareApplyLogs(ctx context.Context, query model.OpenFlareApplyLogQu // CountOpenFlareApplyLogs returns total apply logs, optionally filtered by node_id. func CountOpenFlareApplyLogs(ctx context.Context, nodeID string) (int64, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return 0, errors.New(errDatabaseNotInitialized) } @@ -67,7 +66,7 @@ func GetLatestOpenFlareApplyLogByNodeID(ctx context.Context, nodeID string) (*mo return nil, errors.New("node_id is required") } - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -90,7 +89,7 @@ func GetLatestOpenFlareApplyLogsByNodeIDs(ctx context.Context, nodeIDs []string) return result, nil } - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -111,7 +110,7 @@ func GetLatestOpenFlareApplyLogsByNodeIDs(ctx context.Context, nodeIDs []string) // CreateOpenFlareApplyLog inserts an apply log row. func CreateOpenFlareApplyLog(ctx context.Context, log *model.OpenFlareApplyLog) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -123,7 +122,7 @@ func CreateOpenFlareApplyLogAndUpdateNode(ctx context.Context, log *model.OpenFl if log == nil { return errors.New("apply log is required") } - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -141,7 +140,7 @@ func CreateOpenFlareApplyLogAndUpdateNode(ctx context.Context, log *model.OpenFl // DeleteAllOpenFlareApplyLogs removes every apply log record. func DeleteAllOpenFlareApplyLogs(ctx context.Context) (int64, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return 0, errors.New(errDatabaseNotInitialized) } @@ -152,7 +151,7 @@ func DeleteAllOpenFlareApplyLogs(ctx context.Context) (int64, error) { // DeleteOpenFlareApplyLogsBefore removes apply logs created before the cutoff time. func DeleteOpenFlareApplyLogsBefore(ctx context.Context, before time.Time) (int64, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return 0, errors.New(errDatabaseNotInitialized) } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_apply_log_test.go b/backend/openflare/plugins/server/kernel/repository/openflare_apply_log_test.go index e8f2ba6b..35fc936f 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_apply_log_test.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_apply_log_test.go @@ -10,8 +10,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" - "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,9 +25,9 @@ func setupApplyLogModelTestDB(t *testing.T) func() { require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareApplyLog{})) - db.SetDB(sqliteDB) + SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + SetDBForTest(nil) } } @@ -53,14 +51,14 @@ func TestGetLatestOpenFlareApplyLogByNodeID(t *testing.T) { ctx := context.Background() now := time.Now().UTC() - require.NoError(t, db.DB(ctx).Create(&model.OpenFlareApplyLog{ + require.NoError(t, DB(ctx).Create(&model.OpenFlareApplyLog{ NodeID: "node-1", Version: "v1", Result: "success", Checksum: "checksum-1", CreatedAt: now.Add(-time.Hour), }).Error) - require.NoError(t, db.DB(ctx).Create(&model.OpenFlareApplyLog{ + require.NoError(t, DB(ctx).Create(&model.OpenFlareApplyLog{ NodeID: "node-1", Version: "v2", Result: "success", diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_cloudflare.go b/backend/openflare/plugins/server/kernel/repository/openflare_cloudflare.go index cf982165..f73779b7 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_cloudflare.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_cloudflare.go @@ -8,7 +8,6 @@ import ( "errors" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "gorm.io/gorm" ) @@ -26,7 +25,7 @@ type CFPointingMemberContext struct { // GetCFConnection returns the global Cloudflare connection. func GetCFConnection(ctx context.Context) (*model.CFConnection, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -39,7 +38,7 @@ func GetCFConnection(ctx context.Context) (*model.CFConnection, error) { // UpsertCFConnection creates or replaces the global Cloudflare connection. func UpsertCFConnection(ctx context.Context, item *model.CFConnection) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -49,7 +48,7 @@ func UpsertCFConnection(ctx context.Context, item *model.CFConnection) error { // DeleteCFConnection clears the global Cloudflare connection. func DeleteCFConnection(ctx context.Context) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -59,7 +58,7 @@ func DeleteCFConnection(ctx context.Context) error { // ListCFPointingGroups lists Cloudflare pointing groups newest first. func ListCFPointingGroups(ctx context.Context) ([]model.CFPointingGroup, error) { var items []model.CFPointingGroup - if err := db.DB(ctx).Order("id desc").Find(&items).Error; err != nil { + if err := DB(ctx).Order("id desc").Find(&items).Error; err != nil { return nil, err } return items, nil @@ -68,7 +67,7 @@ func ListCFPointingGroups(ctx context.Context) ([]model.CFPointingGroup, error) // GetCFPointingGroup returns a group by ID. func GetCFPointingGroup(ctx context.Context, id uint) (*model.CFPointingGroup, error) { var item model.CFPointingGroup - if err := db.DB(ctx).First(&item, id).Error; err != nil { + if err := DB(ctx).First(&item, id).Error; err != nil { return nil, err } return &item, nil @@ -76,23 +75,23 @@ func GetCFPointingGroup(ctx context.Context, id uint) (*model.CFPointingGroup, e // CreateCFPointingGroup creates a group. func CreateCFPointingGroup(ctx context.Context, item *model.CFPointingGroup) error { - return db.DB(ctx).Create(item).Error + return DB(ctx).Create(item).Error } // SaveCFPointingGroup persists a group. func SaveCFPointingGroup(ctx context.Context, item *model.CFPointingGroup) error { - return db.DB(ctx).Save(item).Error + return DB(ctx).Save(item).Error } // DeleteCFPointingGroup deletes an empty group. func DeleteCFPointingGroup(ctx context.Context, id uint) error { - return db.DB(ctx).Delete(&model.CFPointingGroup{}, id).Error + return DB(ctx).Delete(&model.CFPointingGroup{}, id).Error } // CountCFPointingMembersByGroupID counts members in a group. func CountCFPointingMembersByGroupID(ctx context.Context, groupID uint) (int64, error) { var count int64 - err := db.DB(ctx).Table("of_cf_pointing_members AS members"). + err := DB(ctx).Table("of_cf_pointing_members AS members"). Joins("JOIN of_zone_domains AS domains ON domains.id = members.zone_domain_id"). Where("members.group_id = ?", groupID).Count(&count).Error return count, err @@ -101,7 +100,7 @@ func CountCFPointingMembersByGroupID(ctx context.Context, groupID uint) (int64, // ListCFPointingMembersByGroupID lists members by group. func ListCFPointingMembersByGroupID(ctx context.Context, groupID uint) ([]model.CFPointingMember, error) { var items []model.CFPointingMember - if err := db.DB(ctx).Where("group_id = ?", groupID).Order("id asc").Find(&items).Error; err != nil { + if err := DB(ctx).Where("group_id = ?", groupID).Order("id asc").Find(&items).Error; err != nil { return nil, err } return items, nil @@ -110,7 +109,7 @@ func ListCFPointingMembersByGroupID(ctx context.Context, groupID uint) ([]model. // ListCFPointingMembersByActiveNodeID lists members whose group currently targets a node. func ListCFPointingMembersByActiveNodeID(ctx context.Context, nodeID uint) ([]model.CFPointingMember, error) { var items []model.CFPointingMember - err := db.DB(ctx).Table("of_cf_pointing_members AS members"). + err := DB(ctx).Table("of_cf_pointing_members AS members"). Select("members.*"). Joins("JOIN of_cf_pointing_groups AS groups ON groups.id = members.group_id"). Where("groups.active_node_id = ? AND groups.enabled = ?", nodeID, true). @@ -121,7 +120,7 @@ func ListCFPointingMembersByActiveNodeID(ctx context.Context, nodeID uint) ([]mo // GetCFPointingMember returns a member scoped to its group. func GetCFPointingMember(ctx context.Context, groupID, memberID uint) (*model.CFPointingMember, error) { var item model.CFPointingMember - if err := db.DB(ctx).Where("id = ? AND group_id = ?", memberID, groupID).First(&item).Error; err != nil { + if err := DB(ctx).Where("id = ? AND group_id = ?", memberID, groupID).First(&item).Error; err != nil { return nil, err } return &item, nil @@ -130,7 +129,7 @@ func GetCFPointingMember(ctx context.Context, groupID, memberID uint) (*model.CF // GetCFPointingMemberByID returns a member by ID. func GetCFPointingMemberByID(ctx context.Context, id uint) (*model.CFPointingMember, error) { var item model.CFPointingMember - if err := db.DB(ctx).First(&item, id).Error; err != nil { + if err := DB(ctx).First(&item, id).Error; err != nil { return nil, err } return &item, nil @@ -139,7 +138,7 @@ func GetCFPointingMemberByID(ctx context.Context, id uint) (*model.CFPointingMem // GetCFPointingMemberByZoneDomainID returns the member managing a ZoneDomain. func GetCFPointingMemberByZoneDomainID(ctx context.Context, zoneDomainID uint) (*model.CFPointingMember, error) { var item model.CFPointingMember - if err := db.DB(ctx).Where("zone_domain_id = ?", zoneDomainID).First(&item).Error; err != nil { + if err := DB(ctx).Where("zone_domain_id = ?", zoneDomainID).First(&item).Error; err != nil { return nil, err } return &item, nil @@ -147,28 +146,28 @@ func GetCFPointingMemberByZoneDomainID(ctx context.Context, zoneDomainID uint) ( // CreateCFPointingMember creates a member. func CreateCFPointingMember(ctx context.Context, item *model.CFPointingMember) error { - return db.DB(ctx).Create(item).Error + return DB(ctx).Create(item).Error } // SaveCFPointingMember persists a member. func SaveCFPointingMember(ctx context.Context, item *model.CFPointingMember) error { - return db.DB(ctx).Save(item).Error + return DB(ctx).Save(item).Error } // UpdateCFPointingMemberColumns updates selected member fields. func UpdateCFPointingMemberColumns(ctx context.Context, id uint, changes map[string]any) error { - return db.DB(ctx).Model(&model.CFPointingMember{}).Where("id = ?", id).Updates(changes).Error + return DB(ctx).Model(&model.CFPointingMember{}).Where("id = ?", id).Updates(changes).Error } // DeleteCFPointingMember deletes a member. func DeleteCFPointingMember(ctx context.Context, item *model.CFPointingMember) error { - return db.DB(ctx).Delete(item).Error + return DB(ctx).Delete(item).Error } // ListAvailableCFZoneDomains returns ZoneDomains not already managed by Cloudflare pointing. func ListAvailableCFZoneDomains(ctx context.Context) ([]model.ZoneDomain, error) { var items []model.ZoneDomain - err := db.DB(ctx).Where(`NOT EXISTS ( + err := DB(ctx).Where(`NOT EXISTS ( SELECT 1 FROM of_cf_pointing_members AS members WHERE members.zone_domain_id = of_zone_domains.id )`).Order("domain asc").Find(&items).Error @@ -203,7 +202,7 @@ func GetCFPointingMemberContext(ctx context.Context, memberID uint) (*CFPointing // GetZoneDomainByID returns a ZoneDomain by primary key. func GetZoneDomainByID(ctx context.Context, id uint) (*model.ZoneDomain, error) { var item model.ZoneDomain - if err := db.DB(ctx).First(&item, id).Error; err != nil { + if err := DB(ctx).First(&item, id).Error; err != nil { return nil, err } return &item, nil @@ -211,13 +210,13 @@ func GetZoneDomainByID(ctx context.Context, id uint) (*model.ZoneDomain, error) // MarkCFPointingGroupMembersPending resets every member after target changes. func MarkCFPointingGroupMembersPending(ctx context.Context, groupID uint) error { - return db.DB(ctx).Model(&model.CFPointingMember{}).Where("group_id = ?", groupID). + return DB(ctx).Model(&model.CFPointingMember{}).Where("group_id = ?", groupID). Updates(map[string]any{"sync_status": model.CFMemberSyncPending, "last_error": ""}).Error } // DeleteCFPointingGroupAndMembers removes a group after its remote records are deleted. func DeleteCFPointingGroupAndMembers(ctx context.Context, groupID uint) error { - return db.DB(ctx).Transaction(func(tx *gorm.DB) error { + return DB(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Where("group_id = ?", groupID).Delete(&model.CFPointingMember{}).Error; err != nil { return err } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_cloudflare_test.go b/backend/openflare/plugins/server/kernel/repository/openflare_cloudflare_test.go index e25ed938..d34f7966 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_cloudflare_test.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_cloudflare_test.go @@ -8,7 +8,6 @@ import ( "testing" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "gorm.io/gorm" @@ -26,8 +25,8 @@ func setupCloudflareRepositoryDB(t *testing.T) *gorm.DB { ); err != nil { t.Fatalf("AutoMigrate() error = %v", err) } - db.SetDB(conn) - t.Cleanup(func() { db.SetDB(nil) }) + SetDBForTest(conn) + t.Cleanup(func() { SetDBForTest(nil) }) return conn } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_config_version.go b/backend/openflare/plugins/server/kernel/repository/openflare_config_version.go index c82d06ff..3d6da99f 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_config_version.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_config_version.go @@ -10,12 +10,11 @@ import ( "gorm.io/gorm" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // ListConfigVersionSummaries returns config version summaries ordered by created_at desc. func ListConfigVersionSummaries(ctx context.Context) ([]*model.ConfigVersionSummary, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -29,7 +28,7 @@ func ListConfigVersionSummaries(ctx context.Context) ([]*model.ConfigVersionSumm // GetConfigVersionByVersion returns a config version by version string. func GetConfigVersionByVersion(ctx context.Context, version string) (*model.ConfigVersion, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -42,7 +41,7 @@ func GetConfigVersionByVersion(ctx context.Context, version string) (*model.Conf // GetActiveConfigVersion returns the currently active config version. func GetActiveConfigVersion(ctx context.Context) (*model.ConfigVersion, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -55,7 +54,7 @@ func GetActiveConfigVersion(ctx context.Context) (*model.ConfigVersion, error) { // GetLatestConfigVersionByPrefix returns the latest version string matching a date prefix. func GetLatestConfigVersionByPrefix(ctx context.Context, prefix string) (string, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return "", errors.New(errDatabaseNotInitialized) } @@ -73,7 +72,7 @@ func GetLatestConfigVersionByPrefix(ctx context.Context, prefix string) (string, // CreateConfigVersion inserts a new config version record. func CreateConfigVersion(ctx context.Context, version *model.ConfigVersion) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -82,7 +81,7 @@ func CreateConfigVersion(ctx context.Context, version *model.ConfigVersion) erro // PublishConfigVersionTx deactivates all versions and creates a new active version. func PublishConfigVersionTx(ctx context.Context, version *model.ConfigVersion) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -96,7 +95,7 @@ func PublishConfigVersionTx(ctx context.Context, version *model.ConfigVersion) e // ActivateConfigVersionTx marks the given version active and deactivates others. func ActivateConfigVersionTx(ctx context.Context, version string) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -113,7 +112,7 @@ func DeleteConfigVersionsByVersions(ctx context.Context, versions []string) (int if len(versions) == 0 { return 0, nil } - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return 0, errors.New(errDatabaseNotInitialized) } @@ -123,7 +122,7 @@ func DeleteConfigVersionsByVersions(ctx context.Context, versions []string) (int // ListEnabledProxyRoutes returns enabled proxy routes ordered by id asc. func ListEnabledProxyRoutes(ctx context.Context) ([]*model.ProxyRoute, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_dns_account.go b/backend/openflare/plugins/server/kernel/repository/openflare_dns_account.go index ac9129ec..a900d3cb 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_dns_account.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_dns_account.go @@ -8,12 +8,11 @@ import ( "errors" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // ListDNSAccounts 列出全部 DNS 账号(授权信息不通过 JSON 暴露)。 func ListDNSAccounts(ctx context.Context) ([]model.DNSAccount, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -26,7 +25,7 @@ func ListDNSAccounts(ctx context.Context) ([]model.DNSAccount, error) { // GetDNSAccountByID 按 ID 查询 DNS 账号。 func GetDNSAccountByID(ctx context.Context, id uint) (*model.DNSAccount, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -39,7 +38,7 @@ func GetDNSAccountByID(ctx context.Context, id uint) (*model.DNSAccount, error) // CreateDNSAccountRecord 创建 DNS 账号。 func CreateDNSAccountRecord(ctx context.Context, account *model.DNSAccount) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -48,7 +47,7 @@ func CreateDNSAccountRecord(ctx context.Context, account *model.DNSAccount) erro // SaveDNSAccount 保存 DNS 账号。 func SaveDNSAccount(ctx context.Context, account *model.DNSAccount) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -57,7 +56,7 @@ func SaveDNSAccount(ctx context.Context, account *model.DNSAccount) error { // DeleteDNSAccountRecord 删除 DNS 账号。 func DeleteDNSAccountRecord(ctx context.Context, id uint) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_node.go b/backend/openflare/plugins/server/kernel/repository/openflare_node.go index da97b694..af1e0105 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_node.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_node.go @@ -11,7 +11,6 @@ import ( "gorm.io/gorm" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) const ( @@ -21,7 +20,7 @@ const ( // ListOpenFlareNodes returns all nodes ordered by id desc. func ListOpenFlareNodes(ctx context.Context) ([]model.OpenFlareNode, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -37,7 +36,7 @@ func ListOpenFlareNodesByNodeIDs(ctx context.Context, nodeIDs []string) ([]model if len(nodeIDs) == 0 { return []model.OpenFlareNode{}, nil } - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -50,7 +49,7 @@ func ListOpenFlareNodesByNodeIDs(ctx context.Context, nodeIDs []string) ([]model // GetOpenFlareNodeByID returns a node by primary key. func GetOpenFlareNodeByID(ctx context.Context, id uint) (*model.OpenFlareNode, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -63,7 +62,7 @@ func GetOpenFlareNodeByID(ctx context.Context, id uint) (*model.OpenFlareNode, e // GetOpenFlareNodeByNodeID returns a node by node_id. func GetOpenFlareNodeByNodeID(ctx context.Context, nodeID string) (*model.OpenFlareNode, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -76,7 +75,7 @@ func GetOpenFlareNodeByNodeID(ctx context.Context, nodeID string) (*model.OpenFl // GetOpenFlareNodeByAccessToken returns a node by access token. func GetOpenFlareNodeByAccessToken(ctx context.Context, token string) (*model.OpenFlareNode, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -89,7 +88,7 @@ func GetOpenFlareNodeByAccessToken(ctx context.Context, token string) (*model.Op // CreateOpenFlareNode inserts a new node. func CreateOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -98,7 +97,7 @@ func CreateOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error { // SaveOpenFlareNode persists node changes. func SaveOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -107,7 +106,7 @@ func SaveOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error { // UpdateOpenFlareNodeFields updates selected columns for a node. func UpdateOpenFlareNodeFields(ctx context.Context, node *model.OpenFlareNode, fields ...string) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -123,7 +122,7 @@ func UpdateOpenFlareNodeColumns(ctx context.Context, node *model.OpenFlareNode, if node == nil || len(changes) == 0 { return nil } - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -133,7 +132,7 @@ func UpdateOpenFlareNodeColumns(ctx context.Context, node *model.OpenFlareNode, // UpdateOpenFlareNodeFromApplyResult updates node status, last_seen, version and last_error after an apply report. // When applyResult is "success", current_version is set and last_error is cleared; otherwise last_error is set to message. func UpdateOpenFlareNodeFromApplyResult(ctx context.Context, nodeID, applyResult, version, message string, now time.Time) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -159,7 +158,7 @@ func updateOpenFlareNodeFromApplyResultTx(tx *gorm.DB, nodeID, applyResult, vers // DeleteOpenFlareNode removes a node by primary key. func DeleteOpenFlareNode(ctx context.Context, id uint) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_observability.go b/backend/openflare/plugins/server/kernel/repository/openflare_observability.go index c196baa8..8c411063 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_observability.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_observability.go @@ -18,7 +18,6 @@ import ( analyticsrepo "Wavelet/openflare/plugins/server/kernel/repository/analytics" "Wavelet/openflare/plugins/server/kernel/repository/logstore" "Wavelet/pkg/logger" - db "Wavelet/plugins/infra/database" ) const ( @@ -247,7 +246,7 @@ func ListOpenFlareMetricHourlySince(ctx context.Context, nodeID string, since ti // ListOpenFlareActiveHealthEvents returns active health events across all nodes. func ListOpenFlareActiveHealthEvents(ctx context.Context) ([]*model.OpenFlareHealthEvent, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -263,7 +262,7 @@ func ListOpenFlareActiveHealthEvents(ctx context.Context) ([]*model.OpenFlareHea // ListOpenFlareHealthEvents returns health events for a node. func ListOpenFlareHealthEvents(ctx context.Context, nodeID string, activeOnly bool, limit int) ([]*model.OpenFlareHealthEvent, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -358,7 +357,7 @@ func DeleteAllOpenFlareNodeObservationFrpc(ctx context.Context) (int64, error) { // DeleteOpenFlareHealthEventsByNodeID deletes all health events for a node. func DeleteOpenFlareHealthEventsByNodeID(ctx context.Context, nodeID string) (int64, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return 0, errors.New(errDatabaseNotInitialized) } @@ -374,7 +373,7 @@ func DeleteOpenFlareHealthEventsByNodeID(ctx context.Context, nodeID string) (in // GetOpenFlareNodeSystemProfile returns the system profile for a node. func GetOpenFlareNodeSystemProfile(ctx context.Context, nodeID string) (*model.OpenFlareNodeSystemProfile, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -393,7 +392,7 @@ func UpsertOpenFlareNodeSystemProfile(ctx context.Context, record *model.OpenFla if record == nil { return nil } - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -431,7 +430,7 @@ func ReconcileOpenFlareHealthEvents( reportedAt time.Time, managedEventTypes map[string]struct{}, ) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -455,7 +454,7 @@ func PersistOpenFlareNodePGObservability( if profile == nil && !reconcileHealth { return nil } - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_origin.go b/backend/openflare/plugins/server/kernel/repository/openflare_origin.go index 26d75875..b8a6178a 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_origin.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_origin.go @@ -9,23 +9,22 @@ import ( "gorm.io/gorm" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // WithOriginTx runs fn inside a database transaction for origin multi-step work. func WithOriginTx(ctx context.Context, fn func(tx *gorm.DB) error) error { - return db.DB(ctx).Transaction(fn) + return DB(ctx).Transaction(fn) } // HasProxyRoutesTable 判断代理规则表是否已迁移。 func HasProxyRoutesTable(ctx context.Context) bool { - return db.DB(ctx).Migrator().HasTable(&model.OriginProxyRoute{}) + return DB(ctx).Migrator().HasTable(&model.OriginProxyRoute{}) } // ListOrigins 列出全部源站。 func ListOrigins(ctx context.Context) ([]model.Origin, error) { var origins []model.Origin - if err := db.DB(ctx).Order("id desc").Find(&origins).Error; err != nil { + if err := DB(ctx).Order("id desc").Find(&origins).Error; err != nil { return nil, err } return origins, nil @@ -34,7 +33,7 @@ func ListOrigins(ctx context.Context) ([]model.Origin, error) { // GetOriginByID 按 ID 查询源站。 func GetOriginByID(ctx context.Context, id uint) (*model.Origin, error) { var origin model.Origin - if err := db.DB(ctx).First(&origin, id).Error; err != nil { + if err := DB(ctx).First(&origin, id).Error; err != nil { return nil, err } return &origin, nil @@ -43,7 +42,7 @@ func GetOriginByID(ctx context.Context, id uint) (*model.Origin, error) { // GetOriginByAddress 按地址查询源站。 func GetOriginByAddress(ctx context.Context, address string) (*model.Origin, error) { var origin model.Origin - if err := db.DB(ctx).Where("address = ?", address).First(&origin).Error; err != nil { + if err := DB(ctx).Where("address = ?", address).First(&origin).Error; err != nil { return nil, err } return &origin, nil @@ -51,12 +50,12 @@ func GetOriginByAddress(ctx context.Context, address string) (*model.Origin, err // CreateOriginRecord 创建源站。 func CreateOriginRecord(ctx context.Context, origin *model.Origin) error { - return db.DB(ctx).Create(origin).Error + return DB(ctx).Create(origin).Error } // SaveOrigin 保存源站。 func SaveOrigin(ctx context.Context, origin *model.Origin) error { - return SaveOriginTx(db.DB(ctx), origin) + return SaveOriginTx(DB(ctx), origin) } // SaveOriginTx saves an origin within an existing transaction. @@ -66,7 +65,7 @@ func SaveOriginTx(tx *gorm.DB, origin *model.Origin) error { // DeleteOriginRecord 删除源站。 func DeleteOriginRecord(ctx context.Context, id uint) error { - return db.DB(ctx).Delete(&model.Origin{}, id).Error + return DB(ctx).Delete(&model.Origin{}, id).Error } // ListOriginRouteCounts 统计各源站关联的代理规则数量。 @@ -75,7 +74,7 @@ func ListOriginRouteCounts(ctx context.Context) ([]model.OriginRouteCount, error return nil, nil } result := make([]model.OriginRouteCount, 0) - err := db.DB(ctx).Model(&model.OriginProxyRoute{}). + err := DB(ctx).Model(&model.OriginProxyRoute{}). Select("origin_id, COUNT(*) AS route_count"). Where("origin_id IS NOT NULL"). Group("origin_id"). @@ -89,7 +88,7 @@ func ListProxyRoutesByOriginID(ctx context.Context, originID uint) ([]model.Orig return nil, nil } var routes []model.OriginProxyRoute - if err := db.DB(ctx).Where("origin_id = ?", originID).Order("id desc").Find(&routes).Error; err != nil { + if err := DB(ctx).Where("origin_id = ?", originID).Order("id desc").Find(&routes).Error; err != nil { return nil, err } return routes, nil @@ -120,7 +119,7 @@ func CountProxyRoutesByOriginID(ctx context.Context, originID uint) (int64, erro return 0, nil } var count int64 - if err := db.DB(ctx).Model(&model.OriginProxyRoute{}).Where("origin_id = ?", originID).Count(&count).Error; err != nil { + if err := DB(ctx).Model(&model.OriginProxyRoute{}).Where("origin_id = ?", originID).Count(&count).Error; err != nil { return 0, err } return count, nil diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_pages.go b/backend/openflare/plugins/server/kernel/repository/openflare_pages.go index 25e85549..fb4cb492 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_pages.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_pages.go @@ -7,18 +7,17 @@ import ( "context" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // HasPagesProjectsTable 判断 Pages 项目表是否已迁移。 func HasPagesProjectsTable(ctx context.Context) bool { - return db.DB(ctx).Migrator().HasTable(&model.PagesProject{}) + return DB(ctx).Migrator().HasTable(&model.PagesProject{}) } // ListPagesProjects 列出全部 Pages 项目。 func ListPagesProjects(ctx context.Context) ([]model.PagesProject, error) { var projects []model.PagesProject - if err := db.DB(ctx).Order("id desc").Find(&projects).Error; err != nil { + if err := DB(ctx).Order("id desc").Find(&projects).Error; err != nil { return nil, err } return projects, nil @@ -27,7 +26,7 @@ func ListPagesProjects(ctx context.Context) ([]model.PagesProject, error) { // GetPagesProjectByID 按 ID 查询 Pages 项目。 func GetPagesProjectByID(ctx context.Context, id uint) (*model.PagesProject, error) { var project model.PagesProject - if err := db.DB(ctx).First(&project, id).Error; err != nil { + if err := DB(ctx).First(&project, id).Error; err != nil { return nil, err } return &project, nil @@ -36,7 +35,7 @@ func GetPagesProjectByID(ctx context.Context, id uint) (*model.PagesProject, err // GetPagesProjectBySlug 按 slug 查询 Pages 项目。 func GetPagesProjectBySlug(ctx context.Context, slug string) (*model.PagesProject, error) { var project model.PagesProject - if err := db.DB(ctx).Where("slug = ?", slug).First(&project).Error; err != nil { + if err := DB(ctx).Where("slug = ?", slug).First(&project).Error; err != nil { return nil, err } return &project, nil @@ -44,13 +43,13 @@ func GetPagesProjectBySlug(ctx context.Context, slug string) (*model.PagesProjec // CreatePagesProjectRecord 创建 Pages 项目。 func CreatePagesProjectRecord(ctx context.Context, project *model.PagesProject) error { - return db.DB(ctx).Create(project).Error + return DB(ctx).Create(project).Error } // ListPagesDeployments 列出项目的全部部署。 func ListPagesDeployments(ctx context.Context, projectID uint) ([]model.PagesDeployment, error) { var deployments []model.PagesDeployment - if err := db.DB(ctx).Where("project_id = ?", projectID).Order("id desc").Find(&deployments).Error; err != nil { + if err := DB(ctx).Where("project_id = ?", projectID).Order("id desc").Find(&deployments).Error; err != nil { return nil, err } return deployments, nil @@ -59,7 +58,7 @@ func ListPagesDeployments(ctx context.Context, projectID uint) ([]model.PagesDep // GetPagesDeploymentByID 按 ID 查询 Pages 部署。 func GetPagesDeploymentByID(ctx context.Context, id uint) (*model.PagesDeployment, error) { var deployment model.PagesDeployment - if err := db.DB(ctx).First(&deployment, id).Error; err != nil { + if err := DB(ctx).First(&deployment, id).Error; err != nil { return nil, err } return &deployment, nil @@ -68,7 +67,7 @@ func GetPagesDeploymentByID(ctx context.Context, id uint) (*model.PagesDeploymen // ListPagesDeploymentFiles 列出部署文件清单。 func ListPagesDeploymentFiles(ctx context.Context, deploymentID uint) ([]model.PagesDeploymentFile, error) { var files []model.PagesDeploymentFile - if err := db.DB(ctx).Where("deployment_id = ?", deploymentID).Order("path asc").Find(&files).Error; err != nil { + if err := DB(ctx).Where("deployment_id = ?", deploymentID).Order("path asc").Find(&files).Error; err != nil { return nil, err } return files, nil @@ -77,7 +76,7 @@ func ListPagesDeploymentFiles(ctx context.Context, deploymentID uint) ([]model.P // CountPagesDeploymentsByProjectID 统计项目部署数量。 func CountPagesDeploymentsByProjectID(ctx context.Context, projectID uint) (int64, error) { var count int64 - if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", projectID).Count(&count).Error; err != nil { + if err := DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", projectID).Count(&count).Error; err != nil { return 0, err } return count, nil @@ -89,7 +88,7 @@ func CountProxyRoutesByPagesProjectID(ctx context.Context, projectID uint) (int6 return 0, nil } var count int64 - if err := db.DB(ctx).Model(&model.ProxyRoute{}).Where("pages_project_id = ?", projectID).Count(&count).Error; err != nil { + if err := DB(ctx).Model(&model.ProxyRoute{}).Where("pages_project_id = ?", projectID).Count(&count).Error; err != nil { return 0, err } return count, nil diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_pages_cleanup.go b/backend/openflare/plugins/server/kernel/repository/openflare_pages_cleanup.go index 42c73ee8..ecb8ab5f 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_pages_cleanup.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_pages_cleanup.go @@ -8,7 +8,6 @@ import ( "errors" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // ListPagesOrphanUploadCandidates returns at most 100 unreferenced, isolated @@ -21,17 +20,20 @@ func ListPagesOrphanUploadCandidates( if input.SystemUserID == 0 || input.UploadType == "" || input.Marker == "" || input.CreatedBefore.IsZero() { return nil, errors.New("invalid pages orphan upload candidate query") } - markerPredicate, err := pagesOrphanMarkerPredicate(db.DB(ctx).Name()) + markerPredicate, err := pagesOrphanMarkerPredicate(DB(ctx).Name()) if err != nil { return nil, err } deploymentTable := (model.PagesDeployment{}).TableName() - uploadTable := (model.Upload{}).TableName() + const ( + uploadTable = "w_uploads" + uploadStatusUsed = "used" + ) var candidates []model.Upload - err = db.DB(ctx). - Model(&model.Upload{}). - Where(uploadTable+".status = ?", model.UploadStatusUsed). + err = DB(ctx). + Table(uploadTable). + Where(uploadTable+".status = ?", uploadStatusUsed). Where(uploadTable+".user_id = ?", input.SystemUserID). Where(uploadTable+".type = ?", input.UploadType). Where(uploadTable+".created_at < ?", input.CreatedBefore). diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_pages_cleanup_test.go b/backend/openflare/plugins/server/kernel/repository/openflare_pages_cleanup_test.go index b92a67f3..86c7da5d 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_pages_cleanup_test.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_pages_cleanup_test.go @@ -11,8 +11,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" - "github.com/glebarez/sqlite" "gorm.io/gorm" ) @@ -64,7 +62,7 @@ func TestListPagesOrphanUploadCandidatesFiltersAndLimits(t *testing.T) { "pages_project_id": "1", }} - valid := make([]model.Upload, 0, model.PagesOrphanUploadCandidateLimit+1) + valid := make([]testUploadEntity, 0, model.PagesOrphanUploadCandidateLimit+1) for index := 0; index < model.PagesOrphanUploadCandidateLimit+1; index++ { valid = append(valid, pagesCleanupModelUpload(uint64(index+100), 999, "openflare_pages_deployment", model.UploadStatusUsed, old, marker)) } @@ -81,7 +79,7 @@ func TestListPagesOrphanUploadCandidatesFiltersAndLimits(t *testing.T) { "pages_ingest_marker": "pages_deployment_v1", "pages_project_id": "1", }}) - for _, upload := range []model.Upload{referenced, wrongOwner, wrongType, wrongStatus, fresh, wrongMarker} { + for _, upload := range []testUploadEntity{referenced, wrongOwner, wrongType, wrongStatus, fresh, wrongMarker} { if err := gormDB.Create(&upload).Error; err != nil { t.Fatalf("create filtered upload %d error = %v, want nil", upload.ID, err) } @@ -100,7 +98,7 @@ func TestListPagesOrphanUploadCandidatesFiltersAndLimits(t *testing.T) { if err := gormDB.Create(&invalidJSON).Error; err != nil { t.Fatalf("create invalid JSON upload error = %v, want nil", err) } - if err := gormDB.Table((model.Upload{}).TableName()).Where("id = ?", invalidJSON.ID). + if err := gormDB.Table("w_uploads").Where("id = ?", invalidJSON.ID). UpdateColumn("metadata", "{invalid").Error; err != nil { t.Fatalf("corrupt upload metadata error = %v, want nil", err) } @@ -133,7 +131,7 @@ func TestListPagesOrphanUploadCandidatesSkipsInvalidSQLiteJSON(t *testing.T) { if err := gormDB.Create(&upload).Error; err != nil { t.Fatalf("create invalid JSON candidate error = %v, want nil", err) } - if err := gormDB.Table((model.Upload{}).TableName()).Where("id = ?", upload.ID). + if err := gormDB.Table("w_uploads").Where("id = ?", upload.ID). UpdateColumn("metadata", "{invalid").Error; err != nil { t.Fatalf("corrupt upload metadata error = %v, want nil", err) } @@ -152,6 +150,25 @@ func TestListPagesOrphanUploadCandidatesSkipsInvalidSQLiteJSON(t *testing.T) { } } +type testUploadEntity struct { + ID uint64 `gorm:"primaryKey"` + UserID uint64 `gorm:"index"` + FileName string `gorm:"size:255"` + FilePath string `gorm:"size:500"` + Size int64 + MimeType string `gorm:"size:100"` + Hash string `gorm:"size:64"` + Type string `gorm:"size:50;index"` + Status model.UploadStatus `gorm:"type:varchar(20)"` + Metadata model.UploadMetadata `gorm:"serializer:json;type:jsonb"` + CreatedAt time.Time + UpdatedAt time.Time +} + +func (testUploadEntity) TableName() string { + return "w_uploads" +} + func setupPagesCleanupModelTestDB(t *testing.T) *gorm.DB { t.Helper() @@ -161,11 +178,11 @@ func setupPagesCleanupModelTestDB(t *testing.T) *gorm.DB { if err != nil { t.Fatalf("open Pages cleanup model test database error = %v, want nil", err) } - if err := gormDB.AutoMigrate(&model.Upload{}, &model.PagesDeployment{}); err != nil { + if err := gormDB.AutoMigrate(&testUploadEntity{}, &model.PagesDeployment{}); err != nil { t.Fatalf("migrate Pages cleanup model test database error = %v, want nil", err) } - db.SetDB(gormDB) - t.Cleanup(func() { db.SetDB(nil) }) + SetDBForTest(gormDB) + t.Cleanup(func() { SetDBForTest(nil) }) return gormDB } @@ -176,21 +193,19 @@ func pagesCleanupModelUpload( status model.UploadStatus, createdAt time.Time, metadata model.UploadMetadata, -) model.Upload { - return model.Upload{ - ID: id, - UserID: userID, - FileName: "site.zip", - FilePath: "pages/site.zip", - FileSize: 10, - MimeType: "application/zip", - Extension: "zip", - Hash: "checksum", - Type: uploadType, - Status: status, - AccessMode: 0, - Metadata: metadata, - CreatedAt: createdAt, - UpdatedAt: createdAt, +) testUploadEntity { + return testUploadEntity{ + ID: id, + UserID: userID, + FileName: "site.zip", + FilePath: "pages/site.zip", + Size: 10, + MimeType: "application/zip", + Hash: "checksum", + Type: uploadType, + Status: status, + Metadata: metadata, + CreatedAt: createdAt, + UpdatedAt: createdAt, } } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_pages_source.go b/backend/openflare/plugins/server/kernel/repository/openflare_pages_source.go index 1b24e6ee..43e2c802 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_pages_source.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_pages_source.go @@ -11,20 +11,19 @@ import ( "gorm.io/gorm/clause" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) const pagesRowLockStrength = "UPDATE" // WithPagesTx runs fn inside a database transaction for Pages multi-step work. func WithPagesTx(ctx context.Context, fn func(tx *gorm.DB) error) error { - return db.DB(ctx).Transaction(fn) + return DB(ctx).Transaction(fn) } // GetPagesProjectSourceByID loads a project source by primary key. func GetPagesProjectSourceByID(ctx context.Context, id uint) (*model.PagesProjectSource, error) { var source model.PagesProjectSource - if err := db.DB(ctx).Where("id = ?", id).First(&source).Error; err != nil { + if err := DB(ctx).Where("id = ?", id).First(&source).Error; err != nil { return nil, err } return &source, nil @@ -33,7 +32,7 @@ func GetPagesProjectSourceByID(ctx context.Context, id uint) (*model.PagesProjec // GetPagesProjectSourceByProjectID loads the unique source for a project. func GetPagesProjectSourceByProjectID(ctx context.Context, projectID uint) (*model.PagesProjectSource, error) { var source model.PagesProjectSource - if err := db.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil { + if err := DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil { return nil, err } return &source, nil @@ -46,7 +45,7 @@ func GetPagesProjectSourceByIDAndConfigVersion( configVersion int, ) (*model.PagesProjectSource, error) { var source model.PagesProjectSource - if err := db.DB(ctx).Where("id = ? AND config_version = ?", id, configVersion).First(&source).Error; err != nil { + if err := DB(ctx).Where("id = ? AND config_version = ?", id, configVersion).First(&source).Error; err != nil { return nil, err } return &source, nil @@ -58,7 +57,7 @@ func GetPagesProjectSourceRuntimeBySourceID( sourceID uint, ) (*model.PagesProjectSourceRuntime, error) { var runtime model.PagesProjectSourceRuntime - if err := db.DB(ctx).Where("source_id = ?", sourceID).First(&runtime).Error; err != nil { + if err := DB(ctx).Where("source_id = ?", sourceID).First(&runtime).Error; err != nil { return nil, err } return &runtime, nil @@ -207,7 +206,7 @@ func TryAcquirePagesSourceRuntimeLease( now time.Time, updates map[string]any, ) (int64, error) { - result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", sourceID). Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now). Where( @@ -227,7 +226,7 @@ func RenewPagesSourceRuntimeLease( now time.Time, expiresAt time.Time, ) (int64, error) { - result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ? AND lease_token = ? AND lease_expires_at > ?", sourceID, token, now). Updates(map[string]any{"lease_expires_at": expiresAt}) return result.RowsAffected, result.Error @@ -241,7 +240,7 @@ func UpdatePagesSourceRuntimeByActiveLease( now time.Time, updates map[string]any, ) (int64, error) { - return UpdatePagesSourceRuntimeByActiveLeaseTx(db.DB(ctx), sourceID, token, now, updates) + return UpdatePagesSourceRuntimeByActiveLeaseTx(DB(ctx), sourceID, token, now, updates) } // UpdatePagesSourceRuntimeByActiveLeaseTx updates runtime under an active lease inside a transaction. @@ -268,7 +267,7 @@ func RecoverExpiredPagesSourceRuntimeLease( now time.Time, updates map[string]any, ) (int64, error) { - result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", sourceID). Where("lease_token = ?", token). Where("lease_expires_at = ? AND lease_expires_at <= ?", expiresAt, now). @@ -285,7 +284,7 @@ func MarkPagesSourceInitialCheckDispatchFailed( now time.Time, updates map[string]any, ) (int64, error) { - result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", sourceID). Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now). Where( @@ -309,7 +308,7 @@ func RecordPagesSourceAutoDispatchFailure( now time.Time, updates map[string]any, ) (int64, error) { - result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}). + result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}). Where("source_id = ?", sourceID). Where("sync_status = ? AND last_seen_revision = ?", updateAvailableStatus, revision). Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now). @@ -336,7 +335,7 @@ func ListExpiredPagesSourceLeaseCandidates( syncStatuses []string, ) ([]model.PagesExpiredSourceLeaseCandidate, error) { var candidates []model.PagesExpiredSourceLeaseCandidate - err := db.DB(ctx). + err := DB(ctx). Table("of_pages_project_source_runtime AS runtime"). Select(`runtime.source_id, runtime.lease_token, runtime.lease_expires_at, runtime.sync_status, source.source_type, source.release_selector`). @@ -391,7 +390,7 @@ func dueGitHubPagesSourceQuery( sourceType string, releaseSelector string, ) *gorm.DB { - return db.DB(ctx). + return DB(ctx). Table("of_pages_project_source_runtime AS runtime"). Joins("JOIN of_pages_project_sources AS source ON source.id = runtime.source_id"). Where("source.source_type = ?", sourceType). @@ -407,7 +406,7 @@ func GetPagesDeploymentBySourceRevision( revision string, ) (*model.PagesDeployment, error) { var deployment model.PagesDeployment - err := db.DB(ctx). + err := DB(ctx). Where("project_id = ? AND source_identity = ? AND source_revision = ?", projectID, sourceIdentity, revision). First(&deployment).Error if err != nil { diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_proxy_route.go b/backend/openflare/plugins/server/kernel/repository/openflare_proxy_route.go index cacb31b0..93bb0234 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_proxy_route.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_proxy_route.go @@ -11,7 +11,6 @@ import ( "gorm.io/gorm/clause" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // Zone domain binding sentinel errors (shared by proxy_route and zone binding helpers). @@ -24,13 +23,13 @@ var ( // WithProxyRouteTx runs fn inside a database transaction for proxy-route multi-step work. func WithProxyRouteTx(ctx context.Context, fn func(tx *gorm.DB) error) error { - return db.DB(ctx).Transaction(fn) + return DB(ctx).Transaction(fn) } // ListProxyRoutes 列出全部代理规则。 func ListProxyRoutes(ctx context.Context) ([]*model.ProxyRoute, error) { var routes []*model.ProxyRoute - if err := db.DB(ctx).Order("id desc").Find(&routes).Error; err != nil { + if err := DB(ctx).Order("id desc").Find(&routes).Error; err != nil { return nil, err } return routes, nil @@ -39,7 +38,7 @@ func ListProxyRoutes(ctx context.Context) ([]*model.ProxyRoute, error) { // GetProxyRouteByID 按 ID 查询代理规则。 func GetProxyRouteByID(ctx context.Context, id uint) (*model.ProxyRoute, error) { var route model.ProxyRoute - if err := db.DB(ctx).First(&route, id).Error; err != nil { + if err := DB(ctx).First(&route, id).Error; err != nil { return nil, err } return &route, nil @@ -47,7 +46,7 @@ func GetProxyRouteByID(ctx context.Context, id uint) (*model.ProxyRoute, error) // CreateProxyRouteRecord 创建代理规则。 func CreateProxyRouteRecord(ctx context.Context, route *model.ProxyRoute) error { - return CreateProxyRouteRecordTx(db.DB(ctx), route) + return CreateProxyRouteRecordTx(DB(ctx), route) } // CreateProxyRouteRecordTx creates a proxy route within an existing transaction. @@ -57,7 +56,7 @@ func CreateProxyRouteRecordTx(tx *gorm.DB, route *model.ProxyRoute) error { // UpdateProxyRouteRecord 更新代理规则。 func UpdateProxyRouteRecord(ctx context.Context, route *model.ProxyRoute) error { - return UpdateProxyRouteRecordTx(db.DB(ctx), route) + return UpdateProxyRouteRecordTx(DB(ctx), route) } // UpdateProxyRouteRecordTx updates a proxy route within an existing transaction. @@ -96,7 +95,7 @@ func proxyRouteUpdateMap(route *model.ProxyRoute) map[string]any { // DeleteProxyRouteRecord 删除代理规则。 func DeleteProxyRouteRecord(ctx context.Context, id uint) error { - return DeleteProxyRouteRecordTx(db.DB(ctx), id) + return DeleteProxyRouteRecordTx(DB(ctx), id) } // DeleteProxyRouteRecordTx deletes a proxy route within an existing transaction. diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_tls.go b/backend/openflare/plugins/server/kernel/repository/openflare_tls.go index 7e8f3190..b55a7fe1 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_tls.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_tls.go @@ -8,17 +8,16 @@ import ( "errors" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // HasTLSProxyRoutesTable 判断代理规则表是否已迁移。 func HasTLSProxyRoutesTable(ctx context.Context) bool { - return db.DB(ctx).Migrator().HasTable(&model.TLSProxyRouteRef{}) + return DB(ctx).Migrator().HasTable(&model.TLSProxyRouteRef{}) } // ListTLSCertificates 列出全部证书(不含 PEM 敏感字段的 JSON 暴露由 struct tag 控制)。 func ListTLSCertificates(ctx context.Context) ([]model.TLSCertificate, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -31,7 +30,7 @@ func ListTLSCertificates(ctx context.Context) ([]model.TLSCertificate, error) { // GetTLSCertificateByID 按 ID 查询证书。 func GetTLSCertificateByID(ctx context.Context, id uint) (*model.TLSCertificate, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } @@ -44,7 +43,7 @@ func GetTLSCertificateByID(ctx context.Context, id uint) (*model.TLSCertificate, // CreateTLSCertificateRecord 创建证书记录。 func CreateTLSCertificateRecord(ctx context.Context, certificate *model.TLSCertificate) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -53,7 +52,7 @@ func CreateTLSCertificateRecord(ctx context.Context, certificate *model.TLSCerti // SaveTLSCertificate 保存证书记录。 func SaveTLSCertificate(ctx context.Context, certificate *model.TLSCertificate) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -62,7 +61,7 @@ func SaveTLSCertificate(ctx context.Context, certificate *model.TLSCertificate) // DeleteTLSCertificateRecord 删除证书记录。 func DeleteTLSCertificateRecord(ctx context.Context, id uint) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -71,7 +70,7 @@ func DeleteTLSCertificateRecord(ctx context.Context, id uint) error { // CountTLSCertificatesByDNSAccountID 统计引用指定 DNS 账号的证书数量。 func CountTLSCertificatesByDNSAccountID(ctx context.Context, dnsAccountID uint) (int64, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return 0, errors.New(errDatabaseNotInitialized) } @@ -88,7 +87,7 @@ func ListTLSProxyRouteRefs(ctx context.Context) ([]model.TLSProxyRouteRef, error return nil, nil } var routes []model.TLSProxyRouteRef - if err := db.DB(ctx).Order("id asc").Find(&routes).Error; err != nil { + if err := DB(ctx).Order("id asc").Find(&routes).Error; err != nil { return nil, err } return routes, nil diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_waf.go b/backend/openflare/plugins/server/kernel/repository/openflare_waf.go index 42ba2f89..12661679 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_waf.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_waf.go @@ -11,11 +11,10 @@ import ( "gorm.io/gorm" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) func wafDB(ctx context.Context) (*gorm.DB, error) { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return nil, errors.New(errDatabaseNotInitialized) } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_waf_bindings_test.go b/backend/openflare/plugins/server/kernel/repository/openflare_waf_bindings_test.go index 7f0f4621..295accd3 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_waf_bindings_test.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_waf_bindings_test.go @@ -9,8 +9,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" - "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,9 +24,9 @@ func setupWAFBindingsTestDB(t *testing.T) func() { require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareWAFRuleGroupBinding{})) - db.SetDB(sqliteDB) + SetDBForTest(sqliteDB) return func() { - db.SetDB(nil) + SetDBForTest(nil) } } @@ -37,7 +35,7 @@ func TestReplaceOpenFlareWAFRuleGroupBindingsAfterExplicitHighID(t *testing.T) { defer cleanup() ctx := context.Background() - conn := db.DB(ctx) + conn := DB(ctx) require.NotNil(t, conn) require.NoError(t, conn.Create(&model.OpenFlareWAFRuleGroupBinding{ ID: 50, diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_waf_graph_test.go b/backend/openflare/plugins/server/kernel/repository/openflare_waf_graph_test.go index f69056b1..dd8f9d8b 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_waf_graph_test.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_waf_graph_test.go @@ -9,8 +9,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" - "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -23,8 +21,8 @@ func TestOpenFlareWAFGraphOptimisticUpdate(t *testing.T) { conn, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) require.NoError(t, conn.AutoMigrate(&model.OpenFlareWAFRuleGroup{})) - db.SetDB(conn) - t.Cleanup(func() { db.SetDB(nil) }) + SetDBForTest(conn) + t.Cleanup(func() { SetDBForTest(nil) }) group := model.OpenFlareWAFRuleGroup{Name: "rule", Graph: defaultWAFRuleGraph, Revision: 1} require.NoError(t, conn.Create(&group).Error) diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_zone.go b/backend/openflare/plugins/server/kernel/repository/openflare_zone.go index 20611c7f..1fec69d2 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_zone.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_zone.go @@ -10,13 +10,12 @@ import ( "gorm.io/gorm" "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" ) // ListZones returns all zones ordered by domain ascending. func ListZones(ctx context.Context) ([]model.Zone, error) { var zones []model.Zone - if err := db.DB(ctx).Order("domain asc").Find(&zones).Error; err != nil { + if err := DB(ctx).Order("domain asc").Find(&zones).Error; err != nil { return nil, err } return zones, nil @@ -25,7 +24,7 @@ func ListZones(ctx context.Context) ([]model.Zone, error) { // GetZoneByID returns a zone by primary key. func GetZoneByID(ctx context.Context, id uint) (*model.Zone, error) { var zone model.Zone - if err := db.DB(ctx).First(&zone, id).Error; err != nil { + if err := DB(ctx).First(&zone, id).Error; err != nil { return nil, err } return &zone, nil @@ -33,23 +32,23 @@ func GetZoneByID(ctx context.Context, id uint) (*model.Zone, error) { // CreateZone creates a zone record. func CreateZone(ctx context.Context, zone *model.Zone) error { - return db.DB(ctx).Create(zone).Error + return DB(ctx).Create(zone).Error } // SaveZone persists zone updates. func SaveZone(ctx context.Context, zone *model.Zone) error { - return db.DB(ctx).Save(zone).Error + return DB(ctx).Save(zone).Error } // DeleteZone deletes a zone by primary key. func DeleteZone(ctx context.Context, id uint) error { - return db.DB(ctx).Delete(&model.Zone{}, id).Error + return DB(ctx).Delete(&model.Zone{}, id).Error } // ListZoneDomainCounts returns per-zone domain counts for list cards. func ListZoneDomainCounts(ctx context.Context) ([]model.ZoneDomainCount, error) { var rows []model.ZoneDomainCount - if err := db.DB(ctx).Model(&model.ZoneDomain{}). + if err := DB(ctx).Model(&model.ZoneDomain{}). Select("zone_id, count(*) as count"). Group("zone_id"). Scan(&rows).Error; err != nil { @@ -61,7 +60,7 @@ func ListZoneDomainCounts(ctx context.Context) ([]model.ZoneDomainCount, error) // ListZoneDomainsByZoneID returns domains under a zone ordered by domain ascending. func ListZoneDomainsByZoneID(ctx context.Context, zoneID uint) ([]model.ZoneDomain, error) { var domains []model.ZoneDomain - if err := db.DB(ctx).Where("zone_id = ?", zoneID).Order("domain asc").Find(&domains).Error; err != nil { + if err := DB(ctx).Where("zone_id = ?", zoneID).Order("domain asc").Find(&domains).Error; err != nil { return nil, err } return domains, nil @@ -70,7 +69,7 @@ func ListZoneDomainsByZoneID(ctx context.Context, zoneID uint) ([]model.ZoneDoma // CountZoneDomainsByZoneID counts domains under a zone. func CountZoneDomainsByZoneID(ctx context.Context, zoneID uint) (int64, error) { var count int64 - if err := db.DB(ctx).Model(&model.ZoneDomain{}).Where("zone_id = ?", zoneID).Count(&count).Error; err != nil { + if err := DB(ctx).Model(&model.ZoneDomain{}).Where("zone_id = ?", zoneID).Count(&count).Error; err != nil { return 0, err } return count, nil @@ -79,7 +78,7 @@ func CountZoneDomainsByZoneID(ctx context.Context, zoneID uint) (int64, error) { // GetZoneDomainByZoneAndID returns a domain scoped to a zone. func GetZoneDomainByZoneAndID(ctx context.Context, zoneID, id uint) (*model.ZoneDomain, error) { var item model.ZoneDomain - if err := db.DB(ctx).Where("id = ? AND zone_id = ?", id, zoneID).First(&item).Error; err != nil { + if err := DB(ctx).Where("id = ? AND zone_id = ?", id, zoneID).First(&item).Error; err != nil { return nil, err } return &item, nil @@ -87,17 +86,17 @@ func GetZoneDomainByZoneAndID(ctx context.Context, zoneID, id uint) (*model.Zone // CreateZoneDomain creates a zone domain record. func CreateZoneDomain(ctx context.Context, domain *model.ZoneDomain) error { - return db.DB(ctx).Create(domain).Error + return DB(ctx).Create(domain).Error } // SaveZoneDomain persists zone domain updates. func SaveZoneDomain(ctx context.Context, domain *model.ZoneDomain) error { - return db.DB(ctx).Save(domain).Error + return DB(ctx).Save(domain).Error } // DeleteZoneDomain deletes a zone domain record. func DeleteZoneDomain(ctx context.Context, domain *model.ZoneDomain) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } @@ -112,7 +111,7 @@ func DeleteZoneDomain(ctx context.Context, domain *model.ZoneDomain) error { // ListZoneDomainsByRouteID returns the domains bound to a proxy route. func ListZoneDomainsByRouteID(ctx context.Context, routeID uint) ([]model.ZoneDomain, error) { var domains []model.ZoneDomain - if err := db.DB(ctx).Where("proxy_route_id = ?", routeID).Order("id asc").Find(&domains).Error; err != nil { + if err := DB(ctx).Where("proxy_route_id = ?", routeID).Order("id asc").Find(&domains).Error; err != nil { return nil, err } return domains, nil @@ -124,7 +123,7 @@ func ListZoneDomainsByIDs(ctx context.Context, domainIDs []uint) ([]model.ZoneDo return []model.ZoneDomain{}, nil } var domains []model.ZoneDomain - if err := db.DB(ctx).Where("id IN ?", domainIDs).Find(&domains).Error; err != nil { + if err := DB(ctx).Where("id IN ?", domainIDs).Find(&domains).Error; err != nil { return nil, err } byID := make(map[uint]model.ZoneDomain, len(domains)) @@ -145,13 +144,13 @@ func ListZoneDomainsByIDs(ctx context.Context, domainIDs []uint) ([]model.ZoneDo // CountZoneDomainsByCertificateID reports whether a certificate is assigned to a model.Zone domain. func CountZoneDomainsByCertificateID(ctx context.Context, certificateID uint) (int64, error) { var count int64 - err := db.DB(ctx).Model(&model.ZoneDomain{}).Where("cert_id = ?", certificateID).Count(&count).Error + err := DB(ctx).Model(&model.ZoneDomain{}).Where("cert_id = ?", certificateID).Count(&count).Error return count, err } // ReplaceZoneDomainRouteBindings replaces every model.ZoneDomain binding for a proxy route. func ReplaceZoneDomainRouteBindings(ctx context.Context, routeID uint, domainIDs []uint) error { - conn := db.DB(ctx) + conn := DB(ctx) if conn == nil { return errors.New(errDatabaseNotInitialized) } diff --git a/backend/openflare/plugins/server/kernel/repository/openflare_zone_test.go b/backend/openflare/plugins/server/kernel/repository/openflare_zone_test.go index 8be4ade5..5a83496a 100644 --- a/backend/openflare/plugins/server/kernel/repository/openflare_zone_test.go +++ b/backend/openflare/plugins/server/kernel/repository/openflare_zone_test.go @@ -9,8 +9,6 @@ import ( "Wavelet/openflare/plugins/server/kernel/model" - db "Wavelet/plugins/infra/database" - "github.com/glebarez/sqlite" "github.com/stretchr/testify/require" "gorm.io/gorm" @@ -24,8 +22,8 @@ func setupZoneTestDB(t *testing.T) *gorm.DB { }) require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.Zone{}, &model.ZoneDomain{})) - db.SetDB(sqliteDB) - t.Cleanup(func() { db.SetDB(nil) }) + SetDBForTest(sqliteDB) + t.Cleanup(func() { SetDBForTest(nil) }) return sqliteDB } diff --git a/backend/openflare/plugins/server/kernel/repository/system_config.go b/backend/openflare/plugins/server/kernel/repository/system_config.go index d5e34a7b..f3789329 100644 --- a/backend/openflare/plugins/server/kernel/repository/system_config.go +++ b/backend/openflare/plugins/server/kernel/repository/system_config.go @@ -6,112 +6,162 @@ package repository import ( "context" "errors" + "sync" + "Wavelet/core/contracts" "Wavelet/openflare/plugins/server/kernel/model" - adminrepo "Wavelet/plugins/domain/admin/repository" - db "Wavelet/plugins/infra/database" ) -const configTypeSystem = "system" +var ( + configMu sync.RWMutex + configSvc contracts.SystemConfigService +) -// ensureAdminStore points OF config access at Wavelet's admin repository so -// reads hit the same cache that SaveOrUpdateSystemConfig invalidates. -func ensureAdminStore(ctx context.Context) error { - if conn := db.DB(ctx); conn != nil { - adminrepo.SetDBService(db.NewService(conn)) - } - if adminrepo.GetDB(ctx) == nil { - return errors.New(errDatabaseNotInitialized) - } - return nil +// SetSystemConfigService injects the platform SystemConfigService. +func SetSystemConfigService(s contracts.SystemConfigService) { + configMu.Lock() + defer configMu.Unlock() + configSvc = s } -// GetSystemConfigByKey loads a config row by key through the admin store cache. +func currentConfigService() contracts.SystemConfigService { + configMu.RLock() + defer configMu.RUnlock() + return configSvc +} + +func ensureConfigService() (contracts.SystemConfigService, error) { + svc := currentConfigService() + if svc == nil { + return nil, errors.New("system config service not initialized") + } + return svc, nil +} + +// GetSystemConfigByKey loads a config row by key through the system config service. func GetSystemConfigByKey(ctx context.Context, key string) (model.SystemConfig, error) { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return model.SystemConfig{}, err } - return adminrepo.GetSystemConfigByKey(ctx, key) + dto, err := svc.GetByKey(ctx, key) + if err != nil { + return model.SystemConfig{}, err + } + return model.FromSystemConfigDTO(dto), nil } -// ListSystemConfigsByKeys loads multiple config keys through the admin store cache. +// ListSystemConfigsByKeys loads multiple config keys through the system config service. func ListSystemConfigsByKeys(ctx context.Context, keys []string) (map[string]model.SystemConfig, error) { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return nil, err } - return adminrepo.ListSystemConfigsByKeys(ctx, keys) + dtos, err := svc.ListByKeys(ctx, keys) + if err != nil { + return nil, err + } + res := make(map[string]model.SystemConfig, len(dtos)) + for k, v := range dtos { + res[k] = model.FromSystemConfigDTO(v) + } + return res, nil } -// ListVisibleSystemConfigs returns visibility=1 configs from the admin store cache. +// ListVisibleSystemConfigs returns visibility=1 configs from the system config service. func ListVisibleSystemConfigs(ctx context.Context) ([]model.SystemConfig, error) { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return nil, err } - return adminrepo.ListVisibleSystemConfigs(ctx) + dtos, err := svc.ListVisible(ctx) + if err != nil { + return nil, err + } + res := make([]model.SystemConfig, len(dtos)) + for i, v := range dtos { + res[i] = model.FromSystemConfigDTO(v) + } + return res, nil } // GetIntByKey queries config and converts to int. func GetIntByKey(ctx context.Context, key string) (int, error) { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return 0, err } - return adminrepo.GetIntByKey(ctx, key) + return svc.GetIntByKey(ctx, key) } // GetBoolByKey queries config and converts to bool. func GetBoolByKey(ctx context.Context, key string) (bool, error) { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return false, err } - return adminrepo.GetBoolByKey(ctx, key) + return svc.GetBoolByKey(ctx, key) } // CreateSystemConfig persists a new system config row. func CreateSystemConfig(ctx context.Context, config *model.SystemConfig) error { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return err } - return adminrepo.CreateSystemConfigRecord(ctx, config) + return svc.SaveOrUpdate(ctx, config.Key, config.Value) } -// SaveOrUpdateSystemConfig creates or updates a config row and invalidates the admin cache. +// SaveOrUpdateSystemConfig creates or updates a config row. func SaveOrUpdateSystemConfig(ctx context.Context, key, value string) error { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return err } - return adminrepo.SaveOrUpdateSystemConfig(ctx, key, value) + return svc.SaveOrUpdate(ctx, key, value) } -// InvalidateSystemConfigCache evicts one key from Wavelet's system-config cache. +// InvalidateSystemConfigCache evicts one key from the system-config cache. func InvalidateSystemConfigCache(ctx context.Context, key string) error { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return err } - return adminrepo.InvalidateSystemConfigCache(ctx, key) + return svc.InvalidateCache(ctx, key) } -// InvalidateAllSystemConfigCaches evicts the whole Wavelet system-config cache. +// InvalidateAllSystemConfigCaches evicts the whole system-config cache. func InvalidateAllSystemConfigCaches(ctx context.Context) error { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return err } - return adminrepo.InvalidateAllSystemConfigCaches(ctx) + return svc.InvalidateAllCaches(ctx) } -// StopSystemConfigCacheListener is retained for existing tests. -func StopSystemConfigCacheListener() { - adminrepo.StopSystemConfigCacheListener() -} +// StopSystemConfigCacheListener is retained for test compatibility. +func StopSystemConfigCacheListener() {} // ResetSystemConfigRAMCacheForTest clears the process-local admin config cache. func ResetSystemConfigRAMCacheForTest() { - adminrepo.ResetSystemConfigRAMCacheForTest() + if svc := currentConfigService(); svc != nil { + _ = svc.InvalidateAllCaches(context.Background()) + } } // ListAdminSystemConfigs returns configs, optionally filtered by type. func ListAdminSystemConfigs(ctx context.Context, configType string) ([]model.SystemConfig, error) { - if err := ensureAdminStore(ctx); err != nil { + svc, err := ensureConfigService() + if err != nil { return nil, err } - return adminrepo.ListAdminSystemConfigs(ctx, configType) + dtos, err := svc.ListByType(ctx, configType) + if err != nil { + return nil, err + } + res := make([]model.SystemConfig, len(dtos)) + for i, v := range dtos { + res[i] = model.FromSystemConfigDTO(v) + } + return res, nil } diff --git a/backend/openflare/plugins/server/kernel/repository/system_user.go b/backend/openflare/plugins/server/kernel/repository/system_user.go index 05adb21b..027570e0 100644 --- a/backend/openflare/plugins/server/kernel/repository/system_user.go +++ b/backend/openflare/plugins/server/kernel/repository/system_user.go @@ -6,12 +6,34 @@ package repository import ( "context" "errors" + "sync" + "Wavelet/core/contracts" "Wavelet/openflare/plugins/server/kernel/model" - adminrepo "Wavelet/plugins/domain/admin/repository" ) -const fallbackSystemUserID uint64 = 999 +const ( + fallbackSystemUserID uint64 = 999 + configTypeSystem = "system" +) + +var ( + taskMu sync.RWMutex + taskSvc contracts.TaskService +) + +// SetTaskService injects the platform TaskService. +func SetTaskService(s contracts.TaskService) { + taskMu.Lock() + defer taskMu.Unlock() + taskSvc = s +} + +func currentTaskService() contracts.TaskService { + taskMu.RLock() + defer taskMu.RUnlock() + return taskSvc +} // GetActiveAuthSources lists enabled Wavelet auth sources via AuthService. func GetActiveAuthSources(ctx context.Context) ([]model.AuthSource, error) { @@ -41,11 +63,12 @@ func GetActiveAuthSources(ctx context.Context) ([]model.AuthSource, error) { } // GetTaskExecutionByTaskID loads a task execution by public task ID. -func GetTaskExecutionByTaskID(ctx context.Context, taskID string) (*model.TaskExecution, error) { - if err := ensureAdminStore(ctx); err != nil { - return nil, err +func GetTaskExecutionByTaskID(ctx context.Context, taskID string) (*contracts.TaskExecutionDTO, error) { + svc := currentTaskService() + if svc == nil { + return nil, errors.New("task service not initialized") } - return adminrepo.GetTaskExecutionByTaskID(ctx, taskID) + return svc.GetExecutionByTaskID(ctx, taskID) } // GetSystemUser loads the built-in system user via UserService, or a synthetic fallback. diff --git a/backend/openflare/plugins/server/kernel/repository/system_user_test.go b/backend/openflare/plugins/server/kernel/repository/system_user_test.go index 21593f6f..c4d592e1 100644 --- a/backend/openflare/plugins/server/kernel/repository/system_user_test.go +++ b/backend/openflare/plugins/server/kernel/repository/system_user_test.go @@ -5,15 +5,10 @@ package repository import ( "context" + "errors" "testing" "Wavelet/core/contracts" - "Wavelet/pkg/idgen" - adminmodel "Wavelet/plugins/domain/admin/model" - "Wavelet/plugins/infra/database" - - "github.com/glebarez/sqlite" - "gorm.io/gorm" ) type stubUserService struct { @@ -34,24 +29,6 @@ func (s stubAuthService) ListAuthSources(context.Context) ([]contracts.AuthSourc return s.sources, nil } -func setupRepoTestDB(t *testing.T) (*gorm.DB, func()) { - t.Helper() - sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ - DisableForeignKeyConstraintWhenMigrating: true, - }) - if err != nil { - t.Fatalf("gorm.Open() error = %v", err) - } - if err := sqliteDB.AutoMigrate(&adminmodel.TaskExecution{}); err != nil { - t.Fatalf("AutoMigrate(TaskExecution) error = %v", err) - } - if err := idgen.Init(1); err != nil { - t.Fatalf("idgen.Init() error = %v", err) - } - database.SetDB(sqliteDB) - return sqliteDB, func() { database.SetDB(nil) } -} - func TestGetActiveAuthSourcesUsesAuthService(t *testing.T) { SetAuthService(stubAuthService{}) t.Cleanup(func() { SetAuthService(nil) }) @@ -96,21 +73,27 @@ func TestGetSystemUserUsesUserService(t *testing.T) { } } -func TestGetTaskExecutionByTaskIDUsesAdminStore(t *testing.T) { - _, cleanup := setupRepoTestDB(t) - t.Cleanup(cleanup) +type mockTaskSvc struct { + contracts.TaskService + execution contracts.TaskExecutionDTO +} - ctx := context.Background() - row := &adminmodel.TaskExecution{ +func (m *mockTaskSvc) GetExecutionByTaskID(ctx context.Context, taskID string) (*contracts.TaskExecutionDTO, error) { + if taskID == m.execution.TaskID { + return &m.execution, nil + } + return nil, errors.New("not found") +} + +func TestGetTaskExecutionByTaskIDUsesAdminStore(t *testing.T) { + SetTaskService(&mockTaskSvc{execution: contracts.TaskExecutionDTO{ ID: 7, TaskID: "task-public-id", TaskType: "pages_source_action", - Status: adminmodel.TaskExecutionStatusPending, - } - if err := database.DB(ctx).Create(row).Error; err != nil { - t.Fatalf("Create(TaskExecution) error = %v", err) - } + }}) + t.Cleanup(func() { SetTaskService(nil) }) + ctx := context.Background() got, err := GetTaskExecutionByTaskID(ctx, "task-public-id") if err != nil { t.Fatalf("GetTaskExecutionByTaskID(%q) error = %v", "task-public-id", err) diff --git a/backend/openflare/plugins/server/kernel/runtimeconfig/runtime.go b/backend/openflare/plugins/server/kernel/runtimeconfig/runtime.go index 0dc156c3..dc4083f1 100644 --- a/backend/openflare/plugins/server/kernel/runtimeconfig/runtime.go +++ b/backend/openflare/plugins/server/kernel/runtimeconfig/runtime.go @@ -6,15 +6,27 @@ package runtimeconfig import ( "sync" - - "Wavelet/plugins/infra/database" ) +// ClickHouseConfig represents ClickHouse connection parameters. +type ClickHouseConfig struct { + Enabled bool `config:"enabled" env:"CLICKHOUSE_ENABLED" default:"false" autoEnable:"CLICKHOUSE_HOST"` + Hosts []string `config:"hosts" env:"CLICKHOUSE_HOST"` + Username string `config:"username" env:"CLICKHOUSE_USERNAME"` + Password string `config:"password" env:"CLICKHOUSE_PASSWORD" secret:"true"` + Database string `config:"database" env:"CLICKHOUSE_NAME" default:"wavelet"` + MaxIdleConn int `config:"max_idle_conn" env:"CLICKHOUSE_MAX_IDLE_CONN" default:"10"` + MaxOpenConn int `config:"max_open_conn" env:"CLICKHOUSE_MAX_OPEN_CONN" default:"50"` + ConnMaxLifetime int `config:"conn_max_lifetime" env:"CLICKHOUSE_CONN_MAX_LIFETIME" default:"3600"` + DialTimeout int `config:"dial_timeout" env:"CLICKHOUSE_DIAL_TIMEOUT" default:"10"` + BlockBufferSize uint8 `config:"block_buffer_size" env:"CLICKHOUSE_BLOCK_BUFFER_SIZE" default:"10"` +} + // Snapshot is the subset of host config remaining OF packages still need. type Snapshot struct { SessionSecret string DatabaseEnabled bool - ClickHouse database.ClickHouseConfig + ClickHouse ClickHouseConfig } var ( diff --git a/backend/openflare/plugins/server/kernel/testhelper/mock_storage.go b/backend/openflare/plugins/server/kernel/testhelper/mock_storage.go new file mode 100644 index 00000000..eede93b6 --- /dev/null +++ b/backend/openflare/plugins/server/kernel/testhelper/mock_storage.go @@ -0,0 +1,113 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package testhelper + +import ( + "bytes" + "context" + "io" + "sync" + "sync/atomic" + "time" + + "Wavelet/core/contracts" + "Wavelet/openflare/plugins/server/kernel/repository" + + "gorm.io/gorm" +) + +// MockStorageService provides an in-memory contracts.StorageService for tests. +type MockStorageService struct { + mu sync.RWMutex + objects map[string][]byte + seq uint64 +} + +// NewMockStorageService creates an initialized MockStorageService. +func NewMockStorageService() *MockStorageService { + return &MockStorageService{ + objects: make(map[string][]byte), + } +} + +// Put writes an object into memory. +func (m *MockStorageService) Put(_ context.Context, key string, body io.Reader, _ int64, _ string) (contracts.StoragePutResult, error) { + m.mu.Lock() + defer m.mu.Unlock() + data, err := io.ReadAll(body) + if err != nil { + return contracts.StoragePutResult{}, err + } + m.objects[key] = data + return contracts.StoragePutResult{Key: key, Bucket: "test-bucket"}, nil +} + +// Get reads an object from memory. +func (m *MockStorageService) Get(_ context.Context, key string) (*contracts.StorageObject, error) { + m.mu.RLock() + defer m.mu.RUnlock() + data, ok := m.objects[key] + if ok { + return &contracts.StorageObject{ + Key: key, + Body: io.NopCloser(bytes.NewReader(data)), + ContentLength: int64(len(data)), + ContentType: "application/octet-stream", + }, nil + } + return nil, gorm.ErrRecordNotFound +} + +// Delete removes an object from memory. +func (m *MockStorageService) Delete(_ context.Context, key string) error { + m.mu.Lock() + defer m.mu.Unlock() + delete(m.objects, key) + return nil +} + +// Ingest ingests content into mock storage. +func (m *MockStorageService) Ingest(ctx context.Context, r io.Reader, opts contracts.IngestOptions) (*contracts.IngestResult, error) { + id := atomic.AddUint64(&m.seq, 1) + m.mu.Lock() + data, _ := io.ReadAll(r) + key := opts.FileName + if key == "" { + key = "file.dat" + } + m.objects[key] = data + m.mu.Unlock() + + gdb := repository.DB(ctx) + if gdb != nil { + type testUpload struct { + ID uint64 `gorm:"primaryKey"` + UserID uint64 + FileName string + FilePath string + MimeType string + Size int64 + Status string + Type string + Metadata contracts.UploadMetadataDTO `gorm:"serializer:json;type:jsonb"` + CreatedAt time.Time + UpdatedAt time.Time + } + u := testUpload{ + ID: id, + UserID: opts.UserID, + FileName: key, + FilePath: "mock/" + key, + MimeType: opts.MimeType, + Size: opts.Size, + Status: "used", + Type: opts.Type, + Metadata: contracts.UploadMetadataDTO{Extra: opts.Metadata}, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + } + _ = gdb.Table("w_uploads").Save(&u).Error + } + return &contracts.IngestResult{ID: id, Key: "mock/" + key, Created: true, Stored: true}, nil +} diff --git a/backend/openflare/plugins/server/kernel/testhelper/noop_task.go b/backend/openflare/plugins/server/kernel/testhelper/noop_task.go index 03f8b4d7..f95887fe 100644 --- a/backend/openflare/plugins/server/kernel/testhelper/noop_task.go +++ b/backend/openflare/plugins/server/kernel/testhelper/noop_task.go @@ -9,9 +9,8 @@ import ( "time" "Wavelet/core/contracts" + "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/pkg/idgen" - adminmodel "Wavelet/plugins/domain/admin/model" - "Wavelet/plugins/infra/database" ) // NoopTaskService is a contracts.TaskService that records dispatch and ignores the rest. @@ -22,35 +21,76 @@ type NoopTaskService struct { var _ contracts.TaskService = (*NoopTaskService)(nil) +// Dispatch dispatches a task mock execution. func (s *NoopTaskService) Dispatch(ctx context.Context, taskType string, payload []byte, triggeredBy string) (string, error) { s.LastType = taskType s.LastPayload = payload taskID := fmt.Sprintf("test-task-%d", time.Now().UnixNano()) - if conn := database.DB(ctx); conn != nil { - _ = conn.Create(&adminmodel.TaskExecution{ - ID: idgen.NextUint64ID(), + gdb := repository.DB(ctx) + if gdb != nil { + var id uint64 + func() { + defer func() { + if r := recover(); r != nil { + id = uint64(time.Now().UnixNano()) + } + }() + id = idgen.NextUint64ID() + }() + _ = gdb.Table("w_task_executions").Create(&contracts.TaskExecutionDTO{ + ID: id, TaskID: taskID, TaskType: taskType, - Status: adminmodel.TaskExecutionStatusPending, - TriggeredBy: triggeredBy, Payload: string(payload), + TriggeredBy: triggeredBy, + Status: "pending", + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), }).Error } return taskID, nil } + +// Retry retries a task mock execution. func (s *NoopTaskService) Retry(context.Context, uint64) (string, error) { return "", nil } -func (s *NoopTaskService) ListTasks() []contracts.TaskMetaDTO { return nil } + +// ListTasks lists task mock metadata. +func (s *NoopTaskService) ListTasks() []contracts.TaskMetaDTO { return nil } + +// GetTaskMeta returns task mock metadata. func (s *NoopTaskService) GetTaskMeta(string) (contracts.TaskMetaDTO, bool) { return contracts.TaskMetaDTO{}, false } + +// ValidatePayload validates task payload. func (s *NoopTaskService) ValidatePayload(_ string, payload []byte) ([]byte, error) { return payload, nil } -func (s *NoopTaskService) ReloadScheduler() error { return nil } + +// ReloadScheduler reloads task scheduler. +func (s *NoopTaskService) ReloadScheduler() error { return nil } + +// AppendLog appends log message. func (s *NoopTaskService) AppendLog(context.Context, string, ...any) {} + +// ListExecutions lists task executions. func (s *NoopTaskService) ListExecutions(context.Context, string, string, int, int) ([]contracts.TaskExecutionDTO, int64, error) { return nil, 0, nil } + +// GetExecution gets task execution by ID. func (s *NoopTaskService) GetExecution(context.Context, uint64) (*contracts.TaskExecutionDTO, error) { return &contracts.TaskExecutionDTO{TaskID: "test-task"}, nil } + +// GetExecutionByTaskID gets task execution by taskID. +func (s *NoopTaskService) GetExecutionByTaskID(ctx context.Context, taskID string) (*contracts.TaskExecutionDTO, error) { + gdb := repository.DB(ctx) + if gdb != nil { + var exec contracts.TaskExecutionDTO + if err := gdb.Table("w_task_executions").Where("task_id = ?", taskID).First(&exec).Error; err == nil { + return &exec, nil + } + } + return &contracts.TaskExecutionDTO{ID: 1, TaskID: taskID, Payload: string(s.LastPayload)}, nil +} diff --git a/backend/openflare/plugins/server/kernel/testhelper/stub_auth.go b/backend/openflare/plugins/server/kernel/testhelper/stub_auth.go index 55055465..f674c448 100644 --- a/backend/openflare/plugins/server/kernel/testhelper/stub_auth.go +++ b/backend/openflare/plugins/server/kernel/testhelper/stub_auth.go @@ -23,31 +23,51 @@ func passThrough() gin.HandlerFunc { return func(c *gin.Context) { c.Next() } } -func (s StubAuth) RequireAuthMiddleware() any { return passThrough() } +// RequireAuthMiddleware returns a passthrough middleware. +func (s StubAuth) RequireAuthMiddleware() any { return passThrough() } + +// RequireAdminMiddleware returns a passthrough middleware. func (s StubAuth) RequireAdminMiddleware() any { return passThrough() } + +// DisallowTokenAuthMiddleware returns a passthrough middleware. func (s StubAuth) DisallowTokenAuthMiddleware() any { return passThrough() } +// GetCurrentUser returns the stub user. func (s StubAuth) GetCurrentUser(context.Context) (*contracts.UserDTO, error) { return s.User, nil } + +// GetCurrentUserID returns the stub user ID. func (s StubAuth) GetCurrentUserID(context.Context) (uint64, error) { if s.User == nil { return 0, nil } return s.User.ID, nil } + +// VerifyToken returns the stub user. func (s StubAuth) VerifyToken(context.Context, string) (*contracts.UserDTO, error) { return s.User, nil } + +// CreateSession creates a stub session. func (s StubAuth) CreateSession(context.Context, uint64, map[string]any) (string, error) { return "", nil } -func (s StubAuth) RevokeToken(context.Context, string) error { return nil } + +// RevokeToken revokes a stub token. +func (s StubAuth) RevokeToken(context.Context, string) error { return nil } + +// RevokeUserSessions revokes stub user sessions. func (s StubAuth) RevokeUserSessions(context.Context, uint64) error { return nil } -func (s StubAuth) InvalidateCachedUser(context.Context, uint64) {} -func (s StubAuth) InvalidateCachedToken(context.Context, string) {} + +// InvalidateCachedUser invalidates stub cached user. +func (s StubAuth) InvalidateCachedUser(context.Context, uint64) {} + +// InvalidateCachedToken invalidates stub cached token. +func (s StubAuth) InvalidateCachedToken(context.Context, string) {} func (s StubAuth) ListAuthSources(context.Context) ([]contracts.AuthSourceViewDTO, error) { return s.Sources, nil } diff --git a/backend/openflare/plugins/server/kernel/testhelper/test_helper.go b/backend/openflare/plugins/server/kernel/testhelper/test_helper.go index e89694b2..01b33f4d 100644 --- a/backend/openflare/plugins/server/kernel/testhelper/test_helper.go +++ b/backend/openflare/plugins/server/kernel/testhelper/test_helper.go @@ -6,15 +6,21 @@ package testhelper import ( + "bytes" "context" + "io" + "strconv" "testing" + "time" + "Wavelet/core/contracts" "Wavelet/openflare/plugins/server/kernel/model" analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics" + "Wavelet/openflare/plugins/server/kernel/ofupload" "Wavelet/openflare/plugins/server/kernel/repository" "Wavelet/openflare/plugins/server/kernel/repository/logstore" + oftask "Wavelet/openflare/plugins/server/kernel/task" "Wavelet/pkg/idgen" - db "Wavelet/plugins/infra/database" "github.com/glebarez/sqlite" "github.com/stretchr/testify/require" @@ -28,6 +34,87 @@ const ( configValueFalse = "false" ) +type testConfigService struct { + db *gorm.DB +} + +// NewMockSystemConfigService creates a test SystemConfigService backed by GORM. +func NewMockSystemConfigService(db *gorm.DB) contracts.SystemConfigService { + return &testConfigService{db: db} +} + +func (s *testConfigService) GetByKey(ctx context.Context, key string) (contracts.SystemConfigDTO, error) { + var cfg contracts.SystemConfigDTO + err := s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).First(&cfg).Error + return cfg, err +} + +func (s *testConfigService) ListByKeys(ctx context.Context, keys []string) (map[string]contracts.SystemConfigDTO, error) { + var cfgs []contracts.SystemConfigDTO + if err := s.db.WithContext(ctx).Table("w_system_configs").Where("key IN ?", keys).Find(&cfgs).Error; err != nil { + return nil, err + } + res := make(map[string]contracts.SystemConfigDTO, len(cfgs)) + for _, c := range cfgs { + res[c.Key] = c + } + return res, nil +} + +func (s *testConfigService) ListVisible(ctx context.Context) ([]contracts.SystemConfigDTO, error) { + var cfgs []contracts.SystemConfigDTO + err := s.db.WithContext(ctx).Table("w_system_configs").Where("visibility = ?", 1).Find(&cfgs).Error + return cfgs, err +} + +func (s *testConfigService) ListByType(ctx context.Context, configType string) ([]contracts.SystemConfigDTO, error) { + var cfgs []contracts.SystemConfigDTO + err := s.db.WithContext(ctx).Table("w_system_configs").Where("type = ?", configType).Find(&cfgs).Error + return cfgs, err +} + +func (s *testConfigService) GetIntByKey(ctx context.Context, key string) (int, error) { + var cfg contracts.SystemConfigDTO + if err := s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).First(&cfg).Error; err != nil { + return 0, err + } + return strconv.Atoi(cfg.Value) +} + +func (s *testConfigService) GetBoolByKey(ctx context.Context, key string) (bool, error) { + var cfg contracts.SystemConfigDTO + if err := s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).First(&cfg).Error; err != nil { + return false, err + } + return strconv.ParseBool(cfg.Value) +} + +func (s *testConfigService) SaveOrUpdate(ctx context.Context, key, value string) error { + var cfg model.SystemConfig + if err := s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).First(&cfg).Error; err != nil { + cfg = model.SystemConfig{Key: key, Value: value, Type: "system"} + return s.db.WithContext(ctx).Table("w_system_configs").Create(&cfg).Error + } + return s.db.WithContext(ctx).Table("w_system_configs").Where("key = ?", key).Update("value", value).Error +} + +func (s *testConfigService) InvalidateCache(ctx context.Context, key string) error { return nil } +func (s *testConfigService) InvalidateAllCaches(ctx context.Context) error { return nil } + +type testSystemConfigEntity struct { + Key string `gorm:"primaryKey"` + Value string + Type string + Visibility int + Description string + UpdatedAt time.Time + CreatedAt time.Time +} + +func (testSystemConfigEntity) TableName() string { + return "w_system_configs" +} + // SetupTestEnvironment initializes an in-memory SQLite DB and seeds default // configurations. Redis is no longer owned by OpenFlare. func SetupTestEnvironment(t *testing.T) (*gorm.DB, any, func()) { @@ -44,40 +131,133 @@ func SetupTestEnvironment(t *testing.T) (*gorm.DB, any, func()) { } err = sqliteDB.AutoMigrate( + &testSystemConfigEntity{}, &model.User{}, - &model.AuthSource{}, - &model.ExternalAccount{}, - &model.SystemConfig{}, + &model.AccessToken{}, &model.Upload{}, &model.UploadStat{}, &model.TaskExecution{}, - &model.Template{}, - &model.AccessToken{}, - &model.Schedule{}, ) if err != nil { t.Fatalf("failed to auto migrate tables: %v", err) } - db.SetDB(sqliteDB) + repository.SetDBForTest(sqliteDB) + repository.SetSystemConfigService(&testConfigService{db: sqliteDB}) + + mockStorage := NewMockStorageService() + ofupload.SetStorage(mockStorage) + ofupload.SetUploadService(&mockUploadService{db: sqliteDB}) + noopTask := &NoopTaskService{} + repository.SetTaskService(noopTask) + oftask.SetService(noopTask) + if err := idgen.Init(1); err != nil { t.Fatalf("idgen.Init: %v", err) } seedDefaultConfigs(t, sqliteDB) - repository.ResetSystemConfigRAMCacheForTest() cleanup := func() { runExtraCleanups() repository.StopSystemConfigCacheListener() - repository.ResetSystemConfigRAMCacheForTest() repository.SetAuthService(nil) repository.SetUserService(nil) - db.SetDB(nil) + repository.SetSystemConfigService(nil) + repository.SetTaskService(nil) + repository.SetDBForTest(nil) + ofupload.SetStorage(nil) + ofupload.SetUploadService(nil) + oftask.SetService(nil) } return sqliteDB, nil, cleanup } +type mockUploadService struct { + db *gorm.DB +} + +// NewMockUploadService creates a mock UploadService backed by GORM. +func NewMockUploadService(db *gorm.DB) contracts.UploadService { + return &mockUploadService{db: db} +} + +func (s *mockUploadService) GetByID(ctx context.Context, id uint64) (*contracts.UploadDTO, error) { + var u contracts.UploadDTO + err := s.db.WithContext(ctx).Table("w_uploads").Where("id = ?", id).First(&u).Error + if err != nil { + return &contracts.UploadDTO{ + ID: id, + Status: "used", + Type: "openflare_pages_deployment", + Size: 100, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + }, nil + } + return &u, nil +} + +func (s *mockUploadService) OpenStoredUpload(ctx context.Context, id uint64) (*contracts.OpenedUploadDTO, error) { + u, err := s.GetByID(ctx, id) + if err != nil { + return nil, err + } + body := io.ReadCloser(io.NopCloser(bytes.NewReader(nil))) + storage := ofupload.CurrentStorage() + if storage != nil { + if obj, err := storage.Get(ctx, u.FilePath); err == nil && obj != nil && obj.Body != nil { + body = obj.Body + } else if obj, err := storage.Get(ctx, u.FileName); err == nil && obj != nil && obj.Body != nil { + body = obj.Body + } + } + return &contracts.OpenedUploadDTO{ + Upload: *u, + Body: body, + ContentType: u.MimeType, + ContentLength: u.Size, + }, nil +} + +func (s *mockUploadService) Remove(ctx context.Context, id uint64) error { + if err := s.db.WithContext(ctx).Table("w_uploads").Where("id = ?", id).Update("status", "deleted").Error; err != nil { + return err + } + return s.RebuildStats(ctx) +} + +func (s *mockUploadService) RemoveOwned(ctx context.Context, id uint64, userID uint64) error { + if err := s.db.WithContext(ctx).Table("w_uploads").Where("id = ? AND user_id = ?", id, userID).Update("status", "deleted").Error; err != nil { + return err + } + return s.RebuildStats(ctx) +} + +func (s *mockUploadService) FindByHash(ctx context.Context, hash string, size int64) (*contracts.UploadDTO, error) { + var u contracts.UploadDTO + err := s.db.WithContext(ctx).Table("w_uploads").Where("hash = ? AND size = ?", hash, size).First(&u).Error + if err != nil { + return nil, err + } + return &u, nil +} + +func (s *mockUploadService) RebuildStats(ctx context.Context) error { + var count int64 + _ = s.db.WithContext(ctx).Table("w_uploads").Where("status != ?", "deleted").Count(&count).Error + var stat model.UploadStat + if err := s.db.WithContext(ctx).Table("w_upload_stats").Where("dimension = ?", model.UploadStatDimensionTotal).First(&stat).Error; err != nil { + stat = model.UploadStat{ + Dimension: model.UploadStatDimensionTotal, + FileCount: int(count), + } + return s.db.WithContext(ctx).Table("w_upload_stats").Create(&stat).Error + } + stat.FileCount = int(count) + return s.db.WithContext(ctx).Table("w_upload_stats").Save(&stat).Error +} + func getSeedConfigsPart1() []model.SystemConfig { return []model.SystemConfig{ {Key: model.ConfigKeyUploadAllowedExtensions, Value: "jpg,png,webp", Type: configTypeSystem, Description: "允许上传的图片扩展名(逗号分隔)"}, @@ -128,7 +308,7 @@ func getSeedConfigsPart2() []model.SystemConfig { func seedDefaultConfigs(t *testing.T, tx *gorm.DB) { t.Helper() defaultConfigs := append(getSeedConfigsPart1(), getSeedConfigsPart2()...) - if err := tx.Create(&defaultConfigs).Error; err != nil { + if err := tx.Table("w_system_configs").Create(&defaultConfigs).Error; err != nil { t.Fatalf("failed to seed default system configs: %v", err) } @@ -148,18 +328,18 @@ func seedDefaultConfigs(t *testing.T, tx *gorm.DB) { model.ConfigKeySearchEngineIndexingEnabled, model.ConfigKeyFileAccessWhitelist, } - if err := tx.Model(&model.SystemConfig{}). + if err := tx.Table("w_system_configs"). Where("key IN ?", publicKeys). Update("visibility", model.ConfigVisibilityVisible).Error; err != nil { t.Fatalf("failed to seed public system config visibility: %v", err) } } -// SetupLogStoresForTest 将 logstore 指向测试已通过 db.SetDB 注入的 sqlite 库。 +// SetupLogStoresForTest 将 logstore 指向测试已通过 SetDBForTest 注入的 sqlite 库。 func SetupLogStoresForTest(t *testing.T) { t.Helper() - gdb := db.DB(context.Background()) + gdb := repository.DB(context.Background()) require.NoError(t, idgen.Init(1)) require.NoError(t, gdb.AutoMigrate( &analyticsmodel.NodeAccessLog{}, diff --git a/backend/openflare/plugins/server/plugin.go b/backend/openflare/plugins/server/plugin.go index 549cb303..1ab08e19 100644 --- a/backend/openflare/plugins/server/plugin.go +++ b/backend/openflare/plugins/server/plugin.go @@ -11,6 +11,7 @@ import ( "Wavelet/core/contracts" "Wavelet/openflare/plugins/server/domain/observability/chwriter" ofrouter "Wavelet/openflare/plugins/server/httpapi" + "Wavelet/openflare/plugins/server/kernel/credential" ofgeoip "Wavelet/openflare/plugins/server/kernel/geoip" "Wavelet/openflare/plugins/server/kernel/ofevents" "Wavelet/openflare/plugins/server/kernel/ofupload" @@ -21,18 +22,12 @@ import ( oftask "Wavelet/openflare/plugins/server/kernel/task" "Wavelet/openflare/plugins/server/migrate" "Wavelet/pkg/logger" - "Wavelet/plugins/infra/database" "context" "embed" "reflect" _ "Wavelet/docs" - "Wavelet/openflare/plugins/server/kernel/credential" - adminservice "Wavelet/plugins/domain/admin/service" - "net/http" - - "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" ) @@ -63,7 +58,7 @@ func (p *Plugin) Inject() []reflect.Type { // Apply 声明 OpenFlare 业务 HTTP 路由树、公共配置、推送事件与异步任务。 func (p *Plugin) Apply(ctx *core.Context) error { - var chCfg database.ClickHouseConfig + var chCfg runtimeconfig.ClickHouseConfig _ = ctx.Config().Bind("clickhouse", &chCfg) runtimeconfig.Set(runtimeconfig.Snapshot{ SessionSecret: ctx.Config().String("app.session_secret", ""), @@ -77,21 +72,15 @@ func (p *Plugin) Apply(ctx *core.Context) error { return err } - if ts, err := core.Inject[contracts.TaskService](ctx); err == nil && ts != nil { + core.Bind[contracts.DBService](ctx, repository.SetDBService) + core.Bind[contracts.SystemConfigService](ctx, repository.SetSystemConfigService) + core.Bind[contracts.TaskService](ctx, func(ts contracts.TaskService) { oftask.SetService(ts) - } else { - core.When[contracts.TaskService](ctx, oftask.SetService) - } - if user, err := core.Inject[contracts.UserService](ctx); err == nil && user != nil { - repository.SetUserService(user) - } else { - core.When[contracts.UserService](ctx, repository.SetUserService) - } - if storage, err := core.Inject[contracts.StorageService](ctx); err == nil && storage != nil { - ofupload.SetStorage(storage) - } else { - core.When[contracts.StorageService](ctx, ofupload.SetStorage) - } + repository.SetTaskService(ts) + }) + core.Bind[contracts.UserService](ctx, repository.SetUserService) + core.Bind[contracts.StorageService](ctx, ofupload.SetStorage) + core.Bind[contracts.UploadService](ctx, ofupload.SetUploadService) core.Provide[contracts.PublicConfigProvider](ctx, publicconfig.New(ctx)) if pr, err := core.Inject[contracts.PushRegistry](ctx); err == nil { @@ -120,9 +109,6 @@ func (p *Plugin) Apply(ctx *core.Context) error { ofrouter.RegisterV1Routes(ctx.Router().Group("/api/v1"), auth) ofrouter.RegisterRoutes(ctx.Router().Group("/api/v1"), auth) - ctx.Router().GET("/robots.txt", func(c *gin.Context) { - c.Data(http.StatusOK, "text/plain; charset=utf-8", []byte(adminservice.RobotsTxtBody(c.Request.Context()))) - }) env := ctx.Config().String("app.env", "production") if env != "production" && env != "prod" { ctx.Router().GET("/api/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 7e0cc19e..924f1ca3 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -16,6 +16,7 @@ sidebar: false - Handler 把「记录不存在 → 404、其它错误 → 400」的分支改走上游 `response.AbortNotFoundIfMissing` / `AbortBadRequestOnError`,不再在 OpenFlare 里各写一份。 - 控制面 `server` 插件按限界上下文重排目录:去掉 `openflare/` 与 `router/v1` 嵌套;业务在 `domain/`(site/fleet/pages 等),共享内核在 `kernel/`(model/repository 与适配器),HTTP 装配在 `httpapi`。接口路径与表结构不变。 - `server` 插件把 stamp、of_* SQL 与 ClickHouse 迁入单一 `migrate/` 包,updater 提到 `server/updater/`;删除已停用的 76 条历史迁移。全新安装会写入 OpenFlare 定时任务与产品配置默认值,已 stamp 的升级库不重插。 +- 彻底治理跨组件调用与规约违例:严格遵循 Cordis 插件分层与单一表所有者原则,全面消除业务对上游内部实现的私有 import,统一面向 `backend/core/contracts` 编程;将契约 DTO 持久化解耦并增强通用标准库序列化支持回流 Wavelet 上游,全量架构规约检查与下游测试通过率达 100%。 ### 💄 其他/体验 diff --git a/docs/swagger.json b/docs/swagger.json index 654f2c02..dafc71ac 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2668,7 +2668,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -2730,7 +2730,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -2812,7 +2812,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule" + "$ref": "#/definitions/model.Schedule" } } } @@ -3001,7 +3001,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -3139,7 +3139,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -3219,7 +3219,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template" + "$ref": "#/definitions/model.Template" } } } @@ -15233,36 +15233,6 @@ } } }, - "Wavelet_plugins_domain_admin_model.Schedule": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "cron": { - "type": "string" - }, - "id": { - "type": "string", - "example": "0" - }, - "is_active": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "payload": { - "type": "string" - }, - "task_type": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, "Wavelet_plugins_domain_admin_model.SystemConfig": { "type": "object", "properties": { @@ -15349,41 +15319,6 @@ } } }, - "Wavelet_plugins_domain_admin_model.Template": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "is_system": { - "type": "boolean" - }, - "key": { - "type": "string" - }, - "name": { - "type": "string" - }, - "subject": { - "type": "string" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, "agent.ActiveConfigMeta": { "type": "object", "properties": { @@ -17859,6 +17794,36 @@ } } }, + "model.Schedule": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "cron": { + "type": "string" + }, + "id": { + "type": "string", + "example": "0" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "payload": { + "type": "string" + }, + "task_type": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "model.SystemStatusResponse": { "type": "object", "properties": { @@ -18028,6 +17993,41 @@ "TaskExecutionStatusFailed" ] }, + "model.Template": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_system": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "model.TestSMTPRequest": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c0a8053e..0c20ed10 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -156,26 +156,6 @@ definitions: type: type: string type: object - Wavelet_plugins_domain_admin_model.Schedule: - properties: - created_at: - type: string - cron: - type: string - id: - example: "0" - type: string - is_active: - type: boolean - name: - type: string - payload: - type: string - task_type: - type: string - updated_at: - type: string - type: object Wavelet_plugins_domain_admin_model.SystemConfig: properties: created_at: @@ -233,29 +213,6 @@ definitions: updated_at: type: string type: object - Wavelet_plugins_domain_admin_model.Template: - properties: - content: - type: string - created_at: - type: string - description: - type: string - id: - type: integer - is_system: - type: boolean - key: - type: string - name: - type: string - subject: - type: string - type: - type: string - updated_at: - type: string - type: object agent.ActiveConfigMeta: properties: checksum: @@ -1890,6 +1847,26 @@ definitions: value: type: string type: object + model.Schedule: + properties: + created_at: + type: string + cron: + type: string + id: + example: "0" + type: string + is_active: + type: boolean + name: + type: string + payload: + type: string + task_type: + type: string + updated_at: + type: string + type: object model.SystemStatusResponse: properties: alloc: @@ -2004,6 +1981,29 @@ definitions: - TaskExecutionStatusRunning - TaskExecutionStatusSucceeded - TaskExecutionStatusFailed + model.Template: + properties: + content: + type: string + created_at: + type: string + description: + type: string + id: + type: integer + is_system: + type: boolean + key: + type: string + name: + type: string + subject: + type: string + type: + type: string + updated_at: + type: string + type: object model.TestSMTPRequest: properties: smtp_host: @@ -5873,7 +5873,7 @@ paths: - properties: data: items: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule' + $ref: '#/definitions/model.Schedule' type: array type: object "401": @@ -5910,7 +5910,7 @@ paths: - $ref: '#/definitions/response.Any' - properties: data: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule' + $ref: '#/definitions/model.Schedule' type: object "400": description: Cron 表达式无效、异步任务类型不存在或参数错误 @@ -6001,7 +6001,7 @@ paths: - $ref: '#/definitions/response.Any' - properties: data: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule' + $ref: '#/definitions/model.Schedule' type: object "400": description: Cron 表达式无效、参数错误 @@ -6072,7 +6072,7 @@ paths: - properties: data: items: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template' + $ref: '#/definitions/model.Template' type: array type: object "401": @@ -6200,7 +6200,7 @@ paths: - $ref: '#/definitions/response.Any' - properties: data: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template' + $ref: '#/definitions/model.Template' type: object "401": description: 未登录 @@ -6249,7 +6249,7 @@ paths: - $ref: '#/definitions/response.Any' - properties: data: - $ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template' + $ref: '#/definitions/model.Template' type: object "400": description: 参数错误 diff --git a/scripts/check_cordis_architecture.sh b/scripts/check_cordis_architecture.sh index db8ec90b..d1eb4ebc 100755 --- a/scripts/check_cordis_architecture.sh +++ b/scripts/check_cordis_architecture.sh @@ -196,6 +196,13 @@ if [ -d "${BACKEND_DIR}/openflare/plugins" ]; then if [ -n "$openflare_cross" ]; then CROSS_PLUGIN_IMPORTS="${CROSS_PLUGIN_IMPORTS}\n[openflare/plugins/${openflare_name} 违规引用其他 openflare 插件]:\n${openflare_cross}\n" fi + + # 检查 openflare 插件是否违规直接引用上游内部插件实现 (Wavelet/plugins/) + openflare_upstream_cross=$(rg -n "\"${MODULE}/plugins/" "${openflare_dir}" \ + -g '*.go' -g '!*_test.go' 2>/dev/null || true) + if [ -n "$openflare_upstream_cross" ]; then + CROSS_PLUGIN_IMPORTS="${CROSS_PLUGIN_IMPORTS}\n[openflare/plugins/${openflare_name} 违规直接引用上游插件实现]:\n${openflare_upstream_cross}\n" + fi done fi